Skip to content

fix(tui): keep completion footer mounted on finish=tool-calls - #2679

Merged
yanyihan-xiaomi merged 6 commits into
mainfrom
fix/tui-footer-sticky-last-line
Oct 19, 2026
Merged

yanyihan-xiaomi merged 6 commits into
mainfrom
fix/tui-footer-sticky-last-line

Conversation

@yanyihan-xiaomi

Copy link
Copy Markdown
Collaborator

Summary

  • The session transcript's completion footer (▣ Mode · model · duration) is the scroll area's last row and must stay visible as a stable last line. It was being unmounted for a frame (and for the whole tool window) whenever the last assistant message's finish became "tool-calls".
  • Root cause: showFooter gated on props.last && finish !== "tool-calls". Writing finish = "tool-calls" at finish-step (actor tools write it even before the tool part flips to completed) made the <Match when={showFooter()}> drop the row until the next assistant message mounted.
  • That gate only existed to suppress a trailing Orchestrator tool-calls row after a stop row. Orchestrator is deleted, so the gate is gone. The live last row now stays mounted, including mid-loop finish="tool-calls" steps. Duration still renders only when finish is set and ≠ "tool-calls".

Changes

  • packages/cli/src/cli/cmd/tui/util/assistant-footer.ts — pure shouldShowAssistantFooter predicate (recovering || aborted || isFinal || isLast)
  • packages/cli/src/cli/cmd/tui/routes/session/index.tsx — wire AssistantMessage.showFooter through the predicate
  • packages/cli/test/cli/tui/assistant-footer.test.ts — 7 regression cases, including "keeps the last row mounted after finish=tool-calls"

Sticky-scroll / follow-bottom re-snap is intentionally out of scope for this PR.

Verification

  • bun run typecheck (packages/cli): PASS
  • bun test ./test/cli/tui/ ./test/cli/cmd/tui/ (packages/cli): PASS — 362 tests, 0 fail

The scrollable transcript's last row (▣ Mode · model) flickered away for a
frame when a toolcall flipped to completed: mid-loop finish="tool-calls"
unmounted the footer, and height growth could leave sticky bottom one row
short. Keep the live last row mounted and re-snap after layout in follow mode.
A global id-order scan treated every later mid-loop tool-calls step as
trailing after turn 1 and re-hid the live last footer on turn 2+. Key the
prior-final check on parentID and count aborted rows as owning the turn
footer.
Drop the sticky-follow half of this change and the Orchestrator-era
trailing-row suppression. The footer is the transcript's last row and must
stay mounted whenever the message is last — including mid-loop finish=
"tool-calls" steps. Orchestrator is gone, so that gate has no remaining
purpose and only caused the one-frame hole.
Extract isFinalAssistantFinish so the footer predicate and the duration gate
agree on what a settled turn step is. Rename the length/cancelled test to say
"settled non-tool-calls finish" — length is final under that rule — and point
it at the non-last path it actually exercises. Refresh the spec commit range.
@yanyihan-xiaomi
yanyihan-xiaomi merged commit 96c66a0 into main Oct 19, 2026
6 checks passed
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