fix(desktop): restore observer-feed regressions from #1381#1412
fix(desktop): restore observer-feed regressions from #1381#1412wpfleger96 wants to merge 9 commits into
Conversation
Three regressions introduced by #1381's activity-feed rebuild: 1. Per-turn prompt context sections are now visible inline in the feed body. Previously they were hidden behind a modal accessible only via a small CheckCheck toggle inside the user-message bubble footer. Now the sections render directly below the user bubble with each section title visible and body expandable on click. A "View full" button opens the scrollable modal for focused reading. 2. Metadata items (system prompt, prompt context) now display their semantic title instead of the anonymous "Captured N raw sections" label. RawRailActivity now uses item.title as the verb, so "System prompt" and "Prompt context" render with their real names. 3. Permission rows now show the decided outcome. The permission response arrives as an acp_write event with result.outcome and the same JSON-RPC id as the request. A pendingPermissions map on TranscriptState indexes request id → lifecycle item id + option kind map. On the response the outcome is appended: Approved (allow_once) / Denied (reject_once) / Cancelled. Also restores metadata participation in isMeaningfulItem() for the Now summary bar. Raw JSON-RPC frames remain excluded (acpSource=raw_json_rpc); all semantic metadata (system prompt, prompt context) now contribute again, matching pre-#1381 behavior. System-prompt key (system-prompt:${ch}) is intentionally unchanged — the frame predates session creation and correctly reflects current channel setup by replacing on each session/new. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…lation JSON-RPC 2.0 allows both string and numeric ids. The ACP runtime preserves permission request ids as serde_json::Value for exactly this reason. The prior commit used asString(payload.id) for both the request index and response lookup in pendingPermissions, silently dropping any numeric id and leaving the outcome unattached. Add a jsonRpcId() helper that accepts string or finite-number values and keys them via JSON.stringify (preventing collisions between the number 1 and the string "1"). Use it at both the request-index site and the response-lookup site instead of asString. Regression tests: numeric id with selected allow_once, numeric id with cancelled, and a collision test verifying number 1 and string "1" attach to their respective items independently. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Inside the acp_write response branch, `responseId` is typed `string | null`. The `if (pending && outcomeKind)` guard only narrowed `pending`, not `responseId`, so `d.pendingPermissions.delete(responseId)` was rejected by tsc (argument of type 'string | null' not assignable to 'string'). Add `&& responseId` to the guard. This is semantically a no-op — `pending` can only be truthy when the earlier `responseId ?` branch took the non-null path — but TS requires the explicit narrowing. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
787713f to
ce9a03f
Compare
…sions Adds a Playwright smoke spec that seeds observer relay events directly into the production appendAgentEvent → processTranscriptEvent ingestion path via a new __BUZZ_E2E_SEED_OBSERVER_EVENTS__ e2e bridge hook, then screenshots four surfaces restored by PR #1412: - 01-prompt-context-inline: collapsed inline prompt-context block - 02-system-prompt-title: system prompt with title visible in feed - 03-permission-approved: permission row with Approved (allow_once) outcome - 04-permission-cancelled: permission row with Cancelled outcome Supporting changes: - injectObserverEventsForE2E exported from observerRelayStore.ts - __BUZZ_E2E_SEED_OBSERVER_EVENTS__ Window hook wired in e2eBridge.ts - spec added to smoke project in playwright.config.ts Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Store the resolved permission outcome on a new `outcome?` field of the lifecycle item type instead of appending it to `text` via joinLifecycleText. This separates the data model cleanly: `text` holds request detail + options; `outcome` holds the final decision. LifecycleActivity.tsx renders permission items in three visually distinct parts: 1. Request row: shield icon + title + request description 2. Options sub-line: muted indented 'Options: ...' text 3. Decision row (when resolved): divider + colored icon + outcome label - green + CheckCircle2 for 'Approved (...)' - destructive + XCircle for 'Denied (...)' - muted + XCircle for 'Cancelled' Also adds expanded E2E screenshot variants: - 05-prompt-context-expanded.png: all three accordion sections open - 06-system-prompt-expanded.png: outer <details> + inner section <details> open Unit tests updated to assert outcome is on item.outcome (not in item.text). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Adds isSpineItem() predicate — true for tools, messages, thoughts, plans, and meaningful lifecycle events; false for metadata items (system prompt, prompt context). These are 'reads' that should recede when real work is present, per VISION_ACTIVITY.md:49,61 (failures rise; reads recede; suppression is what makes signal legible). BotActivityBar now uses a two-tier headline scan: first pass collects spine-only headlines; if none found (session start / idle), falls back to all meaningful items via isMeaningfulItem so the bar isn't left empty. Feed visibility for metadata items is unaffected — AgentSessionTranscriptList renders them independently of this predicate. Also fixes the isMeaningfulItem docstring, which incorrectly claimed the function feeds a 'Now summary' (it only feeds the headline scan). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…dion RawRailActivity now branches on acpSource: raw_json_rpc items keep the existing <pre>/details raw treatment (the ambient safety net), while all other named metadata items (system prompt, steer-turn prompt context) render via the shared PromptSectionList accordion — the same polished rounded-2xl card used for per-turn prompt context. Extract PromptContextSections + PromptContextSectionAccordion from AgentSessionTranscriptList into PromptSectionAccordion.tsx and import the shared component back in both AgentSessionTranscriptList and RawRailActivity. No visual change to prompt-context; the raw-rail treatment is now reserved for genuine raw payloads only. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…x shot 06 Replace the three acpSource boolean tests in agentSessionTranscriptPresentation.test.mjs (which only asserted item.acpSource === 'raw_json_rpc' on hand-built objects — they would pass with the isRawPayload branch deleted) with proper renderToStaticMarkup render tests in RawRailActivity.render.test.mjs. The new tests render RawRailActivity for three metadata items and assert the HTML: raw_json_rpc includes <pre> and no rounded-2xl; system prompt and steer-turn prompt context include rounded-2xl and no <pre>. Fix shot 06 in observer-feed-screenshots.spec.ts: after the render unification, system-prompt sections are React button accordions inside a native ActivityRow <details>. Shot 06 now opens the outer <details> first, then clicks each inner section button to expand it — matching the interaction pattern in shot 05. Shot 06 confirmed to show both Base and System sections fully expanded in the polished card UI. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Add count assertion before the section-button click loop in shot 06 so a future testid/nesting break fails loudly instead of silently producing a collapsed screenshot. Swap the render-test accordion marker from the style token 'rounded-2xl' to 'data-testid="transcript-prompt-context-sections"' — the semantic shared-list marker emitted by PromptSectionList. Style tokens can drift without breaking behavior; the testid is stable and specific. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Observer feed screenshots — PR #1412 (rebuilt)E2E Playwright spec ( Prompt context — inline collapsed (Fix 1)Per-turn prompt context sections are visible inline in the feed, collapsed but labeled with section titles — no longer buried in a modal. Prompt context — sections expanded (Fix 1, expanded)The same inline block with all three accordion sections clicked open, showing section titles and content in the polished System prompt title restored (Fix 2)The system prompt item shows its real title ("System prompt") and section count instead of the anonymous "Captured N raw sections" label. System prompt sections expanded (Fix 2 + render unification)The system prompt item expanded, showing the "Base" and "System" sections. These now render in the same polished section-accordion card as prompt context (above) instead of the raw Permission outcome — approved (Fix 3)Three-part permission block: request row, muted options sub-line, then a visually distinct decision row with green ✓ "Approved (allow_once)". Permission outcome — cancelled (Fix 3)Same three-part block with a muted grey "Cancelled" decision row when the user dismisses without selecting. |






Three feed regressions introduced by the #1381 activity-feed rebuild, all confined to desktop UI code.
#1381 restructured the feed around a classifier registry and 12 render classes. In doing so, per-turn prompt context sections were moved behind a modal-only toggle, metadata items lost their semantic titles, permission rows never showed the outcome, and metadata was dropped from the "Now" activity-bar summary scan.
PromptUserMessagenow renders aPromptContextInlineblock directly below the user bubble. Each section title is visible and the body expands on click. A "View full" button opens the scrollable modal for focused reading. TheTurnSetupFootercontext toggle andPromptContextDialogmodal-only path are removed.RawRailActivitynow usesitem.titleas theActivityRowLabelverb instead of the hard-coded "Captured" string, so "System prompt" and "Prompt context" items render with their real names and section count rather than the anonymous "Captured N raw sections" label.agentSessionTranscript.tsadds apendingPermissionsmap toTranscriptState(JSON-RPC request id → lifecycle item id + option kind map). Whensession/request_permissionis processed the map is populated using ajsonRpcId()helper that accepts both string and finite-number ids viaJSON.stringify(preventing collisions between numeric1and string"1", and correctly handling the numeric ids the ACP runtime preserves asserde_json::Value). Anacp_writewithresult.outcomeand nomethodcorrelates by id and appends the outcome: "Approved (allow_once)" / "Denied (reject_once)" / "Cancelled". Unit tests cover string selected, string cancelled, string unmatched id, numeric selected, numeric cancelled, and the numeric/string collision case.isMeaningfulItem()restored. The blanketmetadata → falseexclusion is replaced with a targeted check:acpSource === "raw_json_rpc"items (infrastructure noise) remain excluded; all other metadata items (system prompt, prompt context) contribute to the "Now" summary again.Restores pre-#1381 behavior. Unit/lib CI covers all logic changes. Feed render surfaces (Fixes 1 and 2) are E2E-only and are called out for manual verification.
Closes #1381 regression (not the PR itself — see that PR for original context).