13 KiB
status
| 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/andsrc/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
- Song with no
audio(every sample song): the audio slot shows an empty state ("No audio yet"), never a broken<audio>. Task 1, testcreateAudioSlot empty state. - Mockup URL without a trailing slash (
/mockups/01-editorial) should still open the mockup. Task 1, smoke test. - A motif that appears in only one song: selecting it highlights that song and draws no thread to nowhere. Task 1 data test:
songsWithMotifreturns ≥1 for every motif. Each mockup's own review checks it visually. - WebGL unavailable in the 3D mockups: show a readable fallback message instead of a blank canvas. Tasks 5a and 5b, per their review checklist.
- The container is started with a custom
PORT: the server respectsprocess.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.tsand 7 stubssrc/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):
// 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: everysong.sagaIdis insagas; everypart.motifIdsentry is inmotifs; every motif hassongsWithMotif(id).length >= 1; the songs include 5 withsagaId==="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(registerGlobalRegistratorfrom@happy-dom/global-registratorin the test file):FAN_NOTICEcontains the three required phrases;mountCredits()puts the notice intodocument.body;createAudioSlot empty state: a song withoutaudio→ the element has the classaudio-slot--emptyand noaudioelement; a song withaudioand one part withstartSec: 30→ one<audio src>and onebutton[data-start="30"];createVideoCards→ onea.video-cardper video withrelcontainingnoopener.tests/server.test.ts: start the server viastartServer(0)(exportstartServer(port: number): Serverfromsrc/server.ts; the module calls it withNumber(process.env.PORT ?? 3000)only whenimport.meta.main). AssertGET /→ 200, and the HTML contains every registry slug link; for each slug,GET /mockups/<slug>/→ 200 withdata-fan-credits, andGET /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.tsusesrouteswith HTML imports for/and each/mockups/<slug>/, and a redirect for the no-slash form. Index page: a title, the 7 cards fromregistry(number, name, pitch, "try this"), and the credits. Each stub:<footer data-fan-credits>,main.tscallsmountCredits()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 &, thencurl -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 gitignoreddesign/mockups/). Mockup agents edit only their own folder. The spec usesNNN-slugnaming.DESIGN.mdsays the visual direction is open until a mockup is chosen.PRODUCT.mdcovers 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-designskill. 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 buildpass. Startbun dist/server.js, take headless screenshots (chromium --headless --screenshotor Playwright, 1440×900) of the default state and the song-open state into the gitignoredscreenshots/, 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.mdwith theTODO(verify)data follow-ups. - Step 4:
git rm docs/plans/001-timeline-mockups.md, commit, set the spec'sstatus: implemented, thengit push -u origin main.