feat(app): app page at /, mockup gallery moved to /mockups/
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c11c2ef079
commit
3235df8bea
@@ -27,7 +27,7 @@ bun dist/server.js # wrong: crashes with 'Bundled file "./index-….js" no
|
||||
and the `../../shared/credits.css` link in the page's HTML, and call `mountCredits()` from
|
||||
`src/shared/credits.ts`. Never write your own notice text.
|
||||
- Mockup agents edit only their own `src/mockups/NN-slug/` folder. `src/data/`, `src/shared/`,
|
||||
`src/server.ts`, `src/index.html` and `src/mockups/registry.ts` are the shared contract; change them
|
||||
`src/server.ts`, `src/mockups/index.html` and `src/mockups/registry.ts` are the shared contract; change them
|
||||
only when the plan says so.
|
||||
- No two mockups may have byte-identical `style.css` — the build fails with
|
||||
"Multiple files share the same output path". Give each one its own styles from the start.
|
||||
@@ -38,7 +38,7 @@ bun dist/server.js # wrong: crashes with 'Bundled file "./index-….js" no
|
||||
- Assets are bundled, never loaded from a CDN: fonts from `@fontsource/*`, three.js from npm.
|
||||
YouTube videos are link cards (`createVideoCards`), not embeds.
|
||||
- Adding a mockup route means editing `src/server.ts`, `src/mockups/registry.ts` and the cards in
|
||||
`src/index.html` together; `tests/server.test.ts` checks every registry slug is served and that the index shows each entry's name, pitch and tryThis.
|
||||
`src/mockups/index.html` together; `tests/server.test.ts` checks every registry slug is served and that the index shows each entry's name, pitch and tryThis.
|
||||
|
||||
## Workflow
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ shared credits snippet.
|
||||
| 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/index.html`, `src/index.ts`, `src/index.css` | Index page: static cards linking each mockup, then `mountCredits()`. |
|
||||
| `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`. |
|
||||
@@ -43,7 +43,7 @@ the site works and `/content/*` answers 404. Otherwise none. Fonts (`@fontsource
|
||||
|
||||
## Invariants
|
||||
- Every page renders `FAN_NOTICE` via `mountCredits()`.
|
||||
- `src/server.ts` routes, `src/mockups/registry.ts` and the cards in `src/index.html` list the same slugs
|
||||
- `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).
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>EPIC: The Musical Timeline — an unofficial fan voyage</title>
|
||||
<link rel="stylesheet" href="../shared/credits.css" />
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main id="voyage-app">
|
||||
<section id="stage" aria-label="The voyage"></section>
|
||||
<aside id="panel" aria-label="Song details"></aside>
|
||||
<nav id="timeline" aria-label="Songs and motif threads"></nav>
|
||||
</main>
|
||||
<footer data-fan-credits></footer>
|
||||
<script type="module" src="./main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,3 @@
|
||||
import { mountCredits } from "../shared/credits";
|
||||
|
||||
mountCredits();
|
||||
@@ -0,0 +1,2 @@
|
||||
/* The voyage app (spec 002). Tokens and layout are filled in by the plan's tasks. */
|
||||
body { margin: 0; background: #1d1712; }
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>EPIC: The Musical Timeline — design mockups</title>
|
||||
<link rel="stylesheet" href="./index.css" />
|
||||
<link rel="stylesheet" href="./shared/credits.css" />
|
||||
<link rel="stylesheet" href="../shared/credits.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mountCredits } from "./shared/credits";
|
||||
import { mountCredits } from "../shared/credits";
|
||||
|
||||
// Cards are static in index.html (tests/server.test.ts checks each registry entry's name, pitch and tryThis appear here).
|
||||
mountCredits();
|
||||
+5
-2
@@ -1,6 +1,7 @@
|
||||
import type { Server } from "bun";
|
||||
import { contentResponse } from "./content/routes";
|
||||
import home from "./index.html";
|
||||
import app from "./app/index.html";
|
||||
import gallery from "./mockups/index.html";
|
||||
import m0 from "./mockups/01-editorial/index.html";
|
||||
import m1 from "./mockups/02-vertical-story/index.html";
|
||||
import m2 from "./mockups/03-transit-map/index.html";
|
||||
@@ -29,7 +30,9 @@ export function startServer(port: number, opts: ServerOptions = {}): Server<unde
|
||||
return Bun.serve({
|
||||
port,
|
||||
routes: {
|
||||
"/": home,
|
||||
"/": app,
|
||||
"/mockups/": gallery,
|
||||
"/mockups": Response.redirect("/mockups/", 302),
|
||||
"/mockups/01-editorial/": m0,
|
||||
"/mockups/01-editorial": Response.redirect("/mockups/01-editorial/", 302),
|
||||
"/mockups/02-vertical-story/": m1,
|
||||
|
||||
+18
-4
@@ -10,15 +10,15 @@ afterAll(() => server.stop(true));
|
||||
|
||||
test("registry has 17 mockups", () => expect(mockups).toHaveLength(17));
|
||||
|
||||
test("index links every mockup", async () => {
|
||||
const res = await fetch(base + "/");
|
||||
test("gallery links every mockup", async () => {
|
||||
const res = await fetch(base + "/mockups/");
|
||||
expect(res.status).toBe(200);
|
||||
const html = await res.text();
|
||||
for (const m of mockups) expect(html).toContain(`/mockups/${m.slug}/`);
|
||||
});
|
||||
|
||||
test("index shows every registry name, pitch and tryThis", async () => {
|
||||
const html = (await (await fetch(base + "/")).text()).replaceAll("&", "&");
|
||||
test("gallery shows every registry name, pitch and tryThis", async () => {
|
||||
const html = (await (await fetch(base + "/mockups/")).text()).replaceAll("&", "&");
|
||||
for (const m of mockups) for (const text of [m.name, m.pitch, m.tryThis]) expect(html).toContain(text);
|
||||
});
|
||||
|
||||
@@ -34,3 +34,17 @@ for (const m of mockups) {
|
||||
else expect(res.status).toBe(200);
|
||||
});
|
||||
}
|
||||
|
||||
test("the app is served at / with the credits hook", async () => {
|
||||
const res = await fetch(base + "/");
|
||||
expect(res.status).toBe(200);
|
||||
const html = await res.text();
|
||||
expect(html).toContain('id="voyage-app"');
|
||||
expect(html).toContain("data-fan-credits");
|
||||
});
|
||||
|
||||
test("/mockups redirects to the gallery", async () => {
|
||||
const res = await fetch(base + "/mockups", { redirect: "manual" });
|
||||
expect(res.status).toBe(302);
|
||||
expect(res.headers.get("location")).toContain("/mockups/");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user