Skip to content

fix(ui): reasoning spinner survived reasoning.end when a later card existed#358

Merged
esengine merged 1 commit intomainfrom
fix-reasoning-spinner-stuck
May 7, 2026
Merged

fix(ui): reasoning spinner survived reasoning.end when a later card existed#358
esengine merged 1 commit intomainfrom
fix-reasoning-spinner-stuck

Conversation

@esengine
Copy link
Copy Markdown
Owner

@esengine esengine commented May 7, 2026

Summary

Reported by user: after a card finishes running, the reasoning… ⠋ header still shows the spinner — looks like a stuck card.

Root cause: splitCardStream only treated the last card as live. Everything before it went into <Static>. When the model streamed reasoning then content (or kicked off a tool card), the reasoning card was no longer last — so it was frozen into <Static> while still streaming: true. reasoning.end later set streaming: false on the cards array, but <Static> doesn't re-render frozen items, so the spinner stayed forever.

Fix

splitCardStream now scans for the first unsettled card and keeps everything from that index onward in the live tree. A card only commits to <Static> once it's settled AND every earlier card is too — preserving the in-order append contract <Static> requires.

suppressLive semantic preserved: the trailing live card is still hidden when a modal owns the screen.

Test plan

  • tests/card-stream.test.ts — two new cases: reasoning + streaming both stay live mid-stream, then both commit once settled
  • npx vitest run — 165/165 test files, 2425 tests pass

…xisted

`splitCardStream` only treated the LAST card as live; everything before
it was committed to Ink's `<Static>`. When the model streamed reasoning
THEN content (or kicked off a tool card), the reasoning card was
no longer last — so it got frozen into `<Static>` while still
`streaming: true`. `reasoning.end` later set `streaming: false` on
the cards array, but `<Static>` doesn't re-render frozen items, so the
"reasoning…" header kept its spinner indefinitely.

Fix: live-vs-committed split now scans for the FIRST unsettled card
and keeps everything from there onward in the live tree. Cards only
move into `<Static>` once they're settled AND every earlier card is
too — preserving the in-order append contract `<Static>` requires.

`suppressLive` semantic preserved: still hides the trailing live card
when the modal owns the screen.

Two new regression tests cover the reasoning + streaming case (must
stay live) and the all-settled case (must commit in order).
@esengine esengine merged commit ccdbfb3 into main May 7, 2026
3 checks passed
@esengine esengine deleted the fix-reasoning-spinner-stuck branch May 7, 2026 05:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant