Skip to content

fix(desktop): clear orphaned pause-gate modals on $turn_complete#1484

Merged
esengine merged 1 commit into
mainfrom
fix/desktop-plan-card-orphan
May 21, 2026
Merged

fix(desktop): clear orphaned pause-gate modals on $turn_complete#1484
esengine merged 1 commit into
mainfrom
fix/desktop-plan-card-orphan

Conversation

@esengine

Copy link
Copy Markdown
Owner

Bug

Reported in #1456. After issuing `/plan`, the user typed follow-up guidance during the approval modal; the composer queued it (busy=true). They then clicked what looked like the send button — but the composer swaps that button for stop/abort while busy. The turn aborted, the queue drained, and the new user message visibly appeared above the still-rendered plan card.

Root cause

The desktop render order is `state.messages → state.pendingPlans` (lines ~1800/1863 in `App.tsx`). Any user message that lands in `messages` while `pendingPlans` still has entries renders above the plan card.

`$turn_complete` only cleared `busy` and `activeSkill`. So on abort:

  1. Loop unwinds, emits `$turn_complete` → busy=false (but pendingPlans stays).
  2. Drain effect at `App.tsx:1335` sends the queued user_input → `send_user` appends to `messages`.
  3. Render order = `[…, queued user msg, …, ZOMBIE plan card]`.

The error toast the user also saw is the aborted `submit_plan` call unwinding — separate path, unchanged.

Fix

`$turn_complete` now also clears `pendingConfirms`, `pendingPathAccess`, `pendingChoices`, `pendingPlans`, `pendingCheckpoints`, `pendingRevisions`. By the time the loop emits this event, any entry still in those arrays is orphaned anyway:

  • Normal completion → user resolved everything mid-turn → arrays already empty → no-op.
  • Aborted turn → model isn't coming back for the gate → clearing prevents the zombie modal.

Test plan

  • New test in `tests/desktop-btw-status.test.ts` seeds all six pending* arrays + busy=true, dispatches `$turn_complete`, verifies every array is cleared.
  • 3533 tests pass locally.

Closes #1456

Reported in #1456. Repro:

  1. /plan → agent generates plan → plan approval card renders below
     the assistant message
  2. User types follow-up guidance and presses Enter → queued (busy=true)
  3. User clicks what looks like the send button — but with busy=true the
     composer swaps it for the stop/abort button. The turn aborts.
  4. \`$turn_complete\` fires (busy=false), the queue drains, send_user
     appends the user's text to state.messages. But pendingPlans was
     never cleared, so the orphaned plan card is still rendered AFTER
     all messages. Result: the new user message visibly lands ABOVE
     the plan card, exactly the layout the issue screenshots show.

By the time the loop emits \`$turn_complete\` any modal still in a
pending* array is orphaned — the tool call that opened it has either
resolved already (so the array entry is gone) or the turn aborted
(so the model isn't coming back for it). Clear all six pending arrays
at that point.

The error toast the user also saw is from the aborted submit_plan call
unwinding through pauseGate; that path is unchanged.

Closes #1456
@esengine esengine merged commit 330a75b into main May 21, 2026
4 checks passed
@esengine esengine deleted the fix/desktop-plan-card-orphan branch May 21, 2026 11:21
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.

关于最新版0.48.0桌面版的 /plan 计划错误

1 participant