Files
Lumotia/src
Jake 1c4ac98504 agent: lumotia — Phase B.10 pin focusTimer expired-rehydrate startTick invariant
Phase B.10 audit of commit 5ba761a (focusTimer rehydrate startTick
invariant; Race-10). The commit landed a comment-only change at
src/lib/stores/focusTimer.svelte.ts lines 204-208:

  // startTick() is REQUIRED here: tick() is the only thing that
  // observes `now >= completionFlashUntil` and calls clear(). Without
  // it, the completion flash would stay visible until the user
  // interacts with the app. stopTick() runs via clear() once the
  // flash window elapses.

A future edit could silently drop the startTick() call (the commit
acknowledged this with the comment) and the regression would only
surface on a real session: the user reopens Lumotia after a closed
expired timer, sees the completion flash, and waits for it to clear.
It never does. They click somewhere → clear() fires from the click
handler. Visible bug, but no automated gate.

The 5ba761a commit could not add a test at the time it landed because
vitest wasn't wired in the workspace — vitest scaffold landed in
commit 206ac62 the next day as Phase A.5. Now that vitest exists
(jsdom environment, .svelte.ts rune transformer, fake timers via
vi.useFakeTimers — see vite.config.js test block), the invariant is
straightforwardly testable.

Fix: new src/lib/stores/focusTimer.test.ts. The single test
`auto-clears the completion flash after the 3s window via the tick loop`:

  1. Seeds localStorage with a timer started 60 s ago that lasted only
     30 s — already-expired by 30 s when rehydrate runs.
  2. Calls focusTimer.rehydrate(). Asserts the flash is now visible
     and the timer is reported active.
  3. vi.advanceTimersByTime(3_500) — pushes wall-clock past the
     completionFlashUntil mark, drives the setInterval ticks.
  4. Asserts showingCompletionFlash is false, active is false,
     remainingMs is 0, and localStorage has been wiped.

If a future edit removes the startTick() call on the already-expired
branch of rehydrate(), step (3) won't run any ticks, the flash won't
clear, and step (4) fires the regression assertion. The test pins the
invariant the comment alone could not.

Verification:
  * npm run test → 13/13 (was 12, +1 from this commit). Both test
    files pass: localStorageMigration.test.ts (unchanged, 12 tests)
    and the new focusTimer.test.ts (1 test).
  * npm run check → 0 errors / 0 warnings across 4015 files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 20:25:32 +01:00
..