Commit Graph

4 Commits

Author SHA1 Message Date
MechaCat02
e72b77d6c2 chore: take in the 16 Sep bundle — lib, curriculum v5, prompt, gate audit
The artifact was reworked after real incidents: a week of lost data, a
student taught out of order, and spelling diagnoses the model invented.
This takes the new export in verbatim; the port catches up in the
commits that follow.

Copied byte-identical from the bundle:
  lib/        lexicon.js and sync.js are new; gate.js gains enforcement,
              hangul.js letter-level marking, srs.js recall evidence,
              conjugation.js deconjugate(); blocks.js now takes the last
              block, closes gloss at "=", and parses recall, ::result and
              ::confirmed
  data/       curriculum.json v5 — six 다지기 phase reviews; the 371
              roadmap words are unchanged and no band moves
  prompt/     English-only rule, recall, LETTER-LEVEL CHECK, marking
  audit-gate.mjs, run-checks.sh, fixtures/  — the word gate measured
              against 54 real tutor messages

CI runs run-checks.sh in place of validate.mjs alone, and `npm run check`
gains the audit. Baselines: validate PASS 0/0; audit 7 of 41 and 2 of 13.

types/lib/ declares the new API, and test/lib/ pins it: letterCheck on
the prompt's own 짧다/빫다 case, deconjugation, the roadmap-first order
that keeps 마셔 out of Phase 1, sync's three gates, and recall evidence —
including the two ways lib's evidence is looser than PORT.md, pinned as
they are so the call site that tightens them is visibly needed.

TaskHost gains a plain recall renderer so the tree typechecks against the
wider Task union.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 19:49:52 +02:00
MechaCat02
0b04931699 fix: a NUL byte was hiding two source files from grep, git and diff
TaskHost.tsx used a literal NUL as the delimiter in its drag-and-drop
payload, and tools/dict/build.mjs used one to join headword and part of
speech. Both work at runtime. Both also make the file *binary* to every
text tool: git shows "Bin 12259 bytes" instead of a diff, and grep prints
nothing at all for a match.

That is not hypothetical. Searching TaskHost.tsx for "<input" came back
empty three times while reviewing it, which is how its four exercise
inputs came to be reported as absent -- and why the accessibility defect
in them went unseen. Written as the escape \u0000 the value is identical
and the file stays text.

test/source-hygiene.test.ts fails on any control byte in a source file, so
this cannot come back quietly.

With the files readable again, the sweep the NUL had been hiding: eleven
form controls had no accessible name. The exercise blanks announced only
an ellipsis, and the part-of-speech select announced nothing. A
placeholder is not a label -- it disappears the moment you type. All
eleven now carry one, named after the thing they answer.

`npm run lint` gains --max-warnings 0. exhaustive-deps is configured as a
warning, so a hooks-dependency bug would have passed CI silently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 21:06:39 +02:00
MechaCat02
c2e1fc23fe feat(server): the sync endpoints, on Node 22 with no build step
Hono and pg, run under --experimental-strip-types, so the deployed thing
is the source. GET /api/sync?cursor=N pages rows above the cursor;
POST /api/sync upserts last-write-wins. Bearer token on everything under
/api; /health is open, for the container healthcheck.

Rows are stored generically — primary key as text, body as JSONB —
because the server never reads inside a row. It stores and orders them and
the client interprets them, which keeps the two schemas from having to
move in lockstep.

change_seq is bumped by a BEFORE UPDATE trigger rather than by the write
path. A row edited after a client last pulled would otherwise keep its old
sequence, sit below that client's cursor, and never be delivered; putting
it in the database means no future write path can forget.

The last-write-wins comparison is in the ON CONFLICT clause itself, so a
losing row is not written at all and does not bump change_seq — a
conflict does not become traffic for every other device.

test/sync/roundtrip.test.ts runs two clients against a real Postgres and
asserts what actually goes wrong in sync: that a fresh client's seeded rows
cannot overwrite the server's history (the artifact's bug, as an executable
test), that a delete propagates, and that dict.loadedBands never crosses
the wire. It skips without HANKAN_TEST_SERVER, so npm test still runs
anywhere.

POST /api/test/reset exists only when HANKAN_TEST_MODE=1, so it cannot be
reached on the Pi even if the token leaks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 19:57:44 +02:00
MechaCat02
b80deb2f6f chore: scaffold, CI, and the export bundle verbatim
The export bundle is the input to this port, not a sketch: the curriculum,
the tutor prompt and the five logic modules are finished and tested. They
land here byte-identical and stay that way.

  diff -r export/data data && diff -r export/lib lib
  diff -r export/prompt prompt && diff export/validate.mjs validate.mjs

data/, lib/, prompt/ and validate.mjs sit at the repo root so validate.mjs
runs verbatim with no path edits. All four are excluded from lint and
formatting — they are not ours to restyle. Types for lib/ live alongside in
types/ rather than as sibling .d.ts files, so the verbatim check stays a
plain directory diff.

CI runs the curriculum gate first, before anything else can pass:

  node validate.mjs   PASS — 0 blocking, 0 advisory

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 19:11:40 +02:00