Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6897PWcUEwHiqZMcVnNF6
61 lines
2.8 KiB
Markdown
61 lines
2.8 KiB
Markdown
# gandora storage contract
|
|
|
|
The page keeps everything through one object, `db`, in `index.html`. Today it is IndexedDB in the browser; a backend
|
|
replaces it by implementing the same four calls and pointing `db` at the new implementation. Nothing else in the page
|
|
touches storage (`brain.js` reads through `db.all()` too).
|
|
|
|
```
|
|
db.put(row, store = "tracks") // create or replace, keyed by row.id
|
|
db.all(store = "tracks") // every row, newest first (tracks by `made`, stations by `last`)
|
|
db.del(id, store = "tracks")
|
|
db.open() // internal; a backend adapter can drop it
|
|
```
|
|
|
|
Two stores.
|
|
|
|
## tracks
|
|
|
|
```
|
|
{
|
|
id: "1760998800000-211979153", // spec.made + "-" + hash32(title); stable, made by the page
|
|
made: 1760998800000, // ms epoch, when the DJ wrote it
|
|
kept: false, // pinned by KEEP (the only pin)
|
|
plays: 3, // play starts + loop passes
|
|
blob: Blob, // the audio (FLAC from Venice, mp3 from Runware); a backend may return { url } instead
|
|
spec: { // the song on paper: enough to re-render it anywhere
|
|
title, tags, lyrics, bpm, key, energy, line, // what the DJ wrote (tags = the music prompt, <= 500 chars)
|
|
seed, station, stationId, // where it came from
|
|
reaction: "🔥 ❤️ \"less snare\" [skipped]", // the listener's reactions, in order; words quoted, skips bracketed
|
|
guess: 0.71, // the taste net's prediction when it was cooked (optional)
|
|
plays, id, made // mirrored for convenience
|
|
}
|
|
}
|
|
```
|
|
|
|
## stations
|
|
|
|
```
|
|
{
|
|
id: "s-1760998800000-211979153", // "s-" + ms + hash32(seed)
|
|
seed: "rainy trains",
|
|
name: "Platform Nine", // chosen by the DJ on the first track, then stable
|
|
taste: { like: [...], avoid: [...], notes: [...] }, // last 8 of each, in words, fed to the DJ
|
|
history: [ { id, title, tags, bpm, key, energy, reaction, made } ], // last 8 tracks, no blobs
|
|
made: 1760998800000,
|
|
last: 1761002400000, // touched on every play and reaction
|
|
played: 23
|
|
}
|
|
```
|
|
|
|
## what a backend adds
|
|
|
|
- identity: rows are per listener; `/user/station/track` links need the listener in the key
|
|
- `blob` as a URL the `<audio>` element and the Web Audio analyser can load (same-origin or CORS-enabled)
|
|
- sync: the page calls `db.all()` freely (library, taste, play counts), so cache on the client and keep `all()` cheap
|
|
|
|
## what stays on the page
|
|
|
|
The taste net (`brain.js`) trains in the browser from `db.all()` rows; it needs only `spec` + `kept` + `plays`.
|
|
Share links (`?t=`) carry a spec, not an id, so they work with no backend at all; with one, a link can carry the id
|
|
and the backend's audio instead of re-rendering.
|