tui-tube is the manual backend / content pipeline for VibeFM. It owns the music catalog and all editorial content. The VibeFM web app consumes this data — it does not produce it.
Think of it as two separate systems:
| System | Role |
|---|---|
| tui-tube | Playlist builder, sync tool, content authoring. Runs locally. Agent-writable. Never touches Convex directly. |
| VibeFM web | Consumes tui-tube's output via a seed script. Serves users. Writes user interactions (likes, dead links) back to Convex. |
When new playlists are added or content is updated in tui-tube, the operator runs the seed script in the VibeFM repo to push changes to Convex. That's the only bridge between the two systems.
| File | Purpose |
|---|---|
catalog.db |
Music catalog — tracks, playlists, YouTube IDs |
vibefm-editorial.json |
Editorial content — liner notes, descriptions, facts, curators |
These two things are intentionally separate. catalog.db is managed by tui-tube's sync tools. vibefm-editorial.json is managed by agents and humans writing content.
tui-tube/catalog.db (track catalog — synced from YouTube)
tui-tube/vibefm-editorial.json (editorial content — written by agents)
↓
seed script (TODO — lives in vibefm repo /scripts)
↓
Convex playlists table (editorial + metadata)
Convex tracks table (real tracks with youtube_id — TODO, not seeded yet)
↓
App queries Convex at runtime
User interactions write to Convex (likes, dead link reports, user playlists)
Current state: lib/vibefm.ts in the vibefm repo still has a fake seeded TRACK_POOL. Real tracks from catalog.db are not in Convex yet. The playlists table exists in Convex schema but is not seeded. Seed script does not exist yet.
Full schema in nextjs-chat-template/convex/schema.ts.
| Table | Status | Notes |
|---|---|---|
playlists |
✅ Schema exists | sortOrder, name, description, linerNotes, facts, tags, tracks (count), curatorName, curatorTitle, coverArt*. Not seeded yet. |
tracks |
✅ Schema exists | youtubeId, title, artist, thumbnail (optional), playlistId. Not seeded yet. |
userLikes |
✅ Schema exists | ownerTokenIdentifier, trackId, likedAt. Note: camelCase table name. |
deadLinks |
✅ Schema exists | youtubeId, trackId, reportedBy (optional), reportedAt. Note: camelCase table name. |
users |
✅ Exists | Auth + profile |
vibefm-editorial.json— write curator names/titles, liner notes, and facts for all 49 playlists- Seed script in vibefm repo
/scripts— readstui-tube/catalog.db+tui-tube/vibefm-editorial.json, pushes to Convexplaylists+tracks - Remove fake TRACK_POOL from
lib/vibefm.tsonce real tracks are seeded - Cover art — generate
.webpper playlist, populatecoverArtFile,coverArtPrompt,coverArtDescription
Each key is a playlist name matching demo_playlists.name in catalog.db. Fields:
| Field | Description |
|---|---|
description |
2–3 sentence card copy. The pitch. |
linerNotes |
First-person narrative essay. The story. See content spec. |
facts |
Array of exactly 3 facts. Educational, not atmospheric. |
curatorName |
The synthetic persona who "made" this playlist. |
curatorTitle |
Their role/title. Creative, playlist-specific. |
Full content spec and writing guidelines:
→ nextjs-chat-template/docs/product/vibefm-playlist-content-spec.md
The most important distinction: description is the pitch, linerNotes is a piece of writing that stands on its own. Read the spec before writing liner notes — the format is specific.
Open vibefm-editorial.json and fill in the fields for any playlist. Empty strings mean not yet written. The JSON keys must match the playlist names exactly as they appear in catalog.db.
When content is ready to ship, run the seed script in the vibefm repo to push to Convex.
49 playlists across 8 mood categories. Full tracklists in catalog.db under demo_playlists and demo_playlist_tracks.
-- Get all tracks for a playlist
SELECT t.song_title, t.artist, t.youtube_id
FROM demo_playlist_tracks dpt
JOIN tracks t ON dpt.youtube_id = t.youtube_id
WHERE dpt.playlist_name = 'Hood Certified'
ORDER BY t.artist;Query the tracklist before writing liner notes — not to reference songs by name, but to understand the emotional composition of the playlist. The themes and tensions in the track selection inform what the liner note is about.
- Query the tracklist for the playlist from
catalog.db - Read the content spec:
nextjs-chat-template/docs/product/vibefm-playlist-content-spec.md - Identify the emotional themes and tensions in the selection — without naming songs or artists
- Write a first-person narrative set in the world the playlist name represents — not about music, about the human experience
- End with a question that distills the piece
- Add to
vibefm-editorial.json