Skip to content

Latest commit

 

History

History
54 lines (39 loc) · 6.12 KB

HANDOFF.md

File metadata and controls

54 lines (39 loc) · 6.12 KB

Hatch handoff

Read AGENTS.md (conventions, commands) first, then content/AUTHORING.md before touching lessons.

State (all green at the time of writing)

  • Curriculum complete: 18 units, 266 lessons (u01-u18), strict npm run content passes, check-content has 0 errors / 0 warnings, verify-content runs every solution and invalid sample through the real checker with 0 problems.
  • App: lessons workspace (bento: lesson / CodeMirror editor / terminal), multi-file projects with file tabs, review sessions, playground, mastery map, achievements, settings, XP / levels / streaks, persistence with backups, mobile tabs, light and dark themes.
  • Quality gates: npm run typecheck, npm run lint, npm test (55 Vitest tests) all pass. Playwright: lesson-flow, features, and walkthrough (plays lessons with their reference solutions; multi-file aware).
  • Walkthroughs (final build, after the last app change): every lesson of all 18 units was played through the real UI (Chromium, JSPI available) with its reference solution and passed: u01-u09 134/134, u10-u11 + u17-u18 47/47, u12 8/8, u13-u16 77/77 (all 266). The lesson-flow and features specs (10 tests) pass too.

Where things live

Area Path
Python runner, checker, AST features, error shaping (single implementation) python/hatch/
Worker that hosts Pyodide (module worker, Atomics-blocking input()) public/hatch-worker.js, src/runtime/
Curriculum schema, graph, validators, execution verifier src/curriculum/
Curriculum data content/ (spec: content/AUTHORING.md)
Pure engine: XP ledger, mastery, review, achievements src/engine/ (tested in tests/)
Store and persistence (hatch.v1, hatch.drafts.v1) src/store/
Lesson/review orchestrator src/components/lesson/workspace.tsx, step-views.tsx

Visual design

  • Blue-accent, larger, sturdier UI (light = sky tint with white cards, dark = deep navy). Tokens live at the top of src/app/globals.css: --accent (solid blue for buttons and text, 5.4:1 on white), --accent-bright (#1cb0f6, fills only: progress bars, active step), --xp-bright (gold fills), green only for success. Nunito (rounded) replaced Geist/Fraunces; JetBrains Mono for code.
  • Root font size is 106.25% (mobile) and 112.5% (>= 768px); Tailwind text-xs/sm/base were enlarged in @theme and the old text-[0.6-0.7rem] micro sizes were bumped to >= 0.8rem. Buttons are chunky (2px border, 4px bottom edge, 3rem tall), panels have 2px borders and a flat ledge shadow, progress bars are 0.75-1rem thick. Default editor font size is now 17 (existing saved preferences keep their value).
  • Contrast for the small coloured labels was checked numerically (all >= 4.5:1 in light, >= 5.8:1 in dark). The Duolingo-style reference tokens were adapted, not copied: white text on the bright #1cb0f6 would fail contrast, so solid buttons use the darker #0a6eb4.

Decisions worth knowing

  • Checks are behavioural. AST rules apply to the entry file only; for multi-file projects the u11/u18 lessons use hidden fixtures (probe.py, mutants.py, h.py) that run the learner's own tests against broken implementations or parse the modules with ast.
  • Async lessons (u17) check the stdout of the learner's complete program. Pages in the fetcher project are simulated (SITE dict + asyncio.sleep): the lesson says so. Delays differ by at least 30 ms to keep the output order deterministic.
  • "Check" button = run the hidden check only; "Run" = run in the terminal, then check (also when the program errors, so steps that expect an error can pass).
  • Preferences: the old sound toggle and the "Full motion" option were removed because they did nothing. Old saved values are migrated by the zod schema (persistence.ts, covered by a test).
  • Lint ignores the vendored public/pyodide/** and src/generated/**.

Versioning and migration

  • Saved progress is a JSON document (hatch-progress, schemaVersion in src/engine/progress.ts). Reading goes through zod in src/store/persistence.ts; to change the shape, bump SCHEMA_VERSION and add a case to migrate(). A document from a newer app is never overwritten (the learner sees a notice), and a damaged one is copied to a backup key before anything is reset.
  • Every lesson has an integer version (YAML header, default 1) and progress records the version a learner completed. Bump it when a lesson changes in a way that should not silently count as the old one; course.version in content/course.yaml is the course-level counterpart.
  • Drafts (hatch.drafts.v1) are per step, pruned oldest first when over budget.

Known limits / next steps

  1. Browsers without JSPI (non-Chromium): live runs work through a rewrite of module-level asyncio.run, but hidden checks that call asyncio.run are not supported there. u17 only relies on stdout checks, but this has not been exercised in Firefox/Safari.
  2. Walkthrough runs: HATCH_E2E_UNITS=u10,u11 npx playwright test walkthrough (about 3 minutes per 25 lessons). Make sure no stale next dev is on :2034 first (a hung one makes every test time out in page.goto). A failing lesson usually means a solution that passes the Node checker but behaves differently in Pyodide. Two real examples found this way: JSPI was never enabled because can_run_sync() was probed outside callPromising, and under JSPI asyncio.run turned 4.0 into 4 (fixed by boxing the result in bridge.fix_asyncio_run); and checklist ticks leaked between checklists of one lesson because they shared the scratchpad key.
  3. Curriculum review: content was validated mechanically (prerequisite leaks, terminology, feature ownership, XP). A human read-through for tone, pacing and difficulty spikes, especially units 12-17, is still the best next investment.
  4. Reasonable future features, none started: export/import of progress as a file, per-unit "skip test" placement quiz (deliberately absent: the app picks the order), keyboard shortcut cheat-sheet.

Housekeeping

  • test-results/ and playwright-report/ are generated and safe to delete.
  • The repository has a single "Initial commit" and nothing committed since; no remote is configured.