ncode b875342864 Phase 2: persistence, and real pickers instead of cycling
The stored blob is now read once at startup through useReducer's lazy
initialiser and written whenever it changes, so deck, direction,
answering mode, context and the Leitner boxes all survive a restart.

persistedState.ts is the only part that touches the browser; the parsing
stays in domain/storage.ts. Every access is guarded, because localStorage
throws rather than returning null in a private window, with site data
blocked, or in a WebView that has not finished starting.

That guard is now tested, along with the claim this whole migration rests
on: a payload copied verbatim out of CapacitorStorage.xml on the phone
loads back with its boxes intact. If that test ever fails, the React app
has orphaned real drill progress. Also covered: damaged JSON, storage
that throws, localStorage absent entirely, and a refused quota on write.

Explore's pickers open a bottom sheet rather than cycling blindly through
options, and the drill settings button is no longer dead - deck,
direction and answering mode, each showing how many cards it selects.
Sheet is shared by all three.

Also fixed a silent one: .sheetLabel was used three times and did not
exist. CSS modules are index-typed, so that compiles clean and renders
with no class at all.

141 tests, typecheck clean. Sheets verified by rendering them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 10:46:42 +03:00
2026-09-17 00:54:34 +03:00
2026-09-17 00:54:34 +03:00
Description
An SP-404 MK2 key combos trainer & quick reference
MIT 920 KiB
2026-09-17 18:51:01 +00:00
Languages
TypeScript 41.6%
HTML 40.3%
CSS 7.3%
JavaScript 6.3%
Shell 3.9%
Other 0.6%