Commit Graph
5 Commits
Author SHA1 Message Date
Lucas WintherandClaude Opus 5 a039176aa3 docs: prune TODO.md, and correct four claims that were no longer true
TODO.md becomes a TODO again: only work that is actually queued. Five of
its seven sections were finished work, and their reasoning has permanent
homes — the rules moved to AGENTS.md (the baked rates seed a new dataset
only, never a fallback), the rest is in the commit messages and the
specs. 215 lines to 131.

The four corrections, all of them things a reader would have believed:

- README said the install prompt could not work because the manifest
  "only ships an SVG icon". It has shipped 192x192 and 512x512 PNGs since
  f50c65b. Now stated as unverified rather than incomplete, which is what
  it actually is.
- TODO said no browser had seen the two rule-editing surfaces, while its
  own section 5 listed both as seen and confirmed.
- TODO said "merged to main" when the last three commits were on a
  branch.
- scripts/screenshot.ts explained its own existence by saying
  ARCHITECTURE.md and TODO.md claim no browser can run here. Both now say
  the opposite — partly because of that script.

ARCHITECTURE.md's gotchas section now says AGENTS.md holds the
enforceable form of those rules and this holds the mechanism, so a reader
hitting the overlap knows which is canonical.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-02 21:57:58 +02:00
Lucas WintherandClaude Opus 5 d83edccc79 docs: move the long-form docs into docs/, without the superpowers nesting
ARCHITECTURE, PRODUCT and TODO move out of the repository root, and
docs/superpowers/{specs,plans} flattens to docs/{specs,plans}:
"superpowers" names the tool that wrote those documents, not a kind of
document.

No content changes. This commit is the move, plus the roughly thirty
cross-references the move invalidated.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-02 21:56:59 +02:00
Lucas WintherandClaude Opus 5 bd5a01e03e chore(tooling): make the screenshot recipe work with no setup
Two things this session depended on lived only in a scratchpad, so the next
agent would have had to reinvent them from a prose hint:

- scripts/screenshot-fonts.conf, now applied by screenshot.ts itself. Without
  it this container renders every generic font family as Fira Code and the UI
  looks monospaced in a way no user sees — a trap that nearly produced a "fix"
  for a font bug that does not exist.
- scripts/demo-data.ts, which seeds a visibly synthetic dataset. Without it
  every screenshot shows an app full of zeros, because Chrome starts from a
  fresh profile each run.

CLAUDE.md now carries the two-command recipe verbatim instead of "point
FONTCONFIG_FILE at an override", and says why --reload is mandatory. Verified
from a clean shell with no environment set.

Also flags docs/superpowers/plans/2026-09-01-budget-app.md as a historical
record: its snippets still show CURRENCIES and CURRENCY_DIGITS, which no
longer exist, and an agent reading it as current would be misled.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-02 16:07:09 +02:00
Lucas WintherandClaude Opus 5 e6da85e7f9 fix(sw): the service worker had never installed, and updates raced the reload
Two defects behind "the new version is ready" not going away.

1. THE WORKER NEVER INSTALLED — in any environment, ever.

   install did `cache.addAll([...PRECACHE, SHELL])`. PRECACHE is derived from
   the build's own outputs, which already contain index.html, and SHELL is
   index.html again. addAll() rejects the ENTIRE install on a duplicate
   request:

     InvalidStateError: Cache.addAll(): duplicate requests (.../index.html)

   So install failed on every load, the worker went redundant, and nothing
   ever activated. Registration itself succeeds and the failure is inside the
   install event, so nothing surfaced it — which is why offline behaviour was
   listed as "never verified" rather than "broken". All 11 precached URLs
   return 200; the list was the only fault. Deduping now lives in
   src/swPrecache.ts, at the point of use, so no future addition to either
   list can reintroduce it.

   Verified in a real browser for the first time: the worker installs,
   activates, claims the page, and caches 11 entries. A rebuild is then
   detected, the prompt appears, clicking Reload applies it, stale caches are
   dropped and the prompt does not return.

2. THE DEV WORKER IGNORED THE LIFECYCLE — my own regression, from 53707b1.

   That commit made the dev server serve an inert sw.js to silence a MIME
   error. Inert included the lifecycle: no skipWaiting, no clients.claim, no
   message handler. A worker like that can sit in `waiting` forever and can
   never be applied, so the prompt returns on every load and the Reload button
   cannot possibly clear it. This is the likeliest cause of the reported
   symptom, since with defect 1 a production build could never have shown that
   prompt at all. The dev worker still caches nothing — that would fight HMR —
   but now honours install, activate and SKIP_WAITING.

3. applyUpdate RACED ITS OWN RELOAD — real, measured, but not reproducible on
   demand, so it is hardening rather than a proven fix.

   It posted SKIP_WAITING and called window.location.reload() on the next
   line. Measured in Chrome: at that instant the new worker was still
   `installed` and the controller had NOT changed — it changed 2 ms later. The
   window is wider in practice because activate deletes every stale cache
   before claiming clients. When the reload wins, the reloaded page still finds
   registration.waiting and shows the prompt again. It now waits for
   controllerchange, with a timeout backstop so a worker that ignores
   SKIP_WAITING cannot leave the button dead. The sequencing is pure and
   tested (src/ui/swUpdate.ts): listener attached before the message, reload
   exactly once, unsubscribed after.

Tooling: scripts/screenshot.ts gains --profile (a fresh Chrome profile has no
registered worker, so nothing stateful across loads could be reproduced at all)
and --eval-after-file (observe a flow that ends in a navigation from the other
side of it).

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-02 15:28:06 +02:00
Lucas WintherandClaude Opus 5 53707b14ca feat(tooling): drive headless Chrome, and see the app for the first time
CLAUDE.md, AGENTS.md, ARCHITECTURE.md and TODO.md all said no browser exists
in this environment, so appearance had never been verified by anyone. Chrome
is on PATH. Its own --screenshot flag is what fails: it fires on the load
event, before this app's async IndexedDB read resolves, and captures a blank
page — which is presumably how the claim took hold.

scripts/screenshot.ts drives CDP instead: waits past the load event, seeds
IndexedDB via --eval-file and reboots the app with --reload, clicks with
--click, applies shadcn's class-based dark palette with --dark, and reports
console errors and uncaught exceptions. A page that renders blank from a
thrown error looks identical to an empty one, so the console output is half
the value.

Two defects found by simply looking:

- The month table's numeric columns had no horizontal padding, so figures ran
  into each other: "+7,175.00 DKK4,200.00 DKK". Now padded.
- Dev logged "The script has an unsupported MIME type ('text/html')" on every
  boot, because registerServiceWorker asks for sw.js and the "/*" catch-all
  answered with the HTML shell. Dev now serves an inert worker.

Confirmed working, first time seen: the carry meter (including red where a post
is overspent), the Task 5 rule-history editor, the Task 6 "change from here"
control, and baked FX rates showing as "manual - 2026-09-01" in Settings.

Recorded in the docs, with the limits stated: this container's fontconfig maps
every generic family to a monospace face, so screenshots need a FONTCONFIG_FILE
override or you will chase a font bug that does not exist; Chrome starts from
an empty profile each run; and no screenshot can judge how a design feels.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-02 14:46:59 +02:00