Files
Epic-The-Musical-Timeline/docs/ARCHITECTURE.md
T

55 lines
4.3 KiB
Markdown

# Architecture
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.
## Map
| Path | Responsibility |
|---|---|
| `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/mockups/index.html`, `index.ts`, `index.css` | Mockup gallery at `/mockups/`: 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 `<footer data-fan-credits>`), `main.ts`, `style.css`, plus optional helper modules (e.g. `scene.ts`, `layout.ts`). |
| `src/data/types.ts` | `Saga`, `Song`, `Part`, `Motif`, `StoryTime`, `OdysseyRef`, `Video`. |
| `src/data/sample.ts` | `sagas`, `motifs`, `songs`. Placeholder lyrics; unverified fields `// TODO(verify)`. |
| `src/data/query.ts` | `songsWithMotif`, `motifsInSong`, `gapYears`, `sagaOf`. |
| `src/shared/credits.ts` | `FAN_NOTICE`, `mountCredits(target?)`. |
| `src/shared/audio-slot.ts` | `createAudioSlot(song)`: `<audio>` for `song.audio`, else an empty-state slot. |
| `src/shared/video-cards.ts` | `createVideoCards(song)`: YouTube link cards, no embeds. |
| `src/shared/credits.css` | Styles for credits, audio slot, video cards. |
| `src/content/routes.ts` | `contentResponse(req, dir)`: `/content/library.json`, `/content/files/<path>` (traversal-guarded, HTTP ranges), `/content/homer/<odyssey\|iliad>/<book>.json`. All 404 when no folder is mounted. |
| `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). |
| `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>`.
## 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
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.
## Invariants
- Every page renders `FAN_NOTICE` via `mountCredits()`.
- `src/server.ts` routes, `src/mockups/registry.ts` and the cards in `src/mockups/index.html` list the same slugs
(`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).
## Known rough edges
- The production bundle only runs with `dist/` as the cwd (`bun run start`, Docker `WORKDIR /app/dist`);
`bun dist/server.js` from the repo root crashes. See `docs/TODO.md`.
- Routes, registry and index cards are three hand-maintained lists. See `docs/TODO.md`.