gandora: STORAGE.md, the contract between the page and whatever stores its tracks
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6897PWcUEwHiqZMcVnNF6
This commit is contained in:
1 parent
a78d6db949
commit
ce2eead558
1 file changed
+60
@@ -0,0 +1,60 @@
|
||||
# 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.
|
||||
Reference in new issue
Block a user