Writing · Side project

Sample Explorer: I built a family tree for every sampled record by describing it to AI

One evening to build, a week to defend from the robots. The idea, the tech, and exactly how I prompted to get there.

I built a family tree for every sampled record in one evening, by describing it. Then I spent a week learning that the hard part of a website isn't the website. It's the robots.

Sample Explorer's page for Think (About It) by Lyn Collins: the sleeve, 586 records that sampled it, producer and label credits, and below, the lineage graph with decade groups along a timeline.
Think (About It), 1972: sampled in 586 records. Everything here was designed, built and deployed by Claude from my descriptions.

01The idea

At 15, Endtroducing rewired my ears. DJ Shadow built that record almost entirely out of other records, and once you know that, you can't unhear it. Every sampled record is a door into another one. Behind the door is a drum break from 1969, a soul B-side, a film score nobody bought.

I've always wanted to follow that chain properly. Pick a record, see what it borrowed, see who borrowed from it, and keep going until it's 2am and you're listening to a library record from a Swedish bank advert.

The obvious place for that is WhoSampled. It has no public API, and its terms ban scraping, so it was out. Genius, it turned out, has the same kind of data and does let you ask for it.

Sample Explorer is the result. Search any record and see what it sampled and what sampled it. Follow the chain as a graph where left to right is time. Press play on any of it.

The Sample Explorer homepage: 'Every record is a family tree.' with a search box, and a small graph of Amen, Brother, sampled in 689 tracks.Home
The homepage's breaks grid: Amen, Brother, Think (About It), Funky Drummer, Impeach the President, Synthetic Substitution and It's a New Day, each with a strip of dots showing who sampled it and when.Start with the breaks
Start with a search, or with the breaks hip-hop was built on. Each strip of dots is a record's samplers, by year.

02Check the crates first

I didn't start with a design. I started with the question every designer should ask before drawing anything: does the stuff we need actually exist?

So the plan began with a "data spike": half a day to check whether Genius really knows who sampled what, before building a single screen. Ten well-known records, three sources asked about each.

RecordGenius: sampled inMusicBrainz: sampled by30s preview
Amen, Brother68350✓
Think (About It)5770✓
Funky Drummer3388✓
Apache760✓
Ain't No Half-Steppin'733✓
Records that sampled each break: Genius knows far more than MusicBrainz. Amen, Brother 683 against 50; Think (About It) 577 against 0; Funky Drummer 338 against 8; Apache 76 against 0; Ain't No Half-Steppin' 73 against 3. GENIUS: SAMPLED IN MUSICBRAINZ: SAMPLED BY Amen, Brother 683 50 Think (About It) 577 0 Funky Drummer 338 8 Apache 76 0 Ain't No Half-Steppin' 73 3
The data check, drawn. Genius knew about ten times more, and MusicBrainz often knew nothing at all. The long bars were also the design brief: 683 records won't fit on one screen.

Genius won by a distance, and the spike said "go" the same day. It also handed me the design problem for free: Amen, Brother has 683 records hanging off it. You can't draw 683 lines and call it an interface. Whatever I built had to fold, group and reveal.

That's research, just faster. Half a day of checking saved me from designing something beautiful on data that wasn't there.

03What got built

The first evening was the product. The next ten days were the polish, and then a week of fighting robots (more on that later). Here's the tally.

1evening to a working product
95commits
11days of building
0lines of code I typed
WhatHow much
First evening21 commits in under three hours: search, track pages, the graph, previews, share images, artist pages, dark and light themes, and a player that keeps playing between pages
Code writtenAbout 9,900 lines
Starting points on the homepage7 breaks hip-hop was built on, and 12 artists
Producers credited1,198 records that sampled the homepage breaks
Ideas I killed3, one of them a whole second version of the site

What you can do with it:

Connect two artists: The Jackson 5 to Taylor Swift, two samples apart, via I'll Be There, two DJ Earworm mashups and Fortnight.Connect
Compare: Think (About It) interpolated Think by The 5 Royales, 15 years later, with both sleeves side by side and play buttons.Compare
The Sampled in list for Think (About It), oldest first, with the player docked at the bottom playing the track.Play
Connect two artists (the Jackson 5 and Taylor Swift are two samples apart), compare an original with its sampler, or press play and keep browsing.
Who samples this most for Think (About It): top artists, top producers, and a bar chart of samplers by decade, busiest in the 2020s.
Who samples this most: the artists, producers and decades behind a record. Think (About It) is still being sampled more than ever.

04How it works

If you're a designer, read the plain-English bit of each step and skip the code. If you build things, the code is there for you.

From a search to a record playing: a search, the page on the server, the store, Genius only when the store doesn't know, the graph in the browser, and a 30-second preview. 01 · YOUA search"think about it"or a link 02 · NEXT.JSThe pagebuilt on the server,keys stay there 03 · STOREMemoryevery answer keptfor months 04 · GENIUSThe dataonly if the storedoesn't know 05 · D3The graphx is the year,drawn in SVG 06 · DEEZERPlay30 seconds,direct to you tap any dot and round it goes again, one record further down the chain
One trip down the chain. The store in the middle is the bit I didn't know I needed until week three.

1. Genius is the record shop

Every record page asks Genius one question: what does this song sample, and what samples it? Genius answers with a list, and the page sorts it oldest first so it reads like a timeline.

Genius takes up to ten seconds to answer for a heavily sampled record, and it only lets you ask 10,000 questions a day. So every answer is remembered. The second person to open Think (About It) gets it instantly, and costs nothing. That turned out to matter far more than I expected. Hold that thought.

The first visitor to a record makes the site ask Genius, which takes 6 to 10 seconds and uses one of 10,000 daily questions; the answer is kept for months. Everyone after gets it instantly from the store, and Genius is never asked. 01 · FIRST VISITORThink (About It)opens the page STOREMemorynothing yet GENIUSThe questionone of 10,000a day RESULTThe page6–10 seconds the answer is kept for months 04 · EVERYONE AFTERThink (About It)opens the page STOREMemoryalready knows GENIUSThe questionone of 10,000a day RESULTThe pageinstant Genius never asked costs 1 questioncosts nothing
Ask once, remember for months. The first person to open a record pays for it in seconds; everyone after gets it for free.

2. A graph where left to right is time

Most network graphs are a hairball: dots pushed around until they stop moving. That tells you what's connected, but not when. In music, when is the whole story.

So every dot is pinned to its release year. The physics only gets to move dots up and down, never sideways. The result reads like a family tree on its side: originals on the left, the records that borrowed from them spreading out to the right.

The same records drawn twice. Left, a usual network graph: a tangle that shows connections but not time. Right, every dot pinned to its release year, so originals sit on the left and the records that borrowed from them spread to the right. MOST GRAPHSwhat's connected SAMPLE EXPLORERwhat's connected, and when 1970s 1980s 1990s 2000s 2010s 2020s
Same records, same connections. On the left, physics decides where dots go. On the right, the year does: the original sits on the left and the borrowing spreads out to the right.
TypeScript · time runs left to right
// every dot is pulled hard toward its release year; only y is free to settle
simulation
  .force("x", forceX((d) => d.targetX).strength(1))
  .force("y", forceY((d) => d.targetY).strength(0.25))

Then there's the 683 problem from the spike. Up to 16 records on a side are shown one by one. Past that, they fold into decade groups: a circle for the 1990s, one for the 2000s. Zoom in and the groups open; zoom out and they close. Open one and every other line fades back, so you can see which records you just let in.

The lineage graph zoomed out: Think (About It) with six decade circles, 40 in the 1980s, 130 in the 1990s, 40 in the 2000s, 115 in the 2010s and 222 in the 2020s.Zoomed out
The graph at its starting zoom: the earliest decades open into individual records, the later ones still grouped.Start
Zoomed all the way in: groups open into hundreds of individual records, up to the 300-record limit.Zoomed in
Same record, three zoom levels. Out, the decades fold into circles. In, they open into individual records, up to 300 on screen.

3. Every record plays

Each record gets a 30-second preview from Deezer, matched by artist and title. The audio never passes through my site: the page asks Deezer where the clip is and sends your browser straight there. That keeps it fast and keeps the audio off my bandwidth bill.

The player lives at the bottom of the screen and keeps playing as you move between pages. Tap a dot in the graph and it plays. Press play on a list and the whole list becomes a queue.

4. Everything else

An Instagram story card for Think (About It): the sleeve, 586 samplers, and a strip of dots by year from 1972 to 2026.
A story card, ready for Instagram.

05How I prompted

Same as last time: these are my real messages, typos and all. Most of the later ones were typed on my phone. I didn't write a clever prompt. I wrote the way I'd talk to a designer sitting next to me.

Describe the behaviour, not the build

Add zooming to the graph: When zooming in to the lineage graph expand the groups and when zooming out close then again

When tapping or clicking on a node in the graph, auto play that song

Two sentences, two features. I said what should happen when someone does something. How zoom levels map to decade groups was Claude's problem.

Name the worry before it's a bug

When a group has been opened, is there a way to differentiate between the lines of the open group and the rest? The problem I foresee is that in the heavily sampled tracks there are SO many lines that it becomes heavy and fiddly

Claude came back with two options. I said "Yes build both". Groups now fan out from their circle, and focusing one fades every other line. Spotting the problem was my job. Solving it was a two-minute conversation.

The graph with the 1990s group focused: its lines light up in acid green while every other line fades, and a panel below says 130 tracks sampled it from the 1990s, including Janet Jackson and Madonna.
"SO many lines." The 1990s group focused: its 130 records light up, everything else steps back.

Ask for a list, then edit it

Any more visual improvements to the graph? For readability and discovery

Do them all apart from 5

That's a design review in two lines. Claude did the thinking out loud, I did the editing. Visited rings, halos and a position strip went in. Number five didn't, and the reason why is the best part of this whole story.

Admit when you got it wrong

Swipe left = previous track
Swipe right = next track

Sorry that's wrong; left = next and right = previous

Same minute. It's cheaper to say "sorry, I got that backwards" than to live with it. Nobody sulked.

Three phone screenshots: the homepage, the Think (About It) page, and the lineage graph with the player docked at the bottom showing a pause button.
On a phone: search, the record, and the graph you swipe through. The player docks at the bottom: play or pause, and swipe the title to skip.

Kill things fast

Three ideas died, and I'm proud of all three deaths.

Remove the v2 completely, it's dull in comparison

I'd asked for a whole second version of the site on Meta's Astryx design system, side by side with the original. It took about a quarter of an hour to build and one look the next morning to bin. It was tidy, consistent and completely forgettable. Worth seeing, so I knew.

this is terrible, let's not do this

delete it

That was a prototype of the graph as a race track: you drive towards the future over the horizon. Before building it I asked for the UX pitfalls, which was smart. Then I went ahead anyway, which was not. It was terrible. It took two messages to get rid of.

The third was number five, which we'll get to.

Ask for a sense check, not a yes

I think the target symbol in the player might be overkill now. Thoughts ?

Claude agreed and said why. I said "yes remove it". Sometimes you just want a second pair of eyes before you delete something.

Report it like a user

urls like this are broken

Followed by a link: the Jackson 5 to Taylor Swift. No diagnosis, no theory. The cause was that the first search for a big pair ran longer than a web page is allowed to, so the page died before it answered. Now it searches in slices and shows "Still digging" while it works.

06What broke

The usual suspects. None needed me to understand the cause, only to notice it.

What I sawWhat was actually wrongThe fix
"Link copied" was hidden on phonesThe phone's share sheet slid up over itMoved the message to the top of the screen
Zooming the graph was "very janky"Every zoom re-ran the physics for up to two-thirds of a second, and the scroll started before the dots movedDropped a force the time axis didn't need, and made the view glide with the dots
Long track names were cut off in the playerNot enough room next to the controlsScrolling titles on phones; on desktop, controls moved right so the title gets the space
Connect pages for big pairs diedThe first search took longer than a page is allowed to runSearch in 12-second slices and carry on
Search stopped working, pages erroredRobots. A lot of robotsSee below. It took a week

07The 10,000 problem

This is the bit I didn't see coming, and the bit I learned the most from.

Genius lets you ask 10,000 questions a day. Remember, every answer is kept, so that sounded like loads for a side project with a few visitors. Then search started failing and pages showed errors. So I sent this:

the limit still seems to be hit frequently with small usage, we need to fix this

When we dug in, Genius had counted 44,708 questions in a single day. My analytics showed a handful of people. Something didn't add up, and finding it was four layers deep.

Layer 1: the site was being helpful

Websites quietly load the pages behind the links you can see, so the next click feels instant. On most sites that's free. On mine, loading a record page meant asking Genius about it. So one person opening one record also asked about every record listed on it, like a waiter bringing every dish on the menu just in case.

Claude proved it with a fake version of Genius that counted questions. One page view: 21 questions. Fixed, it was one.

Before: opening one record page asked Genius about the page plus every record listed on it, 21 questions. After: one question, and other records load only when clicked. BEFORE · ONE PAGE VIEWAFTER · ONE PAGE VIEW You openThink (About It) row 1: ask Genius row 2: ask Genius row 3: ask Genius row 4: ask Genius row 5: ask Genius row 6: ask Genius row 7: ask Genius row 8: ask Genius row 9: ask Genius row 10: ask Genius row 11: ask Genius row 12: ask Genius row 13: ask Genius row 14: ask Genius row 15: ask Genius row 16: ask Genius row 17: ask Genius row 18: ask Genius row 19: ask Genius row 20: ask Genius the page itself: ask Genius 21 questionsfor one person, one click You openThink (About It) the page itself: ask Genius 1 questionthe rows load when you click them
The helpful waiter. Loading the pages behind every visible link made one page view cost 21 questions, more on a heavily sampled record. Switching that off made it one.

Layer 2: the memory had amnesia

Remember the remembering? It turned out the built-in memory empties every time the site is updated, and can drop things whenever it likes. So every deploy started from scratch. The fix was a proper store, Upstash, that keeps every answer for months, whatever happens to the site. It took me two minutes to switch on, from my phone, once I found the right menu.

Layer 3: the limit never lifted

Genius counts refused questions too, over a rolling 24 hours. Each copy of my site stopped asking only after it was personally refused, and there were lots of copies. So the count never fell, and the site never recovered. Now one refusal stops every copy at once, until the time Genius says to come back.

Then I added a usage page so we could finally see real numbers. When the block lifted that evening, 9,999 questions went through in under three hours. About one a second. That wasn't people.

Layer 4: the robots

With a counter on the door, the culprits introduced themselves.

WhoPages in a dayWhat we did
AI and SEO crawlersNot counted: they came in through a side door my robots.txt didn't coverTurned away at every door
Unnamed bots9,614Turned away at the door
Bing (Google barely showed up)700 to 1,300, and it would happily crawl foreverWelcome, with a daily allowance of 1,000 questions
"Browsers"6,512, while the site was downA browser check, below

The last row was the clever one. A scraper was posing as ordinary visitors: one from Germany, one from Spain, one from Peru, each on a different network, each running a browser a couple of years out of date. You can't block that by name or by address.

So the first page anyone loads now runs one line of JavaScript that sets a cookie and reloads. A real browser does it in a blink, and you'll never notice. A script that only downloads pages never gets past it.

Every request meets a doorway. AI and SEO crawlers, unnamed bots and scripts posing as browsers are turned away. Search engines get 1,000 questions a day. People pass a one-line browser check without noticing. Only those reach the store, and Genius only for records nobody has opened before. EVERY REQUESTKnock knockpeople and robots AI and SEO crawlerson the blocklist by name TURNED AWAY Unnamed botssay they're a bot, not which TURNED AWAY Search enginesGoogle, Bing, Apple, DuckDuckGo 1,000 A DAY Scripts posing as browserscan't run the one-line check TURNED AWAY Peoplepass the check in a blink STRAIGHT IN STORE, THEN GENIUSThe pagestored? free.new? one question.
The door, after the week of robots. Three kinds of visitor never reach a page that could cost a question. Search engines get an allowance. People walk straight in.
TypeScript · search engines get an allowance
// a page that's already stored costs nothing; anything new spends today's allowance
if (await redis.exists(storedKey)) return true;
return (await bump("visits", ["bot-budget"], cost)) <= BOT_BUDGET;
44,708questions in a day, at worst
~10,700scraper visits turned away the next day
1,551questions by that evening
0refused
Genius questions per day against the 10,000 limit: 44,708 on the worst day, 9,999 on 29 October in under three hours, 185 on 30 October while blocked, and 1,551 on 31 October with none refused. GENIUS LIMIT · 10,000 44,708 WORST DAYbefore the fixes 9,999 29 OCTin under three hours 185 30 OCTblocked most of the day 1,551 31 OCTnone refused
Questions per day against the limit. The worst day, before any of this, was more than four times over. Two days after the fixes: 1,551, and Genius didn't refuse a single one.

And number five? It was producer counts across the whole graph. Lovely idea, but Genius only gives producers one record at a time, so every page would have cost dozens of extra questions. I said "Let's not build it then", a full two weeks before I understood how right that was. It came back later in a cheaper form: counted once, in advance, for the homepage breaks, and only on request everywhere else.

I couldn't have diagnosed any of this myself. But every layer started with me noticing something felt off and saying so in a sentence. "Small usage" was the clue. The rest was Claude counting.

08What I took from it

The Listening Room taught me that taste is the bottleneck. This one added a few things.

Research still comes first. Half a day checking the data shaped every decision after it. The 683 records behind Amen, Brother designed the graph before I did.

The first evening is the easy bit. A working product took one evening. Making it survive contact with the internet took longer than building it. If you ship things, budget for that. Nobody shows you the week after.

Measure before you guess. I spent days guessing at the limit. The moment there was a counter, the answer took one look. In product terms: instrument it, then decide.

Saying no is a feature. The v2, the race track and number five all died, and the product is better for every one of them. Building things got cheap. Deciding what not to build didn't.

Your best prompt might be a symptom. "Very janky." "Urls like this are broken." "The limit still seems to be hit with small usage." I described what I saw and what I expected. That was always enough.

Go and pick a record. Follow it backwards to the break, or forwards to whoever borrowed it last. If it's 2am and you're listening to a Swedish library record, it's working.

A shout-out. Thanks to Umang in the WeTravel design team, whose feedback and ideas on the graph UI were genuinely valuable. I put them into practice, and the graph is better for it. The best crits still come from people.

Open Sample Explorer ↗

Built with Claude Code. Sample data from Genius, previews from Deezer. Read the companion piece, The Listening Room.