From 27b1d2fe26d4388892a44d7286cc1b6e0332cf8d Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Fri, 30 Oct 2026 02:46:43 +0100 Subject: [PATCH] feat(content): serve the mounted content folder under /content (spec 003) Audio with range requests, library.json, Homer books; LRC parser. Content stays out of git and the image. Co-Authored-By: Claude Opus 5.5 --- .dockerignore | 4 + .gitignore | 6 + AGENTS.md | 9 +- docs/ARCHITECTURE.md | 12 +- docs/DECISIONS.md | 8 ++ docs/TODO.md | 3 + docs/design/002-direction-round-notes.md | 8 +- docs/plans/003-content-mount.md | 26 ++++ docs/specs/003-content-mount.md | 52 ++++++++ src/content/homer.ts | 62 +++++++++ src/content/library.ts | 71 ++++++++++ src/content/routes.ts | 83 ++++++++++++ src/server.ts | 13 +- src/shared/lrc.ts | 20 +++ tests/content.test.ts | 160 +++++++++++++++++++++++ 15 files changed, 527 insertions(+), 10 deletions(-) create mode 100644 docs/plans/003-content-mount.md create mode 100644 docs/specs/003-content-mount.md create mode 100644 src/content/homer.ts create mode 100644 src/content/library.ts create mode 100644 src/content/routes.ts create mode 100644 src/shared/lrc.ts create mode 100644 tests/content.test.ts diff --git a/.dockerignore b/.dockerignore index 20034af..4208241 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,3 +5,7 @@ dist .worktrees screenshots .env* +epic-the-musical-content +content +**/*.flac +**/*.lrc diff --git a/.gitignore b/.gitignore index c049332..4afc078 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,9 @@ CLAUDE.local.md screenshots/ .superpowers/ .claude/settings.local.json + +# Real content is mounted at runtime, never committed (spec 003) +epic-the-musical-content/ +/content/ +*.flac +*.lrc diff --git a/AGENTS.md b/AGENTS.md index 22092c4..0554e4b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,11 +9,11 @@ Deeper context on demand: `docs/ARCHITECTURE.md`, `docs/DECISIONS.md`, `docs/TOD ## Commands Install: `bun install --frozen-lockfile` -Dev: `bun run dev` (`PORT` env, default 3000) +Dev: `bun run dev` (`PORT` env, default 3000; `CONTENT_DIR=~/epic-the-musical-content` for real audio/lyrics) Test: `bun test` | Single file: `bun test tests/data.test.ts` | Single case: `bun test -t "registry has 16"` Typecheck: `bun run typecheck` Build: `bun run build` | Run the build: `bun run start` -Docker: `docker build -t epic-timeline .` then `docker run --rm -p 3000:3000 epic-timeline` +Docker: `docker build -t epic-timeline .` then `docker run --rm -p 3000:3000 -v ~/epic-the-musical-content:/content:ro -e CONTENT_DIR=/content epic-timeline` Run the production bundle **from inside `dist/`**. Bun resolves bundled assets relative to the cwd: ```sh @@ -31,6 +31,8 @@ bun dist/server.js # wrong: crashes with 'Bundled file "./index-….js" no only when the plan says so. - No two mockups may have byte-identical `style.css` — the build fails with "Multiple files share the same output path". Give each one its own styles from the start. +- Real audio, lyrics and cover art are Jorge Rivera-Herrans' copyrighted work: they live only in the + mounted content folder (`CONTENT_DIR`, served under `/content/`), never in git or the Docker image. - Sample data (`src/data/sample.ts`): lyrics are placeholders, never real lyrics. Any fact, story time or Odyssey reference you can't confirm gets `// TODO(verify)`. - Assets are bundled, never loaded from a CDN: fonts from `@fontsource/*`, three.js from npm. @@ -68,7 +70,8 @@ Mockup rounds — any UI change beyond a trivial tweak, while brainstorming and `src/server.ts` -> `Bun.serve` with HTML-import routes: `/` and `/mockups/NN-slug/` `src/data/` -> `types.ts` (shapes), `sample.ts` (shared sample data), `query.ts` (helpers) -`src/shared/` -> credits, audio slot (direct MP3, empty state), YouTube link cards +`src/shared/` -> credits, audio slot (direct MP3, empty state), YouTube link cards, LRC parser +`src/content/` -> `/content/*` routes over the mounted content folder: library.json, files (ranges), Homer books `src/mockups/NN-slug/` -> one design direction each: `index.html`, `main.ts`, `style.css`, plus optional helper modules (e.g. `scene.ts`, `layout.ts`) `tests/` -> data invariants, shared DOM modules (happy-dom), route smoke tests diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 9772cd0..04c5ead 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -11,7 +11,7 @@ shared credits snippet. ## Map | Path | Responsibility | |---|---| -| `src/server.ts` | `startServer(port)`: routes `/`, `/mockups/NN-slug/` (+ 302 from the no-slash form), 404 otherwise. Runs when `import.meta.main`; `PORT` env, default 3000. | +| `src/server.ts` | `startServer(port, { contentDir? })`: routes `/`, `/mockups/NN-slug/` (+ 302 from the no-slash form), `/content/*` via `src/content/routes.ts`, 404 otherwise. Runs when `import.meta.main`; `PORT` env, default 3000. | | `src/index.html`, `src/index.ts`, `src/index.css` | Index page: static cards linking each mockup, then `mountCredits()`. | | `src/mockups/registry.ts` | `mockups: MockupInfo[]` — slug, number, name, pitch, what to try. | | `src/mockups/NN-slug/` | One design direction: `index.html` (links `../../shared/credits.css`, has `