JournalReviving a Yale class project with Claude: Shoutify, SoundCloud-style comments for Spotify

Nov 1, 2026Side project

In spring 2023 my team built Shoutify for a software engineering class at Yale: Spotify's web player with comments, lyric annotations and a social feed. Three years later I gave the code to Claude Code. It came back as a TypeScript app on my home server where comments are pinned to moments in a song and pop up over a waveform as it plays, the way they do on SoundCloud. Here is how the revival went, and how Spotify's 2026 rules shaped it.

In spring 2023, my team built Shoutify for a software engineering class at Yale. It was Spotify's web player with a social layer on top: comments on songs, annotations on lyrics, and a Social tab with a feed. Our pitch was that streaming apps get one thing wrong: people talk about music everywhere except the app they listen in. We shipped it at the end of April, the class ended, and nobody touched the repository again.

Three years later I gave that code to Claude Code, Anthropic's coding agent (running Claude Opus 5.5). Over about thirty hours, from the evening of October 30 to the early hours of November 1, it became a TypeScript app that runs on my home server for me and a few friends. It looks like Spotify's current web player. It also finally does what Shoutify always hinted at: comments are pinned to moments in a song, and they pop up over a waveform when the song reaches them, the way they do on SoundCloud.

This post covers:

  • what we built in 2023;
  • what it took to make it run in 2026;
  • how the SoundCloud-style comments work;
  • how Spotify's 2026 rules shaped almost every decision;
  • what it was like to revive a team project with an AI agent.

The idea: SoundCloud's comments, inside Spotify

SoundCloud has had timed comments for years. A comment is attached to the moment in the track you were listening to. Small avatars line the waveform, and when the song reaches a comment, it pops up above the playhead. Listening to a popular track feels like listening in a room with everyone who heard it before you: someone flags the drop a second before it lands, someone else quotes the line they love.

Spotify has nothing like it. You can't comment on a song. Friend Activity shows what your friends are playing, but you can't say anything about it. The conversation happens on TikTok, in group chats and on forums, and then you go back to Spotify to listen.

Shoutify brings SoundCloud's comments to Spotify's catalogue. You listen in a Spotify-style player, and the comments of the people you follow on Spotify play along with the song.

In 2023 we got halfway there without quite noticing. Every comment saved the song's position when it was written (Spotify's progress_ms), and the comment card printed it as "@1:43". Nothing else used it. The SoundCloud-style player of 2026 is built on that one field.

The 2023 version

We didn't start from scratch. Shoutify is built on JL978's open-source Spotify clone, a React copy of Spotify's 2020 web player with a small login server, both under the MIT license. Between February 8 and April 28, 2023, the team added Shoutify on top in 171 commits:

Area

What it did

Built by (GitHub)

Comments

A Comment button in the player bar, a comment page for each song, comment counts in track lists

treylew01, scedanquah and me

Annotations

The playing song's lyrics from Musixmatch; select a line to annotate it

UwemWilson4, with scedanquah on the server

Social tab

A feed of everyone's comments

HarryShindika, scedanquah and treylew01

Server and deployment

Express routes for comments and annotations, MongoDB, Heroku and Netlify

scedanquah

Spotify plumbing

Login and token refresh, private playlists, Liked Songs, infinite scroll, search fixes

me

The 2023 architecture: a React 16 app on Netlify talks to an Express 4 server on Heroku over axios. The server handles login, a public proxy, a Musixmatch lyrics proxy and comment and annotation routes backed by MongoDB. Login, the public proxy and the comment routes are marked in red as unsafe; the browser calls the Spotify Web API directly when logged in.

2023

Front end

React 16, Create React App, React Router 5, JavaScript

Server

Express 4 on Heroku, with the front end on Netlify

Data

MongoDB through Mongoose

Lyrics

Musixmatch's API, with the team's key

Code

123 files: 5,493 lines of JavaScript and 2,115 of CSS

Tests

None

It worked for a class demo. The red boxes in the diagram are the parts that wouldn't survive contact with the internet, which I'll come back to.

The prompts

This is the whole brief I gave Claude on the first night:

I have a repository I worked in in which I cloned Spotify. The repository is the following: [the class repository], we also added things like commenting on [songs] and annotating [song] lyrics. I need you to do the following: Copy all of the code and use it as your starting ground. I don't want to contribute to this repo, rather I want to make a fresh one. Rewrite all of the code in TypeScript and update the API calls since this project was from 2023. For the time being, switch off the lyrics API and try and see if Spotify offers something native. Switch from MongoDB to something that we can self host here on this server, I'm going to want to use this among my friends so keep that in mind. Make small atomic commits

After that, each request came after I'd used what the last one built:

  1. Fix the bugs the 2023 version always had, and "touch up the CSS just slightly, I want it to look exactly like the Spotify webpage".
  2. Publish it to a new public repository on GitHub.
  3. A question: would an unofficial Musixmatch package be fine for lyrics, "solely for educational purposes"? The answer is further down.
  4. Six features, built one at a time, with a redeploy after each so I could try it. That's the round that made Shoutify SoundCloud for Spotify.

Getting a 2023 app to run in 2026

Claude imported the code into a new repository as one commit, without the class history (that stays in the class's private repository). It deleted the Heroku and Netlify configuration. Then it read the whole codebase, and Spotify's developer changelog, before changing anything.

Spotify moved the ground

Shoutify's biggest problem wasn't old code. Spotify's Web API in 2026 is much smaller than it was in 2023, especially for hobby apps.

In November 2024, Spotify stopped giving new apps related artists, recommendations, audio features, audio analysis, and category and featured playlists. In February 2026 it tightened Development Mode, the mode every app starts in and the only one open to individuals:

What changed

What it broke in Shoutify

What Shoutify does now

An app serves at most five accounts, each added by hand, and its owner needs Premium

Logged-out browsing, through the server's proxy

Login is the first screen, and the proxy is gone

Browse categories and their playlists are gone

Home's category rows, the Browse grid, genre pages

Home shows your top artists and tracks next to Liked Songs

Related artists and artist top tracks are gone

The Related Artists tab and the Popular row

Artist pages show releases

Other users' profiles and playlists are gone

Profile pages, and each comment's author name and picture

Comments store their author's name and picture; only your own profile shows

Playlist contents moved from tracks to items, and only come back for playlists you own or collaborate on

Every playlist page

Other people's playlists show their banner, and still play

Saving and following moved to /me/library, with Spotify URIs

Hearts on playlists, following artists

PUT and DELETE /me/library, GET /me/library/contains

Search and an artist's albums return at most 10 items per request

Long lists

Requests are split to fit

Some of these still answer apps created before February 2026, but Spotify has said they'll go too.

The five-account cap changed what Shoutify is. It can't be a public social network any more. It's a private one for five friends, and that suits it. It also means every design choice can assume a tiny user base: a SQLite file is plenty, and asking Spotify about every other user is cheap when there are four of them.

Holes a class project could get away with

Reading the code with fresh eyes turned up problems we never noticed in 2023, because a class demo never meets an attacker:

In 2023

Why it mattered

In 2026

Login redirected to /#access_token=…&refresh_token=…, and the access token lived in localStorage

Both tokens ended up in browser history, and any script on the page could read the token

The refresh token lives in an httpOnly cookie, and the access token only in memory, renewed a minute before it expires

/refresh_token read the wrong cookie

Every session died after an hour

Fixed, with tests

The browser sent each comment's author ID

Anyone could post as anyone

The author comes from a signed cookie issued at login

POST / fetched any URL a browser sent it, with the app's own Spotify token

An open proxy on our server

Removed, with logged-out browsing

CORS echoed back any origin, with credentials allowed

Any website could read /refresh_token responses

One origin, so no CORS at all

Comments could be read without logging in

Everyone's comments were public

Every API route needs a login

The cookies use the __Host- prefix, so other sites under my domain can't plant them. The server checks with Spotify every hour that you're still on the app's list of five, so removing someone takes effect within about an hour.

New foundations

With the API sorted out, the rest of the stack was replaced one piece at a time, each in its own commit:

  • MongoDB became SQLite. Comments live in one file through Node's built-in node:sqlite. There's no database server and no driver, and a backup is one file. With at most five users, write-ahead logging handles a few people commenting at once.
  • Express 4 in JavaScript became Express 5 in TypeScript. Node 24 runs TypeScript files directly by stripping the types, so the server has no build step. tsc only checks the types. Built-in fetch replaced axios, and node --env-file replaced dotenv.
  • Create React App became Vite. CRA's react-scripts 2 only built with --openssl-legacy-provider. Vite 8, React 19 and React Router 8 replaced it. Then every component was converted to strict TypeScript, one area at a time.
  • One origin. The server serves the built app and keeps its own routes under /api, so there's no second host and nothing to configure for CORS.
  • Docker. The image is built in two stages on Node 24 Alpine, and the runtime keeps about 4 MB of node_modules. The container runs read-only, as a regular user, with every Linux capability dropped. It sits behind the Traefik proxy that serves everything on my home server, with the SQLite file on a named volume.

Making it look like Spotify

The first deploy worked, and I immediately hit the bugs we'd lived with in 2023. My next message listed them:

When we open the app, it should auto-connect to the Spotify Clone Player instead of us having to manually choose. When we play, we shouldn't get JSON error message. Back and forward buttons should work and settings on playlists should come up with actual settings. When a song is playing it should be highlighted and the play button should switch to something indicating music playing.

Claude split this into two passes.

Bug hunts. Separate agents went through the player, the music pages, the social features and the app shell. Each one looked for bugs, and other agents tried to disprove every finding before anything was fixed. Claude ran this twice: on the 2023 code, and later on its own fixes. Two of the bugs that survived show what it found:

  • The Devices button couldn't open its own menu. React 19 runs a click's effects straight away, so the menu's "a click outside closes me" listener was attached while the same click was still bubbling. The menu closed as it opened. React 16 had run those effects later, so the bug arrived with the port.
  • Repeat was backwards. The player read Spotify's repeat modes with 1 meaning "repeat one", following a comment in the TypeScript type definitions. Claude read the source of Spotify's Web Playback SDK, where 1 means "repeat all" and 2 means "repeat one", and fixed the mapping.

A redesign. "Exactly like the Spotify webpage" meant Spotify's current layout, not the 2020 one the clone copied:

  • rounded panels on black;
  • Your Library on the left;
  • a right panel for Now playing, the queue and Friend Activity;
  • Spotify's player bar.

Claude built the shared frame first. Then five agents built the areas in parallel, each in its own git worktree: the home page, playlists and albums, the player, search, and Your Library. Their work was merged at the end. Claude compared screenshots with a reference screenshot of Spotify's web player at the same window size, until the panel edges lined up within about a pixel. Spotify's own typeface isn't free to use, so Shoutify uses Figtree, which is close.

I published that version to GitHub and started using it.

Six features, one deploy each

Then came the request that made Shoutify what it was always meant to be. It's long, so here is the heart of it:

At the moment, when you click on the comment button I don't like how the comment modal appears. Instead it should be the comment page for the song should appear and it should look like a Twitter layout in a way, with the most recent comments at the top. However, there should also be a SoundCloud-like player at the top that shouts out comments when it gets to the section that they mention, just how SoundCloud currently does it. The comment it chooses should be based on popularity of the comment so be sure to implement liking on the comments and commenting on comments.

The rest of it asked for:

  • a Social feed of my friends' comments;
  • friends that are exactly my friends on Spotify, with "no new features" for friendship;
  • Friend Activity in the right panel, and "Social" in the left menu;
  • a green, editable timestamp in front of each comment, with tests for bounds checking;
  • the Annotations button hidden;
  • a notification bell that mixes comments with new releases;
  • clicking a song further down the queue to play it.

It ended with "Build these features one at a time redeploying after each feature so I can see it and give feedback."

#

Feature

Live

1

The comment page, Twitter style, with the green time chip

October 31

2

Likes and replies

October 31

3

The SoundCloud-style player

November 1

4

Friends from Spotify, Friend Activity and the Social feed

November 1

5

What's New: notifications mixed with new releases

November 1

6

Click a song in the queue to play it

November 1

A comment page that reads like Twitter

The Comment button used to open a small dialog over the player. Now it opens the song's own page and puts the cursor in the composer, which works like a tweet box: your picture, your text, and a Post button (or Ctrl/Cmd+Enter). Comments are listed newest first. Each one says how long ago it was posted, the way Twitter does: "now", "45s", "12m", "3h", "5d", then "Oct 31". The 2023 schema had no posting time, so the server now stamps each comment itself, and a browser can't backdate one.

The comment page: a composer with a green 0:00 time chip before “Add a comment…” and a Post button, then comments newest first, each with a green time and how long ago it was posted. One comment has a filled green heart with 2 likes and a thread of three replies joined by a line.

The green chip at the start of the text is the moment in the song the comment is about. It follows the song while you listen, and stops as soon as you start typing or click the chip, so it marks the moment you reacted to. Click the chip to type a time; setting it jumps the song there, so you can check you've got the right moment before you post. The parser behind it is small and strict:

// A time someone typed, "1:05" or past an hour "1:02:05", in ms; null if it's anything else or after `duration`.
export function parseTime(text: string, duration: number) {
    const match = /^(?:(\d+):([0-5]\d)|(\d+)):([0-5]\d)$/.exec(text.trim())
    if (!match) return null
    const [, h, mm, m, ss] = match
    const ms = ((Number(h ?? 0) * 60 + Number(mm ?? m)) * 60 + Number(ss)) * 1000
    return ms <= duration ? ms : null
}

I asked for tests that check the bounds, and they read like a spec. On a 3:20 song, 3:20 is accepted and 3:21 isn't. 1:60, -0:01, 1:5, 1.05 and 1:05pm are turned down. Past an hour, 1:02:05 and 62:05 mean the same moment. A time that's turned down stays in the chip, marked, so you can fix it instead of losing it.

Likes and replies

The 2023 schema already had likes, replies and reshares fields on every comment. They were always zero: nothing ever changed them or showed them. Now likes and replies are real:

  • Likes: one per person per comment, in a comment_likes table, behind a heart that turns green.
  • Replies: a reply is a comment with a parentID. Threads are one level deep, the way Twitter shows a conversation. Replies sit under their comment, oldest first, joined to it by a thread line, and a reply to a reply starts with "@name". A reply belongs to its comment's moment in the song, so it has no chip of its own.

Both changes only add to the schema (a new column and a new table), so the live database picked them up on the next start, with no migration step. Likes and replies matter beyond the page itself: they decide which comment the player shouts out.

The SoundCloud-style player

This is the part I'd been picturing since 2023. The top of every song's page is now laid out like SoundCloud's track header, in Spotify's colours.

The song header: a green pause button, the title and artist on black labels, and a waveform that is green up to the playhead at 1:43 of 3:20. Avatars marked A, C and B sit along the bottom, and a bubble above the playhead shows Cy’s comment about the synth under the chorus.
  • The waveform is a seek bar. It's green up to the playhead and grey after. Click or drag to move the song; the arrow keys move 5 seconds, and Home and End jump to either end. If this isn't the song that's playing, clicking starts it from there.
  • Every comment has a marker along the bottom of the waveform: its author's picture or initial, at the comment's moment. Hover over one, or tab to it, to read it. Click it, and the page scrolls to that comment and lights it up.
  • While the song plays, the waveform shouts out the comment for the moment you're at, in a bubble above the playhead, the way SoundCloud does.

The waveform is made up, on purpose

SoundCloud draws its waveform from the audio file. Shoutify can't. Spotify's player is DRM-protected, so apps never see the audio, and since November 2024 new apps don't get the audio analysis that described a song's loudness over time either. So the waveform is generated from the Spotify track id: the same song always gets the same shape, but the shape isn't its sound.

// FNV-1a hashes the id into the seed of a mulberry32 generator.
let seed = [...id].reduce((h, c) => Math.imul(h ^ c.charCodeAt(0), 16777619), 2166136261)

From that seed, the code builds a shape that looks like a song rather than noise:

  • It picks eight "sections" of loudness and eases between them along a cosine curve.
  • It adds detail from bar to bar, smoothed over neighbouring bars.
  • It fades the intro and the outro.

A comment in the code says exactly what this is: a shortcut, to be replaced with real loudness if Spotify ever provides it.

Choosing the shout-out by popularity

I asked for the shout-out to go to the most popular comment. This is the whole rule:

// The comment the waveform shouts out at `position` (ms) in the song: of the top-level comments about the 4 s up to
// it, the most liked, then the most replied to, then the newest (the highest id, as older comments have no date).
export const shoutOut = (comments: SongComment[], position: number) =>
    comments.filter(({parentID, timestamp}) => parentID === null && timestamp <= position && timestamp > position - 4000)
        .sort((a, b) => b.likes - a.likes || b.replies - a.replies || b.id - a.id)[0] ?? null
Diagram of the shout-out rule with three comments: Ana at 1:38 with 1 like, Bo at 1:40 with 1 like and 2 replies, Cy at 1:43 with 3 likes. With the playhead at 1:41, Ana and Bo are in the 4-second window and Bo wins on replies. At 1:43, Bo and Cy are in it and Cy wins on likes. At 1:48 the window is empty and no bubble shows.

Each comment stays up for four seconds after its moment, long enough to read a line. Within that window:

  1. The most liked comment wins.
  2. If likes are tied, the comment with more replies wins, since starting a conversation counts as popular too.
  3. If that's tied too, the newest comment wins.

If ten people commented on the drop, you see the one your friends liked, not whichever was posted first.

Keeping time without redrawing everything

A waveform that follows the song needs to know where the song is several times a second. Spotify reports it only now and then:

  • When the song plays in the browser, the Web Playback SDK sends an event each time something changes.
  • When it plays on another device, such as your phone, Shoutify asks the Web API every 5 seconds.

Between reports, Shoutify works out the position from the time since the last one. Counting real time instead of timer ticks keeps it right even in a background tab, where browsers slow timers down.

The first draft kept that clock in the app's top-level React state, so every report redrew the whole app, track lists included, to move one playhead. Claude caught it before the feature went live. It moved the clock into a tiny store read with React's useSyncExternalStore, so only the parts that show the time redraw.

Friends, exactly as Spotify has them

My brief was strict: "The friends you have should be the exact friends that are your friends on Spotify, no new features should be included for this." So there are no friend requests and no follow button. Spotify makes this harder than it sounds:

  • Friend Activity has never been part of Spotify's public Web API. Spotify's desktop app gets it from a private service.
  • Since February 2026, an app in Development Mode can't even list whom you follow. It can only ask whether you follow particular users, 40 at a time, with GET /me/library/contains?uris=spotify:user:….

Shoutify turns the second limit into the answer. It knows its own users, and there are at most five. So at every login, and again at every hourly token refresh, the server asks Spotify which of them you follow, and keeps the answer. Your friends on Shoutify are the people you follow on Spotify who also use Shoutify. If Spotify doesn't answer, the last list stays and your login still goes through.

Activity works from the inside. Each person's Shoutify player tells the server when the song changes, or when they press play or pause. When they open Shoutify, it also reports their latest play from Spotify's listening history. If reports arrive out of order, the newer one wins, and no report can be dated in the future, so a device whose clock runs fast can't block its own later reports.

Two panels. Social: “The latest comments from the people you follow on Spotify”, with Ana’s comment at 0:30 on Track One. Friend Activity: Ana playing Track One with green bars, Cy 12 minutes ago, Di 3 hours ago, and Ed, who hasn’t played anything on Shoutify yet.
  • Friend Activity sits in the right panel, as in Spotify's desktop app. It shows each friend's picture, song, artist and album, with moving green bars while they're playing and "12m" or "3h" once they've stopped. It reloads every minute while it's open.
  • Social is pinned in Your Library under Liked Songs. It shows the comments your friends post, newest first, whatever their likes, each with its song.

The catch: Friend Activity only knows what your friends play while Shoutify is open, plus their latest play when they open it. Spotify's own panel sees everything.

What's New: comments and releases in one list

I asked for the bell in the top bar to show every kind of news, with comments mixed in among new releases. Spotify doesn't give apps notifications or a new-releases feed, so Shoutify builds both:

  • Shoutify's notifications come from my server: replies to your comments, likes on them, and new comments from the people you follow, over the last 30 days.
  • New releases come from Spotify: what the artists you follow released in the last 30 days, from each artist's ten latest releases (the most Spotify returns at once). Everyone on a Shoutify server shares one Spotify request quota. So the browser asks about at most three artists at a time, and keeps each answer for 12 hours.

Both kinds go into one list, sorted by time, with All, Music and Comments filters:

  • A release counts from midnight on its release day. Spotify dates some releases only by month or year; those can't be placed, so they count as old.
  • A blue dot marks anything new since you last opened the page, and the bell keeps a dot until you do.
What’s New, in one list: Bo liked your comment and Ana replied to it 3 minutes ago, Ana commented on Track One 4 minutes ago, then two releases from artists you follow, Single Three from 2 days ago and Album Two from a week ago, each with a play button.

Clicking ahead in the queue

The last feature is the smallest. In Spotify you can click a song further down the queue to play it; in Shoutify you couldn't. The Web API has no "play this queue position" command, so Shoutify sends "next" once for every song before the one you clicked, then asks Spotify for the new state. A song far down the queue takes a moment.

The first version had a race. For about half a second after the skips, the panel still showed the old queue, so a quick second click counted from positions that had already moved. Now a queue that has been skipped from ignores clicks until it's been read again. If three seconds pass first, it takes clicks again: that covers Spotify playing the same song again, when the queue isn't reloaded.

Why the annotations are hidden

Annotations were the other half of Shoutify in 2023. The app showed the playing song's lyrics, from Musixmatch's API with our team's key, and you could select a line and write a note on it, as on Genius.

In 2026 there's nothing to annotate. Spotify's Web API has no lyrics at all. The lyrics in Spotify's own apps come from an internal service that needs a web-player session cookie, and Spotify's Developer Terms put it off-limits. So, as I'd asked, the lyrics are switched off. The annotations page still works if you type the line yourself, but its button stays hidden until there's a lyrics source.

I asked about one shortcut: musicxmatch-api, an unofficial Python package that pulls lyrics from Musixmatch for free. Would using it break Spotify's terms if it was "solely for educational purposes"? Claude read the package's code before answering. It said yes, for two reasons:

  • Musixmatch's terms. The package decodes a hidden signing key from Musixmatch's website code and poses as their web app. Musixmatch's API Terms forbid harvesting data from their site "in any way other than by accessing the API with the User Authentication Key".
  • Spotify's terms. Spotify's Developer Policy says not to build products "integrated with streams or content from another service". Its Developer Terms also require the rights to any content an app shows, and lyrics belong to the songwriters' publishers.

"Educational" changes neither. In practice, Spotify could revoke the app's access, which would end logins and playback for everyone. Claude's suggestion was better than the shortcut: comments are already pinned to a moment in the song, so you can quote the line you mean.

Testing without anyone's Spotify account

Testing a Spotify app is awkward. Every page needs a logged-in account, Spotify allows five, and I didn't want tests playing music on my real account. So Shoutify's browser tests run against a fake Spotify:

  • The real Shoutify server runs in Docker, with a small preload that answers Spotify's login and identity endpoints and the follow check. Each refresh cookie logs in as a different fake person: Victor, Ana or Bo. On this fake Spotify, Victor follows Ana but not Bo, which is exactly what the friends feature needs to test.
  • Headless Chromium, driven by Playwright, uses the app the way a person would. Fixtures answer every Web API call, and every album cover is drawn as a colour gradient. That's why the screenshots in this post have gradient covers and names like "Artist One".
  • There are 136 checks, from logging in to liking a reply and skipping ahead in the queue. A check fails on any console error or failed request it didn't expect.

Next to them, 78 unit tests (Node's built-in test runner) cover the rules: the time parser, the shout-out choice, the waveform, the What's New mix, and the server's routes for comments, likes, friends and notifications.

What none of it can tell me is how Shoutify behaves with real accounts, in Firefox or Safari, or on a phone. That's what my friends are for.

Before and after

The 2026 architecture: the browser (React 19, Vite 8, TypeScript) runs the Spotify pages, the Web Playback SDK, a playback clock feeding the comment page, and the social pages, and calls the Spotify Web API directly. Over HTTPS it reaches Traefik on my home server, then one Shoutify container (Node 24, Express 5) that keeps a SQLite file and talks to Spotify Accounts and the Web API for login and follows.

2023

2026

Front end

React 16, Create React App, React Router 5, JavaScript

React 19, Vite 8, React Router 8, strict TypeScript

Server

Express 4 on Heroku, front end on Netlify

Express 5 on Node 24, TypeScript with no build step, one Docker container

Data

MongoDB

One SQLite file

Sign-in

Tokens in the URL and localStorage; logged-out browsing through an open proxy

Refresh token in an httpOnly cookie, access token in memory, a signed author cookie; login required

Who can use it

Anyone with a Spotify account

Five accounts, Spotify's cap

Comments

Text and the moment in the song

Plus posting time, likes, replies, and a waveform that plays them back

Social

A feed of everyone's comments

Friends from Spotify follows: Friend Activity, a Social feed, What's New

Lyrics

Musixmatch, with annotations

Off, with annotations hidden

Tests

None

78 unit tests, 136 browser checks

What working with Claude was like

A few things stood out:

  • It checked what it couldn't know. Spotify's February 2026 changes came after its training data, so it worked from Spotify's migration guide and changelog. The one change it couldn't confirm without a real account is flagged in its commit message. When a type definition and Spotify's SDK disagreed about repeat modes, it read the SDK's source.
  • It used several agents when the work split cleanly. The bug hunt had finders and skeptics, and the redesign had one agent per area. When I said the review rounds were more than I needed ("I think I can test things myself by just using it"), it dropped them. From then on, it built a feature, ran the tests once, deployed, and let me use it.
  • It kept me in the loop. Each feature was built on a dev branch in a separate worktree, then moved to main and deployed on its own. Each deploy came with a short note on what to try and what hadn't been checked.
  • It corrected itself. Its first fix for comment authors checked every new token with Spotify. A review then caught two problems: a token from any Spotify app would pass, and each page load fired a burst of those checks, which my server's fail2ban could mistake for an attack. The second version signs an author cookie at login. It caught the queue race in feature six itself.
  • It told me when the bug wasn't in the app. When I pasted a wall of CORS and WebSocket errors from Firefox, it traced them to my browser blocking Spotify's player (tracking protection and DRM settings). Nothing in Shoutify needed to change.
  • It said no when it should have. The lyrics question is the clearest example.
  • It was upfront about limits. The waveform is decorative, Friend Activity only sees Shoutify, and the tests never touched a real account. Each of those is written down in the README or a code comment, not only in a chat.

By the numbers

2023

2026

Who

A team of students

Me and Claude Code

Time

11 weeks, February 8 to April 28, 2023

About 30 hours, October 30 to November 1, 2026, breaks included

Commits

171, on top of the base project's 25

167

Application code

5,493 lines of JavaScript, 2,115 of CSS

6,471 lines of TypeScript (5,906 in the app, 565 on the server), 4,797 of CSS

Tests

None

78 unit tests and 136 browser checks, about 4,400 lines

Database

MongoDB

One SQLite file

Hosting

Heroku and Netlify

One Docker container on my home server

Users

Anyone with a Spotify account

Five, Spotify's cap

What I took away

  • Old schemas hold old ideas. In 2023 we saved progress_ms with every comment and barely used it. The SoundCloud-style player is mostly a new way to read data we'd been storing all along.
  • The platform's rules are the real spec. Five accounts made Shoutify a friends-only app. With no follows list, "friends" became the people you follow who use Shoutify. No audio data made the waveform decorative, and no lyrics hid the annotations. None of that was in my brief, and all of it shaped the result.
  • A rewrite is also a security review. Tokens in URLs, authors taken from request bodies and an open proxy were fine for a class demo. They aren't fine for anything on the internet, even for five people.
  • A fake service makes honest tests. A fake Spotify with fake friends let 136 checks run without anyone's password, and every screenshot here comes from it.
  • Ship one thing at a time. Six small deploys meant six chances to say "not quite" before the next feature was built on top.

What's next

  • Lyrics, if a source turns up that Spotify's terms allow. Then the annotations come back.
  • Real accounts: getting my friends on it, and testing in Firefox, Safari and on phones.
  • Replies to replies: today they only notify the author of the comment at the top of the thread.
  • A real waveform, if Spotify ever gives apps loudness data again.

Thanks to my 2023 teammates, whose ideas this still runs on, and to JL978 for the clone we started from. The code is on GitHub. Spotify only lets an app in Development Mode serve five accounts, so my copy is for me and a few friends. You can run your own: create a Spotify app, fill in three settings, and start it with Docker Compose. The README walks through it.