feat: a practice drill a board can define — audio and Q&A - #323
Open
ivanmkc wants to merge 1 commit into
Open
Conversation
…d-answer The use case: a daily language practice pushed to the board, with a button to play the audio and question-and-answer built in. The interesting part is not the drill, it is that a board defines it. A board is data and cannot carry a click handler -- letting the agent author behaviour is a different product. So the split is: the CANVAS owns the widget, the BOARD carries its content, and the HOST decides what an interaction means. Two new components (Audio, Practice) plus a widget event channel, and a board that drills Spanish and a board that drills times tables are the same component with different data, neither able to do anything the app cannot already do. Audio reads a card aloud with the browser's own speech synthesis rather than a hosted voice: no key, no network, no per-card cost, and it knows far more languages than we could ship recordings for. A recording is used when one is given. Neither available means a disabled button that says why -- not a button that silently does nothing. Scheduling is code. The model writes what a card says; when it comes back is computed from the answer the person actually gave -- wrong returns the same day, easy waits longer each time. A model asked to schedule revision produces confident, arbitrary dates, and dates that mean nothing are the whole failure mode of spaced repetition. The board is derived from what is due, so it costs nothing to open, differs tomorrow because the dates moved rather than because someone asked, and works on a plane. Reviews come before new cards, and a session is capped -- a hundred cards is not a daily practice. The widget owns the session it is in the middle of: grading does not redraw the board underneath someone mid-card. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The stack
localStoragefor the test suite on Node 25@ivanmkc/termchart-canvas; injectable interact transportEvery branch in the stack was checked out on its own and verified independently:
npm install,tsc --noEmitfor every package,npm testacross the workspace, and the offline e2e where it exists. All twenty are green — no branch depends on a later one to build or pass. Full viewer e2e (11 suites, 106 assertions) was run on #312 as the behaviour-preservation evidence for the extraction.Stack position: 14 of 14 — the top of the stack. Base:
lifeboard/12-integrations.The drill is the easy part. The interesting question is how a board defines an app at all, given that a board is data and cannot carry a click handler — letting the agent author behaviour is a different product, and a more dangerous one.
The split
AudioandPractice, real code in the registry, reviewed like anything else. A board names one; a board cannot define one. Thesatisfies Record<KnownComponent, …>clause makes registering in one place and not the other a compile error.src/practice/schedule.ts.A board that drills Spanish and a board that drills times tables are the same component with different data, and neither can do anything the app could not already do. To add another mini-app: write the component, register it, handle its events in the shell, ship a pack that teaches the model what content to write.
Two decisions worth arguing with
Audio uses the browser's own speech synthesis, not a hosted voice. No key, no network, no per-card cost, and it knows far more languages than we could ship recordings for. A
srcrecording is used when one is given. Neither available means a disabled button that says why — not a button that silently does nothing.Scheduling is code, for the same reason quantities are. The model writes what a card says; when it comes back is computed from the answer actually given — wrong returns the same day, easy waits longer each time. A model asked to schedule revision produces confident, arbitrary dates, and dates that mean nothing are the entire failure mode of spaced repetition. The pack prompt says so explicitly: "Do NOT set
due,ease,interval."The rest
The board is derived from what is due, so it costs nothing to open, differs tomorrow because the dates moved rather than because anyone asked, and works on a plane. Reviews come before new cards, and a session is capped — a hundred cards is not a daily practice. The widget owns the session it is in the middle of, so grading does not redraw the board underneath someone mid-card.
Verified: canvas 485 (+12: registration, the disabled-speech path, reveal/grade/choice flows, the done tally, restart-on-new-deck), lifeboard 372 (+22: the scheduler's bounds and determinism, what is due, the board's empty and not-due states, pack selection, and grading through the shell's real wiring), offline e2e 19/19 including the practice board. All packages green, typechecks clean.
🤖 Generated with Claude Code