Files
functional-programming-snake/docs/superpowers/notes/2026-10-29-build-log.md
T

5.4 KiB
Raw Blame History

Build log — Pure Snake course (2026-10-28 → 2026-10-29)

Record of how the course was built, the decisions made on the owner's behalf, and what is still open. Spec: docs/superpowers/specs/2026-10-28-pure-snake-course-design.md. Plan: docs/superpowers/plans/2026-10-28-pure-snake-course.md (its "contract" section is the canonical API; changes below override it).

Owner decisions (from the conversation)

  • Audience: CS bachelor's/master's students working alone at home, solo or with an AI as Socratic mentor (MENTOR.md).
  • Everything purely functional, including UI and input: hand-built IO type and event streams; no "before"/stateful starter version.
  • Tooling: plain JS ES modules, zero deps, node --test, canvas. Plus full Scala 3 and F# tracks with their own runnable test suites (shared JSON fixtures).
  • Size unconstrained; reference solutions shipped in <track>/solutions/.
  • 2026-10-29: no Scala bridging in the text — bridges start from general programming knowledge (> **From everyday programming:**); FP basics (map/filter/reduce, Option, pattern matching) are taught in Modules 1–2. The Scala track remains as a language option.
  • Remote: origin = https://gitea.lucaswinther.info/lucasw89/functional-programming-snake (main pushed 2026-10-29).

Rulings made during execution (decision — why — cost if wrong)

  1. scripts/serve.mjs owned by Task 1 — duplicate ownership in plan — none.
  2. scripts/smoke.mjs created in Task 8 (headless Chrome via CDP, zero deps) — no task owned it — rework smoke approach.
  3. Subagent-driven execution without asking — owner's CLAUDE.md: no review stops after design approval — higher token spend. 4/16/17/19. Some implementers ran in parallel on disjoint files (F# track vs course text; Task 17 vs F# review) — wall-clock — commit races only.
  4. Scala fixture helper is scala/test/Fixtures.test.scala — scala-cli scoping — none.
  5. Typed fixture decoders added with the Scala/F# ports — none.
  6. js/test.sh; JS runs node --test "test/*.test.js" — Node 24 rejects dir arg — none.
  7. Small adjacent tasks batched into one implementer + one review — overhead — larger review surface.
  8. flatMap(Pure(a), f) evaluates f(a) eagerly at construction; explained in Module 7 — unobservable for pure f — a later laziness lesson would need a Suspend case.
  9. flatMap not stack-safe for ~100k left-nested binds; Module 7 discusses trampolining — game uses right-nested sequence — students' long flatMap loops overflow.
  10. With a fully stubbed tree, m09 tests fail at import — students reach m09 with io done — noisier output if skipping ahead.
  11. Pre-port JS polish: pongKey accepts W/S/R too (contract tweak), explicit held-key mapping, shared classicMain test helper, stronger no-mutation test.
  12. Added scripts/check-progression.sh <track>: for N=1..11, solutions for modules ≤N + stubs for the rest must pass M01..MN — caught a real "test helper imports src" bug.
  13. Scala/F# stream input type is Event (Time/KeyDown/KeyUp), not Input — name clash with the Input module.
  14. F# idioms: bind (flatMap), choose (filterMap), PascalCase record fields; IO.randomSeed (), App.main (), pongMain () are unit functions so stubs fail per test.
  15. Module 2 guiding questions may hint at the design (e.g. "why two directions?") — intended scaffolding — design exercise slightly easier.
  16. F# mulberry32 routes uint32 */+ through uint64 helpers because Fable doesn't wrap uint32 arithmetic; Module 4 warns students — no automated Fable-vs-.NET drift check.

Verification status (last full run 2026-10-29, before the docs-only Scala-bridge rewrite)

  • scripts/verify.sh js|scala|fsharp: 383 / 361 / 362 tests pass against solutions (JS re-run after the rewrite: 383 pass).
  • scripts/check-stubs.sh <track>: M01–M11 fail against stubs in all tracks.
  • scripts/check-progression.sh <track>: all checkpoints pass in all tracks.
  • scripts/smoke.mjs: all 6 pages (snake + pong × 3 tracks) paint with no console errors when built from solutions.
  • node scripts/check-links.mjs: 103 external links + all relative links OK.
  • Not done: a human playing the games end to end.

Open follow-ups (judged OK to leave by the final review)

  • cats-effect / ZIO / fs2 still mentioned as "In production" comparisons in Modules 7–9 (owner may want them cut after the no-Scala-bridging change).
  • No automated guard that the Fable (F# browser) build reproduces the RNG fixtures.
  • Module 11 hint 11.4 gives away the stream merge shape; some near-solution hint skeletons are thin.
  • check-links.mjs ignores reference-style/HTML links and bare (url); no 429 retry; soft-404s pass.
  • Purity audits exclude the whole runtime file; JS comment stripper cuts at // inside strings; F# printf ban misses eprintfn/Console.
  • Two Pong unknown-key fixture cases are vacuous in Scala/F# (unrepresentable by the types); m10 progression checkpoint adds no files for Scala/F#.
  • interval with a backwards Time re-emits; history is O(n²); deepFreeze(obj, ancestors=[]) breaks if passed directly to .map.

Environment notes

  • scala-cli installed at ~/.local/bin/scala-cli (downloads its own JVM 17); dotnet at ~/.dotnet/dotnet; Fable 5.24.0 via fsharp/.config/dotnet-tools.json; headless Chrome at /usr/bin/google-chrome.
  • First scala/build-web.sh / fsharp/build-web.sh run needs network (Scala.js linker / Fable restore). scripts/serve.mjs binds 127.0.0.1:8000; run from the repo root.