docs: add approved spec and plan for timeline mockups

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas Winther
2026-10-23 18:17:20 +02:00
co-authored by Claude Opus 5.5
commit d52ac63afe
2 changed files with 249 additions and 0 deletions
+126
View File
@@ -0,0 +1,126 @@
---
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`.
+123
View File
@@ -0,0 +1,123 @@
---
status: approved
---
# EPIC: The Musical Timeline — Repo bootstrap and 7 design-direction mockups
Status: approved by user 2026-10-23 · Date: 2026-10-23
## Intent
An interactive, unofficial fan website for *EPIC: The Musical* by Jorge Rivera-Herrans. The core is one long
timeline of the songs. Recurring musical motifs and character themes are drawn as threads between the songs
where they appear, and the gaps between songs show how much in-story time passes. Opening a song shows its
place in Homer's *Odyssey*, its in-story time, its lyrics split into the song's parts, the motifs in each
part, fun facts, and related videos. The finished site should feel smooth and polished, and may use 3D
(three.js).
This document covers only the **first step**: preparing the repository, and building **7 visual mockups**
that each show a different design direction. The user will pick a direction, or combine several, and the real
app will get its own spec after that.
## Goals
1. **Repo prepared for agentic development.** Follow the playbook at
`/home/user/preparing-repos-for-agents/skill/preparing-repos-for-agents/SKILL.md` all the way through
(AGENTS.md, CLAUDE.md/GEMINI.md pointers, `docs/` tree, permissions, PRODUCT.md, DESIGN.md, .gitignore).
Use `git init` with the default branch `main`. Set the remote `origin` to
`https://gitea.lucaswinther.info/lucasw89/Epic-The-Musical-Timeline.git`.
2. **Bun app that builds to a Docker image.** A minimal skeleton that serves an index page linking to all 7
mockups. Include `bun run dev`, `bun run build`, and a `Dockerfile` that produces a runnable image.
3. **7 mockups**, one per direction (see the table below). All 7 read from one shared sample dataset.
4. **Fan-project notice and credits** on the index page and on every mockup. The notice says the site is
unofficial and not affiliated with the creator, and thanks Jorge Rivera-Herrans as the creator of EPIC.
## Non-goals
- These are **mockups, not full pages**. Each one shows a visual direction and how it feels to interact with.
None of them is a finished product.
- No complete song catalogue, real backend, CMS, search, or accounts.
- No polish for mobile or accessibility beyond "doesn't fall apart". Mockups target a desktop viewport of
about 1440×900.
- No decision yet on the final design system. DESIGN.md records that the direction is still open.
## Anti-goals (failures even if they technically work)
- Seven variations of one layout with different colours. The directions must differ in kind.
- Generic, templated "AI-looking" UI. Each direction commits fully to its own aesthetic.
- Any page that is missing the fan notice or the credit to Jorge Rivera-Herrans.
- Shipping the content as if it were fact-checked. Any sample field the builder couldn't verify is marked
`// TODO(verify)` in the data.
## The 7 directions
| # | Name | Core idea | Must show |
|---|------|-----------|-----------|
| 1 | Editorial scroll | Clean horizontal line in a parchment-and-serif editorial style. Motifs are thin coloured threads underneath. | Horizontal pan, gap length proportional to in-story time, one song opened |
| 2 | Vertical story | The voyage scrolls downward. Songs reveal one at a time beside a detail panel that stays in place (sticky). | Vertical line, sticky detail updating on scroll |
| 3 | Transit map | Each motif is a coloured metro line and each song is a station. Songs where several motifs meet are interchanges. | Lines running between stations, interchange stations, legend |
| 4 | Greek frieze | The timeline is an unrolled black-figure vase frieze, panned sideways. | Frieze styling, song panels, one song opened |
| 5 | Score / waveform | The line is a music staff or audio waveform. Song parts are segments, and motifs are recurring marks. | Song parts visible as segments, motif recurrence marks |
| 6 | Constellation (3D) | Songs are stars along a path through space, joined by motif "constellation" lines. three.js. | Camera fly-through, a hovered star, an opened song |
| 7 | Sea voyage (3D) | A ship crosses a 3D sea, with islands for the sagas, and the camera follows the route. three.js. | Ship moving along the route, saga islands, an opened song |
Every mockup must also show:
- **A song detail state**: Odyssey reference, in-story time, lyrics divided into parts with the singer of
each part, the motifs in each part, a fun fact, related-video link cards (YouTube), and an audio slot.
The audio slot plays a direct MP3 when the song has one (user requirement: the app supports direct MP3s
as well as YouTube). The sample data ships with no MP3s, so the mockups show the slot's empty state.
- **Motif linking**: selecting a motif highlights every song (and, where possible, every part) where it
appears.
"Interactive enough to feel it" means hover and click to open a song, plus scrolling, panning or a camera
move. Routing, deep links, persistence and edge cases are out of scope.
## Sample data (shared by all mockups)
One typed module is the single source for all 7 mockups. Its shape should be close to what the real app will
use:
- **Saga**: id, name, order.
- **Song**: id, title, sagaId, track number, `storyTime` (a number of years since the fall of Troy, plus a
human label), `odyssey` (book, line range, and a URL to a public-domain translation), `parts[]`, `facts[]`,
`videos[]` (title, URL), and optional `audio` (URL to a direct MP3 file).
- **Part**: name (for example "Verse 1"), singer(s), lyric lines, motif ids present in the part, and an
optional start time in seconds (so a part can be played from the song's MP3).
- **Motif / character theme**: id, name, kind (`motif` | `character`), colour, and a short description.
Scope: the **Troy Saga** (5 songs) and the **Cyclops Saga** (4 songs), plus **1–2 later songs** where a motif
from those sagas returns, so the threads have something to connect. Any fact, time or Odyssey reference the
builder can't confirm is marked `// TODO(verify)`.
Lyrics *(builder's call)*: mockups use **placeholder lyric lines** (clearly marked, 2–4 lines per part) in
the real data shape. The mockups compare designs, not content, and recalling lyrics from memory risks
inaccuracy. Real lyrics come with the real app; the copyright question is deferred by the user.
## Constraints
- Runtime and package manager: **Bun** (1.3.x is installed locally). Use Bun's own server and bundler, with
HTML imports. Don't add a separate framework (Vite, Next, etc.) for the mockups.
- **three.js** only in mockups 6 and 7.
- **Self-contained at runtime** *(builder's call)*: three.js and fonts are bundled from npm (e.g.
`@fontsource/*`), not loaded from a CDN. Video slots are link cards to YouTube (title + link, no embedded
player), so a mockup works offline apart from following the link.
- Docker: a multi-stage image based on the official `oven/bun` image. `docker build` and `docker run` must
work, and the running container must serve the index page and all 7 mockups.
- Every page carries the fan notice and credits, from one shared snippet.
- **Build process (user's instruction):** the mockups are built by AI subagents in **batches of 2–3** running
in parallel. The shared foundation (server, index page, sample data, credits snippet) is built before the
first batch so the agents don't conflict.
## Git
- One commit for the repo bootstrap, then one commit per stage (foundation, each mockup batch).
- Push `main` to `origin` (Gitea) when all stages are done (user approved). If the remote already has commits, integrate them (rebase/merge), never force-push.
## Left to the builder
- File layout under `src/`, naming, and how the shared credits snippet is included.
- Visual details within each direction (palette, typefaces, motion), as long as each direction commits to its
concept.
- The specific later songs chosen as motif callbacks, and which motifs are included.
- How the batches are grouped (for example 1–3, 4–5, 6–7).
- Whether to add a smoke test (for example, the server returns 200 for every mockup route).