docs: handoff, session log and pointers to the contract

docs/HANDOFF.md is the starting point for the next maintainer;
docs/superpowers/notes/2026-11-11-session-log.md records the day.
AGENTS.md, CLAUDE.md and README now point maintainers to the handoff
and to docs/contract.md.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Fable 5.2 committed 2026-11-11 12:06:48 +01:00
1 parent c67d81b288
commit 2277f4beb6
6 files changed
+165 -3

No files matched your search

+2
View File
@@ -3,3 +3,5 @@
If you are an AI assistant helping a student with this course, read [MENTOR.md](MENTOR.md) and follow it: act as a Socratic mentor, and do not write solution code for an exercise unless the student explicitly gives up on it.
Course maintenance (editing the text in `course/`, the stubs, solutions, tests, fixtures or scripts) is different work: MENTOR.md does not apply. See "For maintainers" in [README.md](README.md#for-maintainers). The course assumes general programming experience, not prior Scala or functional programming: explain FP ideas from scratch, and connect them to everyday programming in blockquotes labelled `> **From everyday programming:**`. Check your changes with `scripts/verify.sh <js|scala|fsharp>`, `scripts/check-stubs.sh <track>`, `scripts/check-progression.sh <track>`, `node scripts/smoke.mjs <url>` and `node scripts/check-links.mjs`.
If you are maintaining the course, start with [docs/HANDOFF.md](docs/HANDOFF.md) (current state, how a change is made, what is not verified) and [docs/contract.md](docs/contract.md) (the canonical rules for every module). Further checks: `scripts/typecheck-js.sh`, `node scripts/mutate.mjs` and `scripts/e2e.sh`.
+2
View File
@@ -3,3 +3,5 @@
If you are an AI assistant helping a student with this course, read [MENTOR.md](MENTOR.md) and follow it: act as a Socratic mentor, and do not write solution code for an exercise unless the student explicitly gives up on it.
Course maintenance (editing the text in `course/`, the stubs, solutions, tests, fixtures or scripts) is different work: MENTOR.md does not apply. See "For maintainers" in [README.md](README.md#for-maintainers). The course assumes general programming experience, not prior Scala or functional programming: explain FP ideas from scratch, and connect them to everyday programming in blockquotes labelled `> **From everyday programming:**`. Check your changes with `scripts/verify.sh <js|scala|fsharp>`, `scripts/check-stubs.sh <track>`, `scripts/check-progression.sh <track>`, `node scripts/smoke.mjs <url>` and `node scripts/check-links.mjs`.
If you are maintaining the course, start with [docs/HANDOFF.md](docs/HANDOFF.md) (current state, how a change is made, what is not verified) and [docs/contract.md](docs/contract.md) (the canonical rules for every module). Further checks: `scripts/typecheck-js.sh`, `node scripts/mutate.mjs` and `scripts/e2e.sh`.
+1 -1
View File
@@ -157,7 +157,7 @@ e2e/ maintainer tooling: Playwright end-to-end tests of eve
## For maintainers
These scripts check the course itself; students do not need them. Run them from the repository root.
These scripts check the course itself; students do not need them. Run them from the repository root. The rules the course code must follow are in [docs/contract.md](docs/contract.md); the current state and working method are in [docs/HANDOFF.md](docs/HANDOFF.md).
| Script | What it checks |
|---|---|
+99
View File
@@ -0,0 +1,99 @@
# Handoff — Pure Snake course
Written 2026-11-11 for whoever works on this repository next. It assumes you know nothing about the earlier sessions. Read this page, then `docs/contract.md`.
## State in one paragraph
The course is complete and green. It teaches functional programming by building a purely functional Snake (then Pong, then Conway's Life) in three language tracks: JavaScript (the reference), Scala 3 and F#. Modules 0–13 plus an optional Module 7b exist as text, student stubs, reference solutions, tests and shared fixtures. The work was merged into `main` as `c67d81b` (feature commit `edd57f0`), followed by one documentation commit that added this page; `main` is pushed to `origin` (`https://gitea.lucaswinther.info/lucasw89/functional-programming-snake`). A local branch `audit-and-extensions` holds the same commit and can be deleted. Nothing is in progress.
**The one thing nobody has done: play the games by hand.** Every check is automated.
## Where things are
| Path | What |
|---|---|
| `README.md` | For students: what the course is, module map, checkpoints. "For maintainers" lists every check script. |
| `course/` | The text: `00-setup.md` … `13-capstone-life.md`, `07b-stack-safe-io.md`, `method.md`, `reference.md` (all names in three languages). |
| `<track>/src`, `<track>/solutions`, tests | Stubs, finished code, tests, for `js`, `scala`, `fsharp`. Same file names in `src` and `solutions`. |
| `fixtures/` | Shared JSON test cases, read by all three tracks. |
| `e2e/`, `scripts/e2e.sh` | Playwright end-to-end tests (maintainers only; the tracks have no dependencies). |
| `scripts/` | `verify.sh`, `check-stubs.sh`, `check-progression.sh`, `typecheck-js.sh`, `mutate.mjs` (+ `mutate-allow.txt`), `smoke.mjs`, `check-links.mjs`, `serve.mjs`. |
| `docs/contract.md` | **The canonical contract**: data shapes, every module's rules, constants, pinned values, fixture inventory, required checks. |
| `docs/superpowers/specs/` | `2026-10-28-…-design.md` (original design), `2026-11-11-audit-pass.md`, `2026-11-11-course-extensions.md` (twelve items, with the rules every builder followed). |
| `docs/superpowers/notes/2026-10-29-build-log.md` | Every decision made on the owner's behalf, verification results and open items, in three dated sections. |
| `docs/superpowers/notes/2026-11-11-session-log.md` | What happened on 2026-11-11, in order, and what was learned about working this way. |
| `docs/superpowers/plans/2026-10-28-pure-snake-course.md` | The original build plan. Its "contract" section is **superseded** by `docs/contract.md`. |
| `MENTOR.md`, `AGENTS.md`, `CLAUDE.md` | Instructions for an AI helping a *student* (Socratic mentor) and the pointer for maintainers. |
## Before you change anything
Run these from the repository root and expect them to pass. If one fails on a clean checkout, that is your first task.
```sh
scripts/verify.sh js && scripts/verify.sh scala && scripts/verify.sh fsharp # 728 / 689 / 691 tests
for t in js scala fsharp; do scripts/check-stubs.sh $t && scripts/check-progression.sh $t; done
scripts/typecheck-js.sh # needs network (npx typescript); exits 2 without it
node scripts/mutate.mjs # ~30 s; exit 0 = no surviving mutant outside the allow-list
scripts/e2e.sh # ~25 s after the first build; 176 tests, Chromium + WebKit
node scripts/check-links.mjs # online; two okmij.org PDFs sometimes time out and pass on retry
```
Environment on the machine this was built on: Node 24; `scala-cli` at `~/.local/bin/scala-cli` (fetches its own JDK and Scala 3.5.2); .NET SDK 10 at `~/.dotnet/dotnet` (Fable 5.24.0 as a local tool); Google Chrome at `/usr/bin/google-chrome`; Playwright 1.65.0 with Chromium and WebKit builds in `~/.cache/ms-playwright` (no Firefox; the e2e config skips missing browsers and never downloads). The first Scala and F# builds need the network and are slow. `scripts/serve.mjs` binds `127.0.0.1:8000` by default and takes a port argument.
## How a change is made here
1. **Decide the behaviour** and write it into `docs/contract.md` (and a spec under `docs/superpowers/specs/` if it is more than a fix).
2. **JavaScript first**: stub with its doc comment, solution, tests, fixtures (generated from the JS solution with a throwaway script, then read by hand), course text.
3. **Port to Scala and F#**, touching only that track's directory. Use the same seeds and run counts so pinned values match; if a pinned value differs, the draw order differs — find out why before changing a test.
4. **Run every check above** for every track you touched, and the e2e suite for anything a page shows.
5. **Record** what you decided in the build log.
Rules that are easy to break:
- **Course text never explains an idea by reference to Scala** and assumes no FP background. Bridges come from everyday programming, in blockquotes starting `> **From everyday programming:**`. (Owner's rule, 2026-10-29.)
- Every exercise has signatures in all three languages, three folded hints (nudge, stronger hint, near-solution skeleton with holes — never the solution; capstones have two), a "Tests"/"Proved by" list, and "Check your understanding" questions.
- A stub must compile and fail per test, not at import or module load.
- `fsharp/src/Snake.fsproj` and `fsharp/solutions/Snake.fsproj` stay identical.
- Existing fixture cases are contract: do not change one to make something pass.
- Features arrive in the module that teaches them. A field or platform method may exist earlier but dormant (`frame`, `queued`, `onClick`), and the tests of earlier modules must pass for a student who has not built the later feature. `check-progression.sh` cannot prove that (it copies finished solution files), so check it by hand in a temp copy when you add something cross-cutting.
- F# on Fable does not wrap `uint32` arithmetic: all seed arithmetic goes through `Random.fs`.
- Expecto silently drops plain `testCase` items in a list that also has `for … do yield`; use `yield! [ for … ]` and compare test counts with the JS suite.
- In Scala and F#, a module tag must select only its own module (`./test.sh M07` must not run M07b). Both `test.sh` files handle this; keep it working when adding tags.
## How the owner likes to work
- Once a design is approved, carry it through spec, plan and execution **without extra review stops**, and report the decisions you made at the end.
- "Skip the questions" means: decide, and say what you decided.
- For large builds: the main session writes the specs and verifies; **Opus-class subagents do the coding** (one JS reference builder per wave, then one Scala and one F# porter, each confined to its directory, never two builders on the same files at once). This is how the twelve extensions were built.
- Commit, merge and push only when asked. Commit messages end with the co-author line used in the existing history.
- Reports lead with what was found and what is not verified.
## What is not verified
- **Play by a person**, in a real browser: whether speed and controls feel right, a real hidden tab, browser zoom with Life clicks, key auto-repeat, touch, Safari and Firefox proper.
- **The JS type errors as an editor shows them.** Only the `tsc` command line was run (TypeScript 7.0.2 and 5.9.3); the version is not pinned (`TS_PACKAGE` can pin it).
- **Rendered pixels and fonts.** The e2e suite compares draw calls with the model, not images.
- **Firefox** in the e2e suite (no build installed).
- After the last change (the stored-best rule in the three browser glue files) the Scala and F# *unit* suites were not re-run, because they do not compile the glue; the e2e suite exercised it in both.
## Known small gaps
- Scala lacks the JS test "play without a best answers LoadBest with 0".
- Skipping optional Exercise 4.7 (`State`) leaves red M04 tests unless the student copies the solution file; the text says so. Giving it its own tag, like 7b, would be cleaner.
- `check-progression.sh` and `check-stubs.sh` still carry a workaround for the old M07/M07b tag collision; harmless, could be removed.
- `web/Web.fsproj` does not list `State.fs`, `SafeIO.fs`, `Check.fs` (the browser does not need them).
- The Module 9 and 10 tests on `main` accept the program with or without the stored-best load, so a student between the two modules stays green; the exact sequence is pinned by an Exercise 10.7 test.
- Earlier open items still stand in the first section of the build log (e.g. `check-links.mjs` ignores reference-style links; the purity audits exclude the whole runtime file).
## Ideas not yet built
- A third capstone from `course/method.md`'s list (Tetris or Breakout).
- An auto-pause policy for long gaps instead of dropping time (Module 10, question 9 sketches it).
- Drag-to-draw in Life (needs pointer-move events).
- Exporting a recording from a played game, so the replay viewer can show the student's own game (needs a new `Op`).
- Visual regression snapshots in the e2e suite.
- Pinning the TypeScript version and running the type check in the editor matrix.
## If something looks wrong
Trust, in this order: the fixtures, the stub doc comments, `docs/contract.md`, the course text. The build log explains why things are the way they are; search it for the function or fixture name before "fixing" a surprising rule.
@@ -79,7 +79,7 @@ Owner request: audit the course, fact-check, add sources, knowledge and exercise
- Not done: a human playing the games; the tab-switch fix is covered by stream tests only.
## Open follow-ups (added)
- Changes are uncommitted in the working tree.
- (Committed later the same day; see the end of this file.)
- `Check.fs` is not in `fsharp/web/Web.fsproj` (not needed in the browser).
- A turn queue (honouring Up-then-Left within one step) is still only a question in Module 3.
- Resolved from the earlier list: backwards `Time` is now harmless behind `clampTime`.
@@ -123,7 +123,7 @@ Owner approved eleven extensions; spec: `docs/superpowers/specs/2026-11-11-cours
- VS Code's own display of the JS type errors was not checked, only `tsc`.
- TypeScript version is not pinned (`TS_PACKAGE` can pin it).
- Scala lacks the JS test "play without a best answers LoadBest with 0".
- Everything is uncommitted in the working tree.
- (Committed later the same day; see the end of this file.)
## Playwright end-to-end suite (2026-11-11, owner's suggestion; spec Item 12)
- `e2e/` (own `package.json`, `@playwright/test` 1.65.0 pinned) and `scripts/e2e.sh`; `scripts/play.mjs` deleted, `smoke.mjs` kept. The course tracks stay dependency-free.
@@ -131,3 +131,9 @@ Owner approved eleven extensions; spec: `docs/superpowers/specs/2026-11-11-cours
- Result: 176 tests pass in about 20 s. No frame of any track differs from the model.
- Found and fixed: the three tracks read an unusual stored best differently (`"1e2"`, `"7.0"`, `"2147483648"`). Ruling: only plain decimal digits with a value of at most 2147483647 count; anything else reads as 0. Applied to `js/solutions/browser.js`, `scala/web/BrowserPlatform.scala`, `fsharp/web/BrowserPlatform.fs`.
- Still not covered: real animation timing and real hidden-tab throttling (the clamp is tested with a fake 10 s frame), rendered pixels and fonts, zoom, touch, key auto-repeat, Firefox (no build installed), and a person playing.
## Wrap-up (2026-11-11)
- `docs/contract.md` is now the canonical contract; the "contract" section of the 2026-10-28 plan is superseded.
- Specs: `specs/2026-11-11-audit-pass.md` (written after the fact) and `specs/2026-11-11-course-extensions.md` (twelve items, marked as built).
- Committed on branch `audit-and-extensions` (`edd57f0`), merged into `main` (`c67d81b`) and pushed to origin on the owner's instruction.
- `docs/HANDOFF.md` is the starting point for the next maintainer; `notes/2026-11-11-session-log.md` records the day.
@@ -0,0 +1,53 @@
# Session log — 2026-11-11
What was asked and done on this day, in order. Decisions and results are in the build log (`2026-10-29-build-log.md`, sections "Audit and extension pass", "Extension pass", "Playwright end-to-end suite"); the rules as they now stand are in `docs/contract.md`.
## 1. Audit ("take a look through this course … skip the questions")
The main session read the whole course and the JS solutions, then ran a hand-rolled mutation analysis of the solutions against the tests.
- Found three behaviour bugs: uneven steps at every speed-up, a hidden browser tab killing the snake, Pong restart clearing held keys. Fixed in JS first, then ported to Scala and F# by two subagents working from the JS diff.
- Fact-checked the text; measured mulberry32's output coverage by running its full 2^32 period (44 % of values appear).
- Wrote bonus Module 12 (property-based testing built on the student's own `Random`), with tests that run the library against deliberately broken games.
- Added fixture cases for the six test gaps the mutation run showed, challenge questions and primary sources.
- Spec written afterwards: `docs/superpowers/specs/2026-11-11-audit-pass.md`.
## 2. Ideas ("are there any ideas … to improve and extend the course?")
Eleven ideas were proposed, grouped as: close gaps the course already points at; deepen the theory; take testing further; reach and polish. The owner answered: do all eleven; the main session writes the specs and Opus 5.5 does the coding.
## 3. The eleven extensions
Spec: `docs/superpowers/specs/2026-11-11-course-extensions.md`. Builders were Opus subagents, dispatched in waves so that no two worked on the same files:
| Wave | Builder | Items |
|---|---|---|
| 1 | A (JS, fixtures, text) | 1 turn queue, 7 state generator and model-based test |
| 1 | B | 6 `State`, 8 mutation script |
| 1 | C | 4 stack-safe IO (Module 7b) |
| 2 | D | 2 persisted high score, 5 `play` |
| ports | Scala, F# | items 1, 7, 6, 4 — then 2, 5 |
| 3 | E | 3 replay viewer, 10 Life capstone with `Click` |
| ports | Scala, F# | items 3, 10 |
| 4 | F | 9 JS type checking, 11 play script, clean-ups |
The main session verified each wave before starting the next, kept tar snapshots of the working tree between waves, and ran the complete verification at the end.
## 4. Playwright ("you could consider doing the test with playwright")
Item 12 was added to the spec and built by one more Opus builder. The suite fixes the seed, fakes the clock and records canvas draw calls, then compares every frame each page draws with the pure JS model. It replaced `scripts/play.mjs`. It found one real difference between tracks (how an unusual stored best score is read); the main session ruled on one rule for all three and applied it.
## 5. Wrap-up
Contract (`docs/contract.md`), the after-the-fact audit spec, this log and `docs/HANDOFF.md` were written; everything was committed on branch `audit-and-extensions`, merged into `main` and pushed.
## What was learned about working this way
- **Write the contract into the stub doc comments and the course text first.** Porters then implemented exactly what the text says, and mismatches surfaced as questions instead of silent divergence.
- **Pin seeds, run counts and expected counterexamples.** Three independent implementations reproducing the same shrunk counterexample is strong evidence that draw orders agree.
- **Tell builders their file scope and who else is in the tree.** Shared files (README, `reference.md`, the check scripts) were edited with small replacements after re-reading; there were no lost edits.
- **Ports can run in parallel with the next JS wave** as long as that wave adds fixtures and does not change existing ones.
- **`check-progression.sh` is not enough for cross-cutting features.** Builders hand-made a temp copy of "a student who has not built this yet" each time a dormant field or platform method was added.
- **Ask builders to prove a check can fail.** Each end-to-end check was shown to go red against a deliberately broken copy.
- **Things that went slightly wrong:** one builder ran `git checkout` on a file its script had reformatted (harmless, but against the rules); two builders shared a scratch directory name and a port, and one left a server running; the first draft of the spec had three details that the build corrected (recording delivery, clicks while running, the `tsc` command).
- **A differential test against a pure model is worth more than pixel checks.** The first play script guessed from pixels and could not see text, timing or the speed-up; the Playwright suite compares draw calls frame by frame and covers all of them.