Brighter backgrounds, thicker strokes, and large tonal regions (backlight
pool, lit doorway, face fill, water band) so the seed art is legible as
thumbnails instead of near-black cards.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The art manifest referenced /art/*.png files that never existed; the SPA
fallback returned index.html for them, so the library thumbnails rendered
as broken images. Generate 5 atmospheric B&W placeholder scenes
(tools/gen-art-placeholders.mjs, npm run gen:art) into public/art/.
tests/tools/art-assets.test.js ties every manifest entry to a real PNG.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The cache-first SW never revalidated the HTML, so a device that loaded the
app once kept serving the old bundle forever (new deploys never landed).
Now: network-first for navigations/HTML (fresh on every online load, cached
shell as offline fallback), cache-first only for immutable hashed assets.
Bumped CACHE to director-v2 so the stale v1 cache is purged on activate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The rail was a fixed 64px bar cramming 3 info cells against 9 chips, so
long beat/trigger labels wrapped and clipped ('bled'), and trays were thin
top strips leaving the iPad screen mostly empty.
- Rail: min-height + 2-line clamp on the beat/trigger labels (no bleed),
nowrap 'Reached it', slimmer chips, tabular clock.
- Trays now fill the stage as solid panels (inset:0) instead of a strip.
- New <run-board>: the whole run at a glance behind the trays — every beat
with target time, done beats stamped/dimmed, next beat highlighted, hard
triggers + drift. Fills the previously-empty stage with the most useful
GM view.
- @media (min-width:1024px/1366px): larger type, roomier rows, more art
columns — uses the tablet space.
tests/components/run-board.test.js. 219 tests pass; build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docker-compose.proxy.yml runs the container on a shared external 'proxy'
network with no published ports, for hosting behind an existing edge
proxy (Caddy/Traefik/etc.) that routes <sub>.domain -> the-director:80.
This is how it's deployed at apps.timevans.uk.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Serve the built PWA behind your own domain: multi-stage Dockerfile builds
dist/ and serves it with Caddy on :80 (TLS terminated at your Cloudflare
edge). Caddyfile sets no-cache on sw.js/index/manifest and immutable on
hashed assets; SPA fallback to index.html. docker-compose maps :8087.
Verified locally: image builds, container serves 200s with correct cache
headers and SPA fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Generate the manifest/PWA + native-wrapper icons that were referenced but
missing (icon-192/512 were 404). A reproducible pure-Node generator
(tools/gen-icons.mjs, npm run gen:icons) draws a clock ring with amber
hands + a green target dot in the app palette — no image deps, hand-rolled
PNG encoder with analytic anti-aliasing.
- public/icons/icon-192, -512, -180 (apple-touch); linked in index.html.
- resources/icon.png (1024) + splash.png (2732) for @capacitor/assets.
- tests/tools/icons.test.js ties every manifest icon to a real PNG of the
declared size. 213 tests pass; icons now serve 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wrap the PWA in a thin native iOS shell (Capacitor 8):
- capacitor.config.json (webDir dist, appId uk.timevans.thedirector,
iOS safe-area contentInset + theme background).
- @capacitor/{core,cli,ios} as devDependencies (web app keeps zero
runtime deps); ios:add / ios:sync / ios:open / ios npm scripts.
- main.js skips service-worker registration under Capacitor
(window.Capacitor.isNativePlatform) — assets are already local.
- iOS <meta> hints in index.html; .gitignore for native build output.
- docs/native-ipad-wrapper.md: full Xcode + CocoaPods build guide,
incl. the keep-awake plugin note for the wake toggle in WKWebView.
The iOS project generation/build (npx cap add ios → Xcode) needs a Mac
with full Xcode + CocoaPods, which this env lacks; it is documented for
the user to run. tests/tools/capacitor-config.test.js guards the config.
211 tests pass; build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a deterministic authoring tool that turns a structured scenario
markdown file into a validated scenario-data module:
- tools/scenario-md.js: pure parseScenarioMarkdown + emitScenarioModule
(frontmatter → meta; ## Timeline/## Clues/## Cast lists → the arrays;
auto-slugged ids; validated via validateScenario before emit).
- tools/md-to-scenario.mjs: CLI (npm run gen:scenario), file or stdout.
- tools/example-scenario.md: documented format example.
Parses a defined format, not freeform con prose. tools/ is dev-only and
never bundled. tests/tools/scenario-md.test.js round-trips + validates.
187 tests pass; build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the last two design-§5 tray tools:
- <parking-lot>: one-tap capture of a thread to return to, stamped with
the session time, persisted per-scenario (parkingLot store key), 📝 chip.
- src/core/wake-lock.js: injectable Screen Wake Lock controller (keeps the
tablet awake, re-acquires on foreground, graceful no-op where the API is
absent). Toggled by a 💡 rail chip that lights when active.
Tests: parking-lot.test.js, wake-lock.test.js, gm-shell-parking.test.js,
gm-shell-wake.test.js. 179 tests pass; build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add <break-timer> tray: pick a duration, Start break pauses the session
clock (reusing clock.pause/resume), End break resumes it — so meal/comfort
break time is excluded from elapsed and drift stays honest. The shell owns
pause/resume + persistence (break store key); the rail shows a on-break
badge and gains a coffee chip. fmtCountdown (M:SS) added to format.js.
tests/components/break-timer.test.js + gm-shell-break.test.js (incl. the
30-play/20-break/10-play drift-honesty case) + fmtCountdown. 159 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add brp-d100 rule-pack (Basic Roleplaying roll-under): fumble on 100,
critical <= skill/20, special <= skill/5, success <= skill — bands
transcribed from the Continuum Day One console's verdict() logic.
Register it and switch day-one meta.system from coc-d100 to brp-d100.
tests/dice/brp-d100.test.js covers the ladder, low-skill rounding, and
the fumble-only-on-100 edge. 147 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add <cast-tray>: renders scenario.cast (name + one-line note) with each
NPC secret behind a tap-to-reveal so spoilers stay off-screen at the
table. Wired into the shell as the 'cast' tool with a 👥 rail chip;
reveal state is ephemeral component state. Now the 57 ported NPCs are
visible in a running session.
tests/components/cast-tray.test.js + gm-shell-cast.test.js. 141 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Populate cast[] (kind:'npc') for every Continuum scenario, transcribed
from the gm-utility NPC sources — normalising the two schemas (console
{id,name,ess,sec} and module {name,tag,secret}) into
{ id, name, kind, note, secret? }. 57 NPCs total. Pregens/PCs excluded
(separate character-sheet system).
No cast UI consumer yet — the design reserves scenario.cast for a future
cast dashboard, so this is a data-model port guarded by
tests/scenarios/cast.test.js (shape + unique ids). 131 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Populate clues[] for every Continuum scenario (afterimage, day-one,
vain-crown, silvery-moon, chopper, princes-bride), transcribed faithfully
from the gm-utility console sources (CLUES arrays, gbclue GM-brief spines,
and the princes-bride module). The Slice 4 clue safety-net now works
against real content — essential-gap banner, act labels, and fallbacks.
day-one and princes-bride carry the source's own essential/optional split;
the four GM-brief spines mark each tracked clue essential (single curated
list, no optional tier in-source).
tests/scenarios/clues.test.js guards shape, unique ids, and solvability.
129 tests pass; build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rewrite continuum-2026.js from the authoritative con-desk.html SLOTS
array: six real slots with days/times/systems/players and startsAt set,
so the hub's live-now/up-next follows the real convention clock.
Add five scenario modules (day-one, vain-crown, silvery-moon, chopper,
princes-bride) transcribed from the existing gm-utility console
timelines, and register them so every slot is tappable. Day One (BRP)
borrows the coc-d100 pack; the others map to their own systems.
Corrections found while porting: the slate is six slots (Slot 5 Silvery
Moon was missing) and s6 is 'Get to the Chopper — Another Fine Mess'.
tests/con/continuum-2026.test.js guards slot resolvability, valid dice
packs, and schedule status. 125 tests pass; build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the convention hub landing screen: a pure schedule engine
(analyzeSchedule) classifies each slot as live/next/done/upcoming from an
injected now; <con-hub> renders the slate + summary and emits open-scenario;
a scenario registry resolves a slot's scenarioId to its module; gm-shell
routes hub<->session (hiding/restoring the Rail) with a home rail chip.
Ships continuum-2026.js with the real slate (only AFTERIMAGE ported;
others scenarioId:null, start-times TBC) and a fully-timed example-con.js
template/fixture. 122 tests pass; build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>