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>
76 lines
2.3 KiB
JavaScript
76 lines
2.3 KiB
JavaScript
import js from "@eslint/js";
|
|
import tseslint from "typescript-eslint";
|
|
import reactHooks from "eslint-plugin-react-hooks";
|
|
|
|
/* The clock guard.
|
|
|
|
The artifact's sync bug was a fresh device stamping its own empty defaults
|
|
as newer than the server's real data. db/writes.ts owns the distinction
|
|
between a seed write (updated_at stays 0) and a user edit (stamped), so it
|
|
is the only file under src/db/ allowed to read the clock. Anywhere else,
|
|
reaching for Date.now() means someone is about to stamp a row without
|
|
deciding which kind of write it is. */
|
|
const CLOCK_BAN = [
|
|
{
|
|
selector: "CallExpression[callee.object.name='Date'][callee.property.name='now']",
|
|
message:
|
|
"Only db/writes.ts may read the clock. Use a seedX() helper (leaves updated_at 0) " +
|
|
"or an editX() helper (stamps it) — see the timestamp rule in db/writes.ts.",
|
|
},
|
|
{
|
|
selector: "NewExpression[callee.name='Date']",
|
|
message:
|
|
"Only db/writes.ts may read the clock. Use a seedX() or editX() helper from db/writes.ts.",
|
|
},
|
|
];
|
|
|
|
export default tseslint.config(
|
|
// lib/, data/ and validate.mjs are copied in from the export bundle
|
|
// unchanged and must stay byte-identical — they are not ours to restyle.
|
|
{
|
|
ignores: [
|
|
"**/dist/**",
|
|
"**/node_modules/**",
|
|
"export/**",
|
|
"vendor/**",
|
|
"app/android/**",
|
|
"lib/**",
|
|
"data/**",
|
|
"validate.mjs",
|
|
],
|
|
},
|
|
js.configs.recommended,
|
|
...tseslint.configs.recommended,
|
|
// Build scripts and the shared modules are plain Node ESM.
|
|
{
|
|
files: ["tools/**/*.mjs", "shared/**/*.mjs", "*.config.js"],
|
|
languageOptions: {
|
|
globals: {
|
|
console: "readonly",
|
|
process: "readonly",
|
|
URL: "readonly",
|
|
fetch: "readonly",
|
|
Buffer: "readonly",
|
|
},
|
|
},
|
|
},
|
|
/* The rules of hooks, and exhaustive deps in particular. A stale closure
|
|
in an effect is what let the tutor seed its opening turn three times. */
|
|
{
|
|
files: ["app/src/**/*.{ts,tsx}"],
|
|
plugins: { "react-hooks": reactHooks },
|
|
rules: {
|
|
"react-hooks/rules-of-hooks": "error",
|
|
"react-hooks/exhaustive-deps": "warn",
|
|
},
|
|
},
|
|
{
|
|
files: ["app/src/db/**/*.ts"],
|
|
rules: { "no-restricted-syntax": ["error", ...CLOCK_BAN] },
|
|
},
|
|
{
|
|
files: ["app/src/db/writes.ts"],
|
|
rules: { "no-restricted-syntax": "off" },
|
|
},
|
|
);
|