docs: add approved spec and plan for timeline mockups
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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`.
|
||||
@@ -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).
|
||||
Reference in New Issue
Block a user