Files
Epic-The-Musical-Timeline/docs/plans/001-timeline-mockups.md
T

127 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
status: in-progress
---
# EPIC Timeline — Repo bootstrap + 7 mockups Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** A Bun app, shipped as a Docker image, that serves an index page and 7 design-direction mockups. All of them share one sample dataset and carry fan-project credits. The repo is prepared for agents following the playbook.
**Architecture:** `Bun.serve` with HTML-import routes. `bun build --target=bun` bundles the server and every page into `dist/`, and the Docker image runs `bun dist/server.js`. Each mockup is a self-contained folder under `src/mockups/NN-slug/` that imports the shared data, credits and audio modules.
**Tech Stack:** Bun 1.3.x (runtime, bundler, test runner), TypeScript, three.js (mockups 06/07 only), `@fontsource/*` for self-hosted fonts, Docker `oven/bun:1.3`.
**Spec:** `docs/specs/001-timeline-mockups.md`
## Global Constraints
- Every page (the index and all 7 mockups) shows the fan notice and the credit to **Jorge Rivera-Herrans**, both from `src/shared/credits.ts`.
- Mockups, not full pages. Target a desktop viewport of 1440×900. No routing, persistence, or mobile polish.
- Self-contained at runtime: no CDN scripts or fonts. Video slots are YouTube link cards. Songs may have an optional direct MP3.
- three.js only in `src/mockups/06-constellation/` and `src/mockups/07-sea-voyage/`.
- No framework (Vite, React, Next…). Use plain TypeScript and the DOM plus Bun's bundler.
- Sample data: Troy Saga (5 songs), Cyclops Saga (4 songs), and 1–2 later callback songs. Lyrics are placeholder lines. Facts that haven't been checked are marked `// TODO(verify)`.
- A mockup agent edits **only its own folder** `src/mockups/NN-slug/`. Shared files are fixed after Task 1.
- Commits use Conventional Commits and end with `Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>`.
## Review Focus
1. **Song with no `audio`** (every sample song): the audio slot shows an empty state ("No audio yet"), never a broken `<audio>`. *Task 1, test `createAudioSlot empty state`.*
2. **Mockup URL without a trailing slash** (`/mockups/01-editorial`) should still open the mockup. *Task 1, smoke test.*
3. **A motif that appears in only one song**: selecting it highlights that song and draws no thread to nowhere. *Task 1 data test: `songsWithMotif` returns ≥1 for every motif. Each mockup's own review checks it visually.*
4. **WebGL unavailable** in the 3D mockups: show a readable fallback message instead of a blank canvas. *Tasks 5a and 5b, per their review checklist.*
5. **The container is started with a custom `PORT`**: the server respects `process.env.PORT` (default 3000). *Task 6, docker run check.*
---
### Task 1: Foundation — Bun app, shared modules, sample data, mockup stubs
**Files:**
- Create: `package.json`, `tsconfig.json`, `bun.lock` (via install), `.gitignore`, `.dockerignore`, `Dockerfile`
- Create: `src/server.ts`, `src/index.html`, `src/index.ts`, `src/index.css`
- Create: `src/data/types.ts`, `src/data/sample.ts`, `src/data/query.ts`
- Create: `src/shared/credits.ts`, `src/shared/credits.css`, `src/shared/audio-slot.ts`, `src/shared/video-cards.ts`
- Create: `src/mockups/registry.ts` and 7 stubs `src/mockups/{01-editorial,02-vertical-story,03-transit-map,04-greek-frieze,05-score,06-constellation,07-sea-voyage}/{index.html,main.ts,style.css}`
- Test: `tests/data.test.ts`, `tests/shared.test.ts`, `tests/server.test.ts`
**Interfaces (Produces; mockup agents rely on these exactly):**
```ts
// src/data/types.ts
export type MotifKind = "motif" | "character";
export interface Motif { id: string; name: string; kind: MotifKind; color: string; description: string }
export interface Saga { id: string; name: string; order: number }
export interface OdysseyRef { book: number; lines: string; url: string }
export interface StoryTime { yearsAfterTroy: number; label: string }
export interface Part { name: string; singers: string[]; lines: string[]; motifIds: string[]; startSec?: number }
export interface Video { title: string; url: string }
export interface Song {
id: string; title: string; sagaId: string; track: number;
storyTime: StoryTime; odyssey: OdysseyRef | null; // null = no Homer counterpart
parts: Part[]; facts: string[]; videos: Video[]; audio?: string;
}
// src/data/sample.ts
export const sagas: Saga[]; export const motifs: Motif[]; export const songs: Song[]; // songs sorted by track
// src/data/query.ts
export function songsWithMotif(motifId: string): Song[];
export function motifsInSong(song: Song): Motif[]; // unique, in first-appearance order
export function gapYears(a: Song, b: Song): number; // b.storyTime.yearsAfterTroy - a.storyTime.yearsAfterTroy
export function sagaOf(song: Song): Saga;
// src/shared/credits.ts
export const FAN_NOTICE: string; // must contain "unofficial", "not affiliated", "Jorge Rivera-Herrans"
export function mountCredits(target?: Element): HTMLElement; // fills [data-fan-credits] or appends a <footer> to body
// src/shared/audio-slot.ts
export function createAudioSlot(song: Song): HTMLElement; // .audio-slot; empty state .audio-slot--empty; parts with startSec get seek buttons
// src/shared/video-cards.ts
export function createVideoCards(song: Song): HTMLElement; // .video-cards > a.video-card[target=_blank][rel=noopener]
// src/mockups/registry.ts
export interface MockupInfo { slug: string; number: number; name: string; pitch: string; tryThis: string }
export const mockups: MockupInfo[]; // 7 entries, slugs = folder names above
```
Shared modules use plain class names and minimal neutral CSS (`credits.css`), so each mockup restyles them.
- [ ] **Step 1: Scaffold.** `bun init -y`, then delete the generated CLAUDE.md, README and index.ts. `bun add three @fontsource/cormorant-garamond @fontsource/inter`, `bun add -d @types/three @types/bun typescript @happy-dom/global-registrator`. Scripts: `"dev": "bun --hot src/server.ts"`, `"start": "bun dist/server.js"`, `"build": "bun build src/server.ts --target=bun --production --outdir dist"`, `"test": "bun test"`, `"typecheck": "tsc --noEmit"`. `.gitignore`: `node_modules/ dist/ .env* CLAUDE.local.md .worktrees/ screenshots/`.
- [ ] **Step 2: Write the failing tests.**
- `tests/data.test.ts`: every `song.sagaId` is in `sagas`; every `part.motifIds` entry is in `motifs`; every motif has `songsWithMotif(id).length >= 1`; the songs include 5 with `sagaId==="troy"`, 4 with `"cyclops"`, and ≥1 other; every song has ≥1 part, ≥1 fact, ≥1 video; `gapYears(songs[0], songs[1]) >= 0`; ids are unique.
- `tests/shared.test.ts` (register `GlobalRegistrator` from `@happy-dom/global-registrator` in the test file): `FAN_NOTICE` contains the three required phrases; `mountCredits()` puts the notice into `document.body`; **`createAudioSlot empty state`**: a song without `audio` → the element has the class `audio-slot--empty` and no `audio` element; a song with `audio` and one part with `startSec: 30` → one `<audio src>` and one `button[data-start="30"]`; `createVideoCards` → one `a.video-card` per video with `rel` containing `noopener`.
- `tests/server.test.ts`: start the server via `startServer(0)` (export `startServer(port: number): Server` from `src/server.ts`; the module calls it with `Number(process.env.PORT ?? 3000)` only when `import.meta.main`). Assert `GET /` → 200, and the HTML contains every registry slug link; for each slug, `GET /mockups/<slug>/` → 200 with `data-fan-credits`, and `GET /mockups/<slug>` → 200 or a redirect to the slash form.
- [ ] **Step 3: Run `bun test`.** Expected: FAIL (modules missing).
- [ ] **Step 4: Implement.** Write the types, query functions, credits, audio slot and video cards. `server.ts` uses `routes` with HTML imports for `/` and each `/mockups/<slug>/`, and a redirect for the no-slash form. Index page: a title, the 7 cards from `registry` (number, name, pitch, "try this"), and the credits. Each stub: `<footer data-fan-credits>`, `main.ts` calls `mountCredits()` and renders the mockup's name and "stub". Write the sample data (see the spec's data section). Odyssey URLs point at a public-domain translation (e.g. Butler on Project Gutenberg / Wikisource). Story times and Odyssey refs that haven't been checked carry `// TODO(verify)`. Songs: Troy — The Horse and the Infant, Just a Man, Full Speed Ahead, Open Arms, Warrior of the Mind. Cyclops — Polyphemus, Survive, Remember Them, My Goodbye. Callbacks — at least one later song that reuses a Troy or Cyclops motif. Motifs: at least 4 musical motifs and 3 character themes, each with a distinct colour.
- [ ] **Step 5: Run `bun test && bun run typecheck`.** Expected: all pass, 0 type errors.
- [ ] **Step 6: Verify the build.** `bun run build && PORT=3999 bun dist/server.js &`, then `curl -s -o /dev/null -w '%{http_code}' localhost:3999/mockups/01-editorial/` → `200`. Kill the server.
- [ ] **Step 7: Commit.** `git add -A && git commit -m "feat: bun app foundation, sample data, shared credits/audio, mockup stubs"`
### Task 2: Agent-ready repo docs (playbook)
**Files:** Create `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `README.md`, `PRODUCT.md`, `DESIGN.md`, `docs/{ARCHITECTURE,DECISIONS,TODO,PERMISSIONS}.md`, `docs/specs/README.md`, `docs/plans/README.md`, `.claude/settings.json`, `.codex/config.toml`, `.codex/rules/project.rules`, `.gemini/settings.json`.
- [ ] **Step 1:** Follow `/home/user/preparing-repos-for-agents/skill/preparing-repos-for-agents/SKILL.md` §2–§8. Re-run each candidate command in a fresh shell (`bun install --frozen-lockfile`, `bun test`, `bun run typecheck`, `bun run build`, `bun run dev`, `docker build -t epic-timeline .`) and record each one in the Verified table.
- [ ] **Step 2: Deviations to encode.** Mockups live in `src/mockups/`, are **committed and served** (user decision, overriding the template's gitignored `design/mockups/`). Mockup agents edit only their own folder. The spec uses `NNN-slug` naming. `DESIGN.md` says the visual direction is open until a mockup is chosen. `PRODUCT.md` covers the fan project, the credit, the audience, lyrics + MP3 + YouTube. There is no CI yet, so delete the CI line.
- [ ] **Step 3: Deletion pass (§8).** `wc -l AGENTS.md` < 150.
- [ ] **Step 4: Commit.** `git commit -m "chore: bootstrap agent-ready docs and rules"`
### Tasks 3–5: Mockups in batches (subagents in parallel, one per mockup)
Batch A (Task 3): 01 Editorial, 02 Vertical story, 03 Transit map. Batch B (Task 4): 04 Greek frieze, 05 Score/waveform. Batch C (Task 5): 06 Constellation, 07 Sea voyage.
**Per-mockup brief** (each agent gets this plus its row from the spec's directions table):
- Files: only `src/mockups/<slug>/` (any number of files inside it).
- Consumes: Task 1 interfaces (`songs`, `sagas`, `motifs`, `songsWithMotif`, `motifsInSong`, `gapYears`, `sagaOf`, `mountCredits`, `createAudioSlot`, `createVideoCards`).
- Must show (from the spec): the direction's "Must show" column; a song detail state (Odyssey ref with link, story time, parts with singers and placeholder lines, motif chips per part, a fact, video cards, audio slot); motif selection highlighting every song and part where the motif appears; visible credits.
- Gap spacing between songs is proportional to `gapYears` (or visibly labelled with it) in mockups 01, 02, 03 and 07.
- Use the `frontend-design` skill. Commit fully to the direction's aesthetic, with bundled fonts only.
- 06/07: three.js. If WebGL is unavailable, show a styled fallback message (Review Focus #4).
- Verify: `bun test && bun run typecheck && bun run build` pass. Start `bun dist/server.js`, take headless screenshots (`chromium --headless --screenshot` or Playwright, 1440×900) of the default state and the song-open state into the gitignored `screenshots/`, and look at them before reporting.
- Don't commit. The controller reviews and commits each batch.
- [ ] **Task 3:** dispatch batch A in parallel → review each against the brief (spec compliance + visual check of the screenshots) → fix → `git commit -m "feat(mockups): 01 editorial, 02 vertical story, 03 transit map"`
- [ ] **Task 4:** batch B, same loop → `git commit -m "feat(mockups): 04 greek frieze, 05 score"`
- [ ] **Task 5:** batch C, same loop → `git commit -m "feat(mockups): 06 constellation, 07 sea voyage (three.js)"`
### Task 6: Docker verification, final review, push
- [ ] **Step 1:** `docker build -t epic-timeline .` → succeeds.
- [ ] **Step 2:** `docker run -d --rm -e PORT=8080 -p 8080:8080 --name epic-tl epic-timeline`. `curl` `/` and all 7 `/mockups/<slug>/` → 200. `docker stop epic-tl`.
- [ ] **Step 3:** Run a whole-branch review (superpowers:requesting-code-review). Fix findings. Update `docs/TODO.md` with the `TODO(verify)` data follow-ups.
- [ ] **Step 4:** `git rm docs/plans/001-timeline-mockups.md`, commit, set the spec's `status: implemented`, then `git push -u origin main`.