From 353ff4d18b48526ed1b5385e6e6b0d3eda5bbed4 Mon Sep 17 00:00:00 2001 From: Patrick Buckley Date: Tue, 28 Apr 2026 23:01:18 -0700 Subject: [PATCH] feat(coord): inline tool-batch construct replaces approval dock (#447) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(coord): inline tool-batch construct replaces approval dock The pinned bottom approval-dock didn't scale: a 10-call spawn_workstream fan-out filled the whole pane with a wall of repeated verdict chips, and the call → approval → result lifecycle was split across three disconnected surfaces (.msg.tool bubble + dock + .msg.tool result). Replaces it with one chat-stream construct per dispatch turn that pairs each tool call with its result and embeds the approval gate: - .coord-tool-batch--solo single-call serial turn - .coord-tool-batch--parallel ≥2 calls; rows share a left rail + per-row tick so they read as siblings of one assistant decision Lifecycle: rows render with optional "judge evaluating…" placeholder, upgrade in place when intent_verdict arrives, and on tool_result the output lands paired under the originating row. When the batch needs approval, one Approve/Deny/Always action row renders inside the construct (envelope-level — server semantics resolve siblings together). After approval_resolved the action row morphs into a ✓ approved / ✗ denied status pill that stays as a receipt. Critical bug closed: when a page reload races a pending approval, pre-scan tool_call_ids in history; turns whose call_ids have no matching tool result are rendered pending (not resolved-approved). The SSE approve_request replay then upgrades the existing batch in place — drops --approved/--denied, adds --pending, swaps the status pill for actions, and assigns activeBatch. Without this the operator was locked out of any approval pending at reload. Defence-in-depth follow-ups from the same review: - approval_resolved falls back to a DOM lookup if activeBatch is null (cross-tab resolution where this tab never set it). - _appendVerdictLineTo dedupes via a row.dataset.verdictSig so SSE reconnect storms + repeat intent_verdict events don't tear down + rebuild an unchanged verdict line. - judgeVerdicts Map soft-capped at 500 entries (FIFO eviction) via _cacheJudgeVerdict. - toolRows entries hold {batch, row} only — the originating item payload is no longer pinned for the page lifetime. - _scheduleScroll coalesces messagesEl.scrollTop writes through requestAnimationFrame so history replay doesn't reflow once per appended message. - Rationale
now inserts immediately after the verdict line (was tail-appending, breaking ordering once a result landed below). - .coord-tool-batch--error wired: _appendResultToRow lifts a row's error onto the enclosing batch; _renderBatchRow does the same for policy-blocked rows at construction. - _buildStatusPill extracted; both _morphBatchResolved and the appendToolBatch resolved-replay branch route through it. Removed: ~248 lines of dead .approval-dock CSS, the dock