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>
40 lines
1.1 KiB
JSON
40 lines
1.1 KiB
JSON
{
|
|
"name": "hankan",
|
|
"version": "0.1.0",
|
|
"private": true,
|
|
"type": "module",
|
|
"description": "Hankan \u2014 a Korean reading tutor for manhwa. Offline-first, web + Android from one codebase.",
|
|
"workspaces": [
|
|
"app",
|
|
"server"
|
|
],
|
|
"engines": {
|
|
"node": ">=22"
|
|
},
|
|
"scripts": {
|
|
"validate": "node validate.mjs",
|
|
"dict:fetch": "node tools/dict/fetch.mjs",
|
|
"dict:build": "node tools/dict/build.mjs",
|
|
"dict:assert": "node tools/dict/assert-roadmap.mjs",
|
|
"typecheck": "tsc -b --pretty",
|
|
"test": "vitest run",
|
|
"test:watch": "vitest",
|
|
"lint": "eslint . --max-warnings 0",
|
|
"dev": "npm run dev -w app",
|
|
"build": "npm run build -w app",
|
|
"preview": "npm run preview -w app",
|
|
"cap:sync": "npm run cap:sync -w app",
|
|
"check": "npm run validate && npm run typecheck && npm run test && npm run dict:assert"
|
|
},
|
|
"devDependencies": {
|
|
"@eslint/js": "^9.17.0",
|
|
"@types/node": "^22.10.2",
|
|
"eslint": "^9.17.0",
|
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
"typescript": "^5.7.2",
|
|
"typescript-eslint": "^8.18.1",
|
|
"vite": "^6.4.3",
|
|
"vitest": "^3.2.4"
|
|
}
|
|
}
|