Phase 2: domain layer with tests
Vite + React + TypeScript + Vitest. No router, state library or UI kit. The 159-row combo table is extracted from reference/index.html by scripts/extract-reference-data.mjs rather than retyped: it balances brackets past string literals to slice out the pure-data declarations and evaluates them in a vm with no DOM. raw.ts and labels.ts are generated. The extracted count matches the 159 the Phase 1 app reported on device. Entry.id keeps the reference's format verbatim (shift>p1@S#FIX VEL), built from the unsplit context string, so drill progress already on the phone migrates instead of being orphaned. The domain is pure where the reference was not: boxes go in and new boxes come out, and the random source is injected. That is what made card selection testable. Two bugs found by doing so: - pickCard returned the card just answered when random() gave exactly 0. A zero-weight card does not move the accumulator, so "r <= 0" matched it. The reference has this; at one draw in 2^53 it is invisible in use, but a pinned random source hits it every time. Fixed to "r < 0". - boxDistribution incremented through an index that noUncheckedIndexedAccess types as possibly undefined. 65 tests. Several assert numbers observed on the device: 159 entries, sample-mode layers of 3 / 35 / 2, a 16-card SHIFT+pads deck. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
42
CLAUDE.md
42
CLAUDE.md
@@ -156,6 +156,48 @@ now empty and bar styling is configured in `capacitor.config.json`
|
||||
installs over Phase 1's without an uninstall and drill progress survives.
|
||||
`appId` is `dev.ncode.sp404layers` and must not change for the same reason.
|
||||
|
||||
## Phase 2: the React port
|
||||
|
||||
Stack: **Vite + React + TypeScript + Vitest**. No router, no state library, no UI
|
||||
kit — the app is four tabs and a reducer, and none of those earn their keep.
|
||||
|
||||
| Command | What it does |
|
||||
|---|---|
|
||||
| `npm test` | Run the domain suite once |
|
||||
| `npm run test:watch` | Watch mode |
|
||||
| `npm run typecheck` | `tsc --noEmit` |
|
||||
| `npm run extract:data` | Regenerate the combo table from `reference/` |
|
||||
|
||||
```
|
||||
src/domain/
|
||||
types.ts hand-written: RawEntry, Entry, ContextCode, GroupId
|
||||
raw.ts GENERATED — the 159-row combo table
|
||||
labels.ts GENERATED — control labels, groups, contexts
|
||||
entries.ts derives Entry[] from RAW
|
||||
layers.ts which combos exist for a context + held modifiers
|
||||
drill.ts decks, Leitner boxes, answer checking, card selection
|
||||
```
|
||||
|
||||
**The combo table is never transcribed by hand.** `scripts/extract-reference-data.mjs`
|
||||
lifts the literals out of `reference/index.html`, evaluating only pure-data
|
||||
declarations in a `vm` with no DOM, and writes `raw.ts` and `labels.ts`. Re-run it
|
||||
after any reference change; never edit those two files. Retyping 159 rows is
|
||||
precisely where silent errors would creep in, and the extraction cross-checks:
|
||||
159 entries, matching the count the Phase 1 app reported on the device.
|
||||
|
||||
**`Entry.id` keeps the reference's format verbatim** (`shift>p1@S#FIX VEL`) — built
|
||||
from the *unsplit* context string and the target as authored. That string is the
|
||||
key drill progress is stored under, so preserving it byte-for-byte means existing
|
||||
boxes on the phone migrate to the React app instead of being orphaned.
|
||||
|
||||
**The domain layer is pure.** The reference mutated a module-level `store` and
|
||||
called `Math.random()` inline; here boxes go in and new boxes come out, and the
|
||||
random source is injected. That is what makes card selection testable at all.
|
||||
|
||||
Tests assert against numbers **observed on the device**, not just against the
|
||||
code: 159 entries, sample-mode layer counts of 3 / 35 / 2 for no-modifier /
|
||||
SHIFT / SHIFT+REVERSE, and a 16-card SHIFT+pads deck.
|
||||
|
||||
## Visual spec (settled — build Phase 2 against this)
|
||||
|
||||
The look is **Direction A, "manual"**, after Teenage Engineering's EP-133 manual,
|
||||
|
||||
Reference in New Issue
Block a user