Presenter side: presenter tab, prompter loop, desk UI, e2e + eval harnesses #5
Loading…
Reference in a new issue
No description provided.
Delete branch "presenter-side"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Implements steps 1–2 of ADR 0001 (the ADR convention itself is introduced here, with ADR 0002 as a draft):
window.open+ a React portal over the same in-memory stores (no BroadcastChannel). Stylesheets mirror into the child document;useBoxSizeobserves resize from the element's own realm so rough strokes regenerate on window resize. The surface is letterboxed to a fixed aspect with hidden overflow, assuming the draft slide-viewport direction.agent/intoloop/; the deck author becomes a thin composition over it, andsession.tsfans committed transcript chunks out to a consumer list. Behavior-preserving (all prior tests unchanged).presentations.script:next_line({block_id})points into stable script block ids (the pointer-not-paragraph hot path),next_line({text, resume_at})composes answers/bridges with a rejoin block,advise()for meta guidance. Events land asprompter.called/prompter.turnwithaudioAt; console state is a pure fold. Per-role LLM connections (resolveConnection('author'|'prompter'), unset fields inherit).task e2e) — tests own the browser: fresh profile, seeded pairing + stub LLM, the real CLI bridge (@lazysketch/cli/bridge), production build. Three specs cover the scripted pipeline, prompter-mode runs, and the desk flow.task eval:prompter) — script + annotated paraphrase-delivery fixture scored on pointer trajectory; first Haiku pass landed 8/8 (~0.9s median turn) after adding the composed-line rule.Bugs found and fixed by the harnesses: event seqs are now allocated atomically in the db adapter (two session instances used to mint colliding seqs that log consumers silently dropped), and a run-watcher early-finish (an ending marker is void once a later
started/resumedfollows).Test plan
task check/task lint:eslint/task test(203 unit tests) greentask e2e— 3 Playwright specs against the production build, stub LLMtask eval:prompterfixture 8/8 on claude-haiku-4-5 through a paired tab; presenter tab and prompter console verified in a real browser sessionCommits are unsigned (1Password was locked during autonomous work — per instruction).
ADR 0001 plumbing: presentations gain script (the prepared talk the prompter reads) and prompterInstruction; sessions gain prompterEnabled; prompter.called/prompter.turn join the event vocabulary. All optional — no Dexie bump, transfer format stays v1, script.md lands at the CLI workspace root. scriptBlocks() gives the prompter its address space: stable ordinal ids over splitScript. Settings gain the per-role connection seam — resolveConnection('author'|'prompter'), full connection overridable, unset fields inherit — and the author now resolves through it. session.ts splits its author wiring into session.agent.ts and log plumbing into session.transcript.ts (the prompter wiring will mirror the former).ADR 0001 step 2 core. prompter/ is the loop module's second instance: next_line({block_id}) points into the script's stable block ids — the pointer-not-paragraph hot path — or composes a line for off-script moments (answer proposals, bridges back) carrying its rejoin block; advise() is guidance that isn't words to say. The whole script rides the cached system prompt; turns carry the delta, the cursor, and measured time only (elapsed now, slot with step 3) — timing prose in script/prompterInstruction is reasoned over, never parsed. Actions land as prompter.called/prompter.turn events (audioAt attached) and fold into the console state — live and resume are one fold (session.prompter.ts, sibling of session.agent.ts). The stage console renders the teleprompter margin: exact script text, the pointed block circled in pen, composed lines on a highlighter smear, advice under the header — and the tray carries one lag gauge per loop, since the loops run on individual clocks. The prompter resolves its own LLM connection (per-role seam). Prompter mode rides sessions.prompterEnabled: `lazysketch run --prompter` over a new optional protocol field; the desk toggle is still to come. Covered by unit tests (tools, loop cursor, state fold) and a second Playwright spec driving both loops through one stub end to end. stage-view splits into .surfaces/.tray support files.The talk script gets its own section on the desk (collapsed to an invitation when empty — "unlocks the prompter" — or a one-line summary once written): an on-sheet hand-font editor that saves on blur and shows the prompter's address space live while writing ("3 prompter lines (b1–b3)"), with the prompter instruction beside it. The GO LIVE pull gains a drawn Prompter checkbox when a script exists; rehearsals prefill from the talk script and can run "with prompter" — the fastest in-browser evaluation loop. Settings exposes the prompter model override (blank = inherit). The teleprompter's pointed line bumps to glanceable size. Covered by a third Playwright spec driving the whole flow through the UI: write script on desk → split note → toggle appears → rehearse with prompter → stage console shows the script with the cursor.task eval:prompter drives the exact browser prompter loop from Node (createPrompter -> streamChat, .env credentials — the sanctioned live path): a script plus a DELIVERY transcript (paraphrases, a digression, an audience question, a skip-ahead), with expectations annotated per chunk ({expect:b4}, {expect:composed->b5|b6}, {quiet}) and a scored pointer trajectory. Sample fixture pair included. Live findings (claude-haiku-4-5 via the paired browser tab, realtime pace): pointer tracking, digression discipline, and the skip-ahead all held from the start; the audience answer consumed upcoming script content and resumed past it — defensible, now codified: composed lines stay to 1-2 spoken sentences and resume_at names the first block the answer did NOT cover. 8/8 after the rule, median turn 896ms. Also fixes a real run-watcher bug the live run exposed: an early 'stopped' marker (dev StrictMode double-mount) ended runs after one chunk — an ending marker now only counts while no later started/resumed marker follows it.The contract inverts, motivated by the four-pillars story the feature exists for: with points delivered out of order, a pointer back into the script points at a plan that is already dead — recovery is a rewrite from here, which is exactly next-token prediction. The model's streamed reply IS the next thing to say: the script reproduced while the talk tracks it, the live rewrite when it doesn't (spent points dropped, unspent salvaged), an answer when the audience asks — and it works identically when the script is bare talking points. Take a breath; the line is already on the console. Plumbing: streamChat exposes content deltas; the loop passes text through with turn attribution and stamps causedByAudioAt on onTurn; the prompter accumulates the stream (console draft, pen blue — the active edge) and finalizes it as a prompter.called {kind:'line'} event. Tools become the cold path: mark_position (the block the line delivers — resurrecting exactly what 0001 folded away) and advise. The Say card is the console hero; pre-0003 composed payloads still fold. Live-tuned on Haiku (task eval:prompter, headless via .env): 7/7 and 5/7 across runs at ~1.4s median; residual echo-on-near-verbatim noted in the ADR, plus the negative result that over-instruction ("decide silently first" + inline example) regresses hard on floor models.Validated against the real recording of the talk that motivated ADR 0003 (whisper small.en, first 20 min pre-round-table, 159 STT-shaped chunks vs the 67-block script). The lines were right from the first run — instant mid-talk localization, correct material under audience questions, correct tracking through a 30-block drift — but coaching leaked into the Say line at pressure moments ("you're doing great", "I can see you're having a technical hiccup"), which is unreadable verbatim mid-stream. Three prompt changes, each re-validated on the full recording: the identity sentence itself now says the reply is read verbatim into the microphone; a rule bans addressing the presenter (coaching belongs in advise); and the speech-topology rule — the prompter is never spoken to, transcript speech is aimed at the audience or the slide-drawing agent ("Atlas, put these up" is part of the talk, not a message). Coach-voice: systematic at pressure moments -> 2 of 159, both on direct-address bait (a literal "can you hear me?" mic check). The eval now counts unsayable-smelling lines per run, and the sample fixture gains an Atlas-aside chunk (8/8 with it).