CrateVibe Playlist
What it does for you
Describe a vibe in plain words and get Spotify search queries with real field filters plus a ranked, energy-arc playlist, built to work after Spotify closed its recommendation endpoints.

Screenshots

In more detail
Crate takes a sentence like "rainy sunday coffee, mellow 90s neo soul" and turns it into Spotify searches plus a ranked playlist that warms up, peaks, and cools down. Streaming apps are good at "more like this" and bad at "this feeling."
Honest limits
The Spotify connector was not connected in this environment. The live path was tested against a fixture and a mocked Spotify API in a headless browser (including a 401), never a real account. The ranking leans on hand-tagged seed data, which is the weakest part. It runs and it's tested, but it's not hammered.
How I built it
This was one of the 20 Cowork projects. I told Claude Cowork to one-shot all of them, and it took #51 off the list, scoped it to a one-day box, and built it in one pass: the core logic first (crate.js), then the tests, then the page on top of that same module. About 0.8 hours.
The interesting constraint was Spotify itself. The case study notes that Spotify closed its recommendations and audio-features endpoints to new apps in late 2024, so the usual "set target energy and tempo" approach was off the table. We went search-only. The parser turns free text into a structured vibe (genres, moods, energy, tempo, era, activity, artists, exclusions), a planner writes up to six Spotify Search queries with real field filters like genre:"neo soul" year:1990-1999, and one scorer ranks either a 197-track offline seed catalog or live results.
Claude Cowork also wrote a Cowork skill for a Spotify MCP connector: search is read-only, and a private playlist only gets created after the user confirms. Seed tracks export as search links because we would not invent track IDs.
What I learned
- Blanking matched text with spaces keeps every later regex index valid, but only if nothing re-normalizes the string. My era parser collapsed whitespace on already-blanked text and shifted every span, so I split it into a public wrapper and an internal pass.
- Longest-phrase-first matching is what stops "neo soul" from also adding "soul." Some phrases need rewriting before genre matching at all: "house party" is a party, not house music.
- Exclusions need prefix matching. "No rap for the gym" has to drop the whole hip hop family and leave "for the gym" for the activity parser.
- An energy arc and "no same artist twice in a row" fight each other. A simple neighbor swap broke the arc in the end-to-end test, so an artist's second track now goes on the other slope, and any later swap is kept only if the arc stays clean.
- Score only what the vibe actually asks for. Unknown energy on a live result gets half credit labeled "unknown" instead of a guess.
What carried forward
Crate follows the house pattern from the June projects: one DOM-free logic module imported by both the page and verify.mjs, so the shipped code is the tested code, plus a zero-dependency serve.mjs. It also shows what the Cowork setup made easy: a cowork-skill/SKILL.md riding next to the app. No later project imports Crate's code.
If I took it further
I would move the seed tags into a small CSV that listeners can correct, and add an optional Claude pass that suggests extra queries for vague vibes, scored by the same local scorer so it can't get around the exclusions.
#051 Built with Claude Cowork September 23, 2026 290 automated checks