Skip to content

feat: a practice drill a board can define — audio and Q&A - #323

Open
ivanmkc wants to merge 1 commit into
lifeboard/12-integrationsfrom
lifeboard/13-practice
Open

feat: a practice drill a board can define — audio and Q&A#323
ivanmkc wants to merge 1 commit into
lifeboard/12-integrationsfrom
lifeboard/13-practice

Conversation

@ivanmkc

@ivanmkc ivanmkc commented Sep 2, 2026

Copy link
Copy Markdown
Owner

The stack

# PR What it adds Tests at this point
1 #311 A real localStorage for the test suite on Node 25 viewer 747
2 #312 @ivanmkc/termchart-canvas; injectable interact transport canvas 473 · viewer 278
3 #313 lifeboard PWA: fact log, boards, service worker lifeboard 36
4 #314 Shell, family profiles, tap layer lifeboard 82
5 #315 The agent turn, four providers, Connections lifeboard 142
6 #316 Packs, recipes, shopping-list arithmetic lifeboard 231
7 #317 Provenance and lineage lifeboard 257
8 #318 Calendar, Gmail, mail triage lifeboard 301
9 #319 The bridge and long jobs cli 314 · lifeboard 320
10 #310 Proactivity, scheduled boards, Google sign-in cli 320 · lifeboard 343
11 #320 Persistence audit fixes: multi-tab, stale builds, no-storage boot lifeboard 350
12 #321 User guide, bridge setup, QA guide, persistence audit lifeboard 350
13 #322 A plan for third-party integrations (docs only) unchanged
14 #323 A practice drill a board can define: audio + Q&A canvas 485 · lifeboard 372
15 #325 Export and restore lifeboard 389
16 #326 Draft a message, approve it, then send lifeboard 399
17 #327 Hand long work to the Mac lifeboard 406
18 #328 A Packs screen, and authoring one by describing it lifeboard 418
19 #329 Activity — what changed, who changed it, undo lifeboard 428
20 #330 Four defects found reviewing the stack cli 323 · lifeboard 431

Every branch in the stack was checked out on its own and verified independently: npm install, tsc --noEmit for every package, npm test across 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.

"Someone wants to create a daily language practice pushed to the board. Needs a button to play the audio and built-in question and answer UI. The sort of custom app definition within the board needs to be supported."

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

  the board says          the app owns              the app decides
 ┌──────────────────┐   ┌──────────────────┐     ┌────────────────────┐
 │ { type:          │   │ <Practice/> —    │     │ setWidgetTransport │
 │   "Practice",    │──▶│ reveal, choices, │────▶│ grade -> review()  │──▶ the log
 │   cards: [...] } │   │ progress, audio  │     │ -> a new due date  │
 └──────────────────┘   └──────────────────┘     └────────────────────┘
  1. Components in the canvasAudio and Practice, real code in the registry, reviewed like anything else. A board names one; a board cannot define one. The satisfies Record<KnownComponent, …> clause makes registering in one place and not the other a compile error.
  2. A widget event channel — the widget says "this happened to me"; the host decides what it means. The existing interact channel is one boolean for one checkbox, which is right for a checkbox and wrong for everything else. With nothing listening the drill still works and says it is not saving, rather than pretending.
  3. Deterministic logic in the hostsrc/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 src 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, 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

…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant