fix(dashboard): tool cards no longer stuck spinning forever#1676
Merged
Conversation
`loopEventToDashboard` was generating dashboard event ids as
`${assistantId}-${role}-${Date.now()}` for both tool_start and tool
events. Different role string and different timestamp → completely
different ids. The SSE bridge passes that id through as the segment
callId, and the dashboard reducer keys segments by it — so tool.result
never matched the segment created by tool.intent, segment.result
stayed undefined, and the card sat in `running` state forever
(its only "done" trigger is `result !== undefined`).
LoopEvent already carries a stable `callId` for this exact purpose
(see `src/loop/types.ts:40-41`: "UI uses this as the card id"). Use
it for both tool_start and tool so they share an id and the reducer
can pair them.
Regression-tested in `tests/loop-to-dashboard.test.ts`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
reasonix code+ browser dashboard, every tool card's spinner stayed visible forever even after the underlying tool completed (or was aborted). The card showedrunningindefinitely until session-reload, even though the loop had long since produced the result.src/cli/ui/effects/loop-to-dashboard.ts:8minted dashboard event ids as`${assistantId}-${role}-${Date.now()}`for every event.tool_startandtoolcarry different role strings and fire at different timestamps → completely different ids. The SSE bridge (dashboard/src/lib/tauri-bridge.ts) passes that id through as the segment callId, and the dashboard reducer keys segments by it (dashboard/src/App.tsx:931-951) — sotool.resultnever matched the segment created bytool.intent,segment.resultstayedundefined, andToolCard/ShellCard'srunning = result === undefined(dashboard/src/ui/cards.tsx:345,thread.tsx:137-141) kept the spinner spinning forever.LoopEvent.callIdalready exists for exactly this purpose —src/loop/types.ts:40-41says"UI uses this as the card id"— and bothtool_start(loop.ts:1024) andtool(loop.ts:1078) emit it fromthis.inflightIdFor(call). Useev.callId ?? idinloopEventToDashboardfor both events so they share a stable id and the dashboard reducer can pair them.How it was found
SSE event log captured via temporary
?debug-sseinstrumentation showed the full event sequence flowing correctly (busy-change{busy:true}→ deltas →assistant_final→tool_start→ user abort →tool(with kill output) →assistant_final(synthetic) →busy-change{busy:false}), with$turn_completecorrectly synthesized on the finalbusy-change. Sostate.busywas actually flipping false on the dashboard (confirmed by thejobs_listpoll firing right after, which is gated on!state.busy). The visible "spinner" was the per-card spinner, not the composer's stop/send affordance — pointing at the segment-update path.Test plan
npx vitest run tests/loop-to-dashboard.test.ts— 8 passed (added a regression case asserting tool_start + tool share the loop's callId)npx tsc --noEmitreasonix code→ browser dashboard → ask AI to do anything that uses a tool → confirm card transitions running → done within a few hundred ms of the tool finishing (and not only on session reload)