docs: the voyage app in DESIGN, PRODUCT, AGENTS, ARCHITECTURE, DECISIONS, TODO

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Opus 5.5 committed 2026-10-30 15:12:53 +01:00
1 parent d355b3a41f
commit a196c09b82
6 files changed
+117 -45

No files matched your search

+25 -9
View File
@@ -3,10 +3,12 @@
How `epic-the-musical-timeline` is built today. Update in the same commit as any structural change.
## Overview
A Bun server (`Bun.serve`) with HTML-import routes serves an index page and seven mockup pages. Bun's bundler
turns each HTML page and its imported TS/CSS into hashed assets. `bun run build` bundles the server and every
page into `dist/`; the Docker image runs that bundle. All pages read one typed sample dataset and mount one
shared credits snippet.
A Bun server (`Bun.serve`) with HTML-import routes serves the voyage app at `/`, the mockup gallery at
`/mockups/` and the design-round mockups at `/mockups/NN-slug/`, plus `/content/*` from an optional mounted
content folder. Bun's bundler turns each HTML page and its imported TS/CSS into hashed assets. `bun run
build` bundles the server and every page into `dist/`; the Docker image runs that bundle. The app reads the
album data (`src/data/album/`); the mockups read the frozen sample dataset. Every page mounts one shared
credits snippet.
## Map
| Path | Responsibility |
@@ -26,18 +28,30 @@ shared credits snippet.
| `src/content/library.ts` | `scanLibrary(dir)`: sagas (story order) and songs from `Songs and Lyrics/EPIC - <Saga> (<year>)/`; `.lrc` preferred over `.txt`. |
| `src/content/homer.ts` | `splitBooks(text)`: Gutenberg Butler text → books (heading, summary, paragraphs). |
| `src/shared/lrc.ts` | `parseLrc(text)`: timed lyric lines, sorted. |
| `tests/` | `data.test.ts` (dataset invariants), `shared.test.ts` (DOM modules under happy-dom), `server.test.ts` (every route 200s, index links every registry slug, each mockup has the credits hook), `content.test.ts` (LRC, library scan, Homer split, `/content/*` routes on a temp fixture). |
| `src/app/index.html`, `main.ts`, `style.css` | The app page: stage (canvas, frieze layer, caption, timeline) and panel. `main.ts` wires store → controller → world/curtain, panel, timeline, play/prev/next, keyboard (←/→, Esc), the no-WebGL frieze stage. `style.css` holds the design tokens (see `DESIGN.md`). |
| `src/app/state.ts` | `AppState` (`songId`, `tab`, `motif`, `timeline`, `ribbons`, `read`), `parseState`/`stateToSearch` (URL), `createStore` (push on song change, replace otherwise, `popstate` restores). |
| `src/app/voyage.ts` | `transitionFor(from, to)`: same saga → sail; adjacent saga → that saga's curtain; further → fade; plus the target's landing. |
| `src/app/controller.ts` | Runs steps one at a time, last request wins, a cancelled step's curtain is lifted by the next. Order under a curtain: cover → sail → landing → horizon on the frieze ground line → lift. Play voyage (3.4s per song, silent), Back to sea. |
| `src/app/world/` | `createWorld(canvas)` (null without WebGL 2): `route.ts` (9 places, 40 stops, pure), stylised islands, ship, ribbons ("threads on the sea"), `landings/` cave, arena, hall. |
| `src/app/frieze/` | `friezeSvg(sagaId)` for the 9 sagas from a figure kit; `createCurtain(layer)`: drop, lift (part along the ground line, or zoom through a doorway), fade, `groundY()`. |
| `src/app/panel/` | `mountPanel`: About (facts, Odyssey reading view), Listen & lyrics (real player, line-synced lyrics, Watch), Paths (strip when the timeline is hidden, motif rows, nine-place chart), Fan works. |
| `src/app/timeline/` | `layoutTimeline` (pure: current saga expanded, others compact) and `mountTimeline`: stops, you-are-here, threads, hide/show, "Threads on the sea". |
| `src/app/content.ts` | Client for `/content/*`: library match by normalised title, lyrics (`.lrc` timed / `.txt`), Homer books, passage start by keyword. |
| `src/data/album/` | `sagas`, `songs` (40, album order, `landing`), `motifs` (character themes derived from singers, except Odysseus), `fanWorks` (empty in v1), query helpers. |
| `tests/` | `data.test.ts` (dataset invariants), `shared.test.ts` (DOM modules under happy-dom), `server.test.ts` (every route 200s, index links every registry slug, each mockup has the credits hook), `content.test.ts` (LRC, library scan, Homer split, `/content/*` routes on a temp fixture), `album.test.ts`, `state.test.ts`, `voyage.test.ts` (transitions, controller with fakes), `route.test.ts`, `frieze.test.ts`, `timeline.test.ts`, `panel.test.ts`, `app-content.test.ts`. |
| `Dockerfile` | Two-stage `oven/bun:1.3`: install + build, then copy `dist/` and run from `WORKDIR /app/dist`. |
## Data flow
1. Request hits `Bun.serve`; an HTML-import route returns the bundled page (dev: bundled on demand with HMR
via `bun --hot`; production: prebuilt assets from `dist/`).
2. The page's `main.ts` imports `src/data/*` and `src/shared/*` (bundled in; no runtime API).
3. `main.ts` renders the direction and calls `mountCredits()` into `<footer data-fan-credits>`.
2. The page's `main.ts` imports `src/data/*` and `src/shared/*` (bundled in) and calls `mountCredits()`.
3. App: the URL becomes `AppState`; every change goes through the store. The controller moves the stage
(curtain + world), and the panel and timeline re-render from the store. The panel fetches
`/content/library.json`, the song's audio and lyrics, and Homer books on demand; all of it is optional.
## External dependencies
Optional at runtime: the content folder named by `CONTENT_DIR` (real audio, lyrics, cover art, Butler's
Odyssey and Iliad). It is mounted read-only, never committed or baked into the image (spec 003). Without it
Optional at runtime: the content folder named by `CONTENT_DIR` (real audio, lyrics, cover art, the Odyssey and
Iliad: Butcher & Lang / Lang, Leaf & Myers preferred, Butler as fallback). It is mounted read-only, never committed or baked into the image (spec 003). Without it
the site works and `/content/*` answers 404. Otherwise none. Fonts (`@fontsource/*`) and three.js are bundled from npm. Outbound links only: YouTube
(video cards) and Project Gutenberg (Odyssey references) — the site works without them.
@@ -47,6 +61,8 @@ the site works and `/content/*` answers 404. Otherwise none. Fonts (`@fontsource
(`tests/server.test.ts`).
- Every motif id used by a part exists; every motif appears in at least one song (`tests/data.test.ts`).
- No two mockups ship byte-identical `style.css` (the build fails otherwise).
- The album has 40 songs whose titles and order match the content folder (`tests/album.test.ts`).
- No lyric text in the repo; lyrics are fetched from `/content/` at runtime only.
## Known rough edges
- The production bundle only runs with `dist/` as the cwd (`bun run start`, Docker `WORKDIR /app/dist`);
+15
View File
@@ -90,3 +90,18 @@ Roman names (Ulysses, Minerva, Jove); EPIC uses the Greek ones. Both are public
**Decision:** the album data has no Odysseus character theme. **Why:** character themes are derived from
singers, and Odysseus sings in 35 of 40 songs, so his thread marked almost every stop and crowded the
timeline ("+28 more"). His story is carried by the musical motifs (Warrior of the Mind, Just a Man, …).
## 2026-10-30 — The voyage app (spec 002): structure and v1 choices
- **Album data is separate from the mockups' data** (`src/data/album/` vs `src/data/types.ts`/`sample.ts`),
so the 17 mockups keep working unchanged.
- **App at `/`, gallery at `/mockups/`.** The mockups stay served as the design record.
- **Odyssey references are a book plus keywords**, not line numbers: the public-domain prose translations
have no line numbers. Links point at Butcher & Lang (Gutenberg #1728).
- **Landings:** cave = Polyphemus, Survive; arena = God Games; hall = all five Ithaca songs.
- **Transitions:** same saga sails, the adjacent saga drops its frieze, further jumps fade. Last request
wins; a curtain left down by a cancelled step is lifted by the next one.
- **Play voyage is silent** (3.4s per song): starting audio without a click is close to autoplay.
- **Fan works are empty in v1** with an empty state; no invented works are shown.
- **Without WebGL** the frieze is the stage (dropped, never lifted), so the album stays browsable.
- **Back to sea** leaves a landing but keeps the song.
+10
View File
@@ -88,3 +88,13 @@ Small, specific, independently actionable. Remove only when actually done.
- Homer books: with Butler's fallback text, the last book (Odyssey XXIV) also swallows the translator's
notes after it. The preferred Butcher & Lang text does not have this problem.
- App v1 (spec 002) follow-ups:
- 97 album values are `// TODO(verify)` (story years, some singers, Odyssey books/keywords, facts).
- Parts and motifs are not time-aligned to the recordings; needs a mapping file in the content folder
(part → first lyric line). Then the lyrics can show part headings and motif colours per line.
- Passage starts in the Odyssey reading view are keyword guesses.
- Fan works: decide sourcing, credit and permission process; the data type exists, the list is empty.
- Mobile (<900px) is usable but not polished; no favicon yet (one 404 per page load).
- Some frieze figures are small at five songs per saga; Charybdis reads mainly as a ring from the ship.
- Timeline motif inks on parchment can look alike (Warrior / Thunder Bringer / Zeus).