fix(web,desktop): the Call tab stands a call's first ask under its turn, folds a spoken turn's words and footer with its work, and a goodbye's turn settles rather than fails
Summary
Dean's 3:38 PM call showed three faults on the Call tab, each traced to its cause against the store's own rows for that call (turns f1103ef1 and 98d6730e, conversation 57cb2809).
- Order. The brain turn that answered Dean's first ask sank to the end of the call. Every call opens with the explore ask (
voice/live-brain.ts'sexplore), keyed by the call's session and naming no line, and the brain opens a turn for it at once; the developer's first ask is then steered into that running turn, so its ask row is newer than the turn's start. #2417's rule inspokenAskRowsOfdropped every ask made after its turn started, meaning the follow-ups a parked turn takes, and so dropped the first ask of every call too: the turn named no line, found no anchor, and stood by its start time after the call.noSteeredAsknow counts an ask as a follow-up only where the turn already had a delegation (an ask naming a voice session) before it. The explore ask is no delegation, so the first ask steered into its turn is the turn's ask. - Brain text. An unanchored turn is drawn as unspoken, so its written words showed as a message. With the turn anchored, today's code dropped its words whole. Now a spoken turn's words run with its calls (
turn-rows.ts): inside the open group while it moves, inside the fold once it has finished, with its "Worked for" line (TranscriptRows'strailer), the fold's line counting calls, subagents and coding agents and no message, and no Copy under the message. A spoken turn with nothing but words draws nothing, as before. The end-call turn is a brain turn under the ask that asked for it, so it folds the same way: one line, "1 tool call". - "Failed". The end-call turn's row was
failed/model, "The model did not return a response". The tool's words tell the model to call it first and add nothing; eve then asked the model for a step after the call, got an empty reply, reissued the call once with a nudge, got empty again, and failed the turn. The hang-up was never involved.END_CALL_TOOLnow declares eve'sendsTurn, carried throughHostedToolDeclarationto thedefineToolineve/tools/brain.ts, so the turn settles on the call. The tool's description is unchanged, so no golden moves.
Tests, each failing without its change: plan-work.test.ts plays the explore turn, the steered first ask and a later steered follow-up through the relay on PGlite and reads back the turn's asked lines; turn-rows.test.ts reads a spoken turn's rows moving and finished; plan-call.test.ts mounts the sequence and reads the fold's line, its opened body, and its place between the ask and Luke's goodbye; hosted-brain-host-tools.test.ts reads which planning tool is declared to end its turn.
Evidence
- Platform-independent checks:
./scripts/check.shexit 0 on this Linux workspace. - macOS Electron verification (
./scripts/verify.sh):not run. This is a Linux cloud workspace, so CI could not verify the Mac UI andverify.shwas not run; LUKE-159 is the manual Mac pass.
Visual evidence
The Call tab rendered in headless Chrome from a throwaway page over the renderer's own PlanCall and stylesheet, fed Dean's sequence (synthetic words), before on main's renderer with the data the service sent that day, after on this branch with the data it now sends.
| Dark | Light | |
|---|---|---|
| Before | ![]() | ![]() |
| After | ![]() | ![]() |
| After, folds opened | ![]() | ![]() |
- Fixture screenshots inspected:
not attached(no Mac).
Physical-device evidence
- Screenshot or screen recording:
not attached - Device/display configuration:
not recorded
Invariants
-
./scripts/check.shgreen; renderer/UI PRs also./scripts/verify.shwith evidence inspected. (CI) āverify.shnot run, see above. - JSON Schema goldens unchanged (
LUKE_UPDATE_FIXTURESnot run, or diff explained line by line). (CI) - Every behavior change has, in this PR, the test that fails without it; no change-detector test added. (manual; root
AGENTS.md"Testing") - Gateway envelope goldens unchanged. (CI)
- No new
astype assertion outside the allowlisted files. (CI via anti-slop) - No
Effect.runPromise/runSync/runForkoutside the runtime edges and the shimstools/oxlint/anti-slop/effect-edges.jsonrecords. (CI viaanti-slop/no-run-promise-outside-edges) - No new
setTimeout/setInterval/new Promise/AbortController/fs.watchoutside the filestools/oxlint/anti-slop/effect-edges.jsonrecords. (CI viaanti-slop/no-raw-async-primitives) - No Effect, Stream, or Layer written as a statement and dropped; a description a caller walks past runs nothing. (CI via
pnpm discarded-effect) - Test count equals the pre-PR count or the delta is listed. (manual:
vitest run --reporter=json) ā four tests added, none removed. - Each hand-rolled file the PR replaces is deleted, or the part that remains is a permanent adaptor named in root
AGENTS.md's "The permanent adaptors" with the reason it stays. ā nothing replaced. - Every
CLAUDE.md/AGENTS.mdsentence naming a changed part is edited; root pair stays byte-identical. (CI for pair existence; manual for wording) ā none names one. -
PRIVACY.mdunchanged, or the change is a product decision called out in the PR body. - Package
package.jsondeps match what the sources import by bare specifier;effectviacatalog:. (CI) - Barrel/door rule: no Node-reaching Effect module (
@effect/platform-node,effect/unstable/sql) behind a barrel the renderer or a web function opens. (CI: existing renderernode:grep plus a new grep for those specifiers in renderer bundles)
Notes
- Blockers or follow-up verification: the end-call fix is at the eve boundary (
endsTurn), which no hermetic test can run end to end; the next live call with a goodbye is the check that its turn settles rather than fails.
š¤ Generated with Claude Code





