Skip to content

fix(tui): force repaint final chat events#87348

Closed
MonkeyLeeT wants to merge 1 commit into
openclaw:mainfrom
MonkeyLeeT:codex/86871-tui-force-render
Closed

fix(tui): force repaint final chat events#87348
MonkeyLeeT wants to merge 1 commit into
openclaw:mainfrom
MonkeyLeeT:codex/86871-tui-force-render

Conversation

@MonkeyLeeT

@MonkeyLeeT MonkeyLeeT commented May 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Force a full TUI repaint for handleChatEvent final-event early returns that otherwise only mutate state, drop assistant output, or add command/system text.
  • Cover the three reported branches with focused tui-event-handlers regression assertions.

Fixes #86871.

Root Cause

@earendil-works/pi-tui exposes requestRender(force?: boolean), and passing true resets the previous-line diff state before scheduling a repaint. The TUI already uses that forced path in other full-refresh cases, but the three final-event early returns in src/tui/tui-event-handlers.ts only called requestRender() and could leave the terminal unchanged until another action forced a repaint.

Verification

  • pnpm install --frozen-lockfile
  • node scripts/run-vitest.mjs src/tui/tui-event-handlers.test.ts
  • node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.ts
  • OPENCLAW_TUI_PTY_INCLUDE_LOCAL=1 node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.ts
  • OPENCLAW_TUI_PTY_INCLUDE_LOCAL=1 OPENCLAW_TUI_PTY_MIRROR_PATH=<tmpfile> node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.ts src/tui/tui-pty-local.e2e.test.ts
  • Temporary command-final PTY proof script using the real runTui() loop and a PTY-backed fixture backend
  • git diff --check

Real behavior proof

Behavior addressed: TUI final chat events that finish through the local BTW empty-final path, the no-message final path, or the command-message final path now request a forced repaint with tui.requestRender(true). This directly addresses the reported symptom where final assistant/system output could remain off-screen until another action, such as toggling /verbose, forced a full redraw.

Real environment tested: local macOS source worktree with repo dependencies installed from the lockfile. I exercised the changed code in three layers: focused handler regression coverage, the scoped fake-backend PTY harness, and a real tui --local process under a pseudo-terminal with only the model HTTP endpoint mocked. I also ran a temporary PTY proof that starts the real runTui() loop and emits a command: true final chat event, which is one of the patched early-return branches.

Exact steps or command run after this patch: pnpm install --frozen-lockfile; node scripts/run-vitest.mjs src/tui/tui-event-handlers.test.ts; node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.ts; OPENCLAW_TUI_PTY_INCLUDE_LOCAL=1 node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.ts; OPENCLAW_TUI_PTY_INCLUDE_LOCAL=1 OPENCLAW_TUI_PTY_MIRROR_PATH=<tmpfile> node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.ts src/tui/tui-pty-local.e2e.test.ts; temporary command-final PTY proof script; git diff --check.

Evidence after fix: the focused handler suite passed with 57 tests, including assertions that all three #86871 early-return branches call requestRender(true). The fake-backend TUI PTY harness passed with 13 tests. The local-backend TUI PTY lane passed with 14 tests, and the mirrored tui --local terminal output showed the final model response visible in the terminal after submitting one prompt, with no /verbose command sent. The temporary command-final PTY proof showed VISIBLE_COMMAND_FINAL_PROOF rendered on the terminal after a command: true final chat event, also with no /verbose command sent. The installed @earendil-works/pi-tui contract was checked: dist/tui.d.ts declares requestRender(force?: boolean), and dist/tui.js resets previousLines, previous dimensions, cursor rows, and viewport state when force is true.

Copied redacted terminal proof from the real tui --local PTY run, ANSI stripped:

local ready | idle
agent main | session main | tui-pty-mock/gpt-5.5 | tokens ?/128k
send the local PTY smoke response
running - 0s | local ready
LOCAL_PTY_RESPONSE
local ready | idle
agent main | session main | tui-pty-mock/gpt-5.5 | tokens ?/128k

Copied redacted terminal proof from the command-final PTY run, ANSI stripped. This proof hits the patched isCommandMessage(evt.message) early-return branch:

openclaw tui command final proof - pty-proof://command-final - agent main (Main) - session main
local ready | idle
agent main (Main) | session main | proof-provider/proof-provider/proof-model | tokens ?/128
trigger command final proof
running - 0s | local ready
trigger command final proof
VISIBLE_COMMAND_FINAL_PROOF
local ready | idle
agent main (Main) | session main | proof-provider/proof-provider/proof-model | tokens ?/128

Observed result after fix: the final output was visible in the terminal immediately after the final event in both PTY captures, and the command-final proof confirmed no /verbose command appeared in the transcript.

What was not tested: a real external model provider was not used. The local PTY smoke mocks only the model HTTP endpoint, and the command-final proof uses a fixture backend to emit the specific patched final-event shape. Gateway network transport, real provider behavior, session persistence, and live streaming were not exercised by this proof.

@openclaw-barnacle openclaw-barnacle Bot added size: XS triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels May 27, 2026
@clawsweeper

clawsweeper Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed May 27, 2026, 1:52 PM ET / 17:52 UTC.

Summary
The PR changes TUI final-chat early returns to call requestRender(true) and adds regression assertions for local BTW, no-message, and command final events.

PR surface: Source 0, Tests +22. Total +22 across 2 files.

Reproducibility: Do we have a high-confidence way to reproduce the issue? Yes at source level: current main has three final-event early returns that change visible TUI state and only request an unforced repaint, and the pi-tui contract shows requestRender(true) is the forced redraw path.

Review metrics: none identified.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Risk before merge

  • The reviewed head still had pending/unstable GitHub checks, so maintainers should wait for required CI before merge.

Maintainer options:

  1. Decide the mitigation before merge
    Land the focused forced-repaint fix after required CI completes, keeping the proof scoped to TUI repaint behavior rather than Gateway, provider, session persistence, or live streaming coverage.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge
No ClawSweeper repair lane is needed; the remaining work is ordinary maintainer review and required CI completion.

Security
Cleared: Cleared: the diff touches only TUI handler code and tests, with no dependency, workflow, secret, install script, package resolution, or supply-chain surface changes.

Review details

Best possible solution:

Land the focused forced-repaint fix after required CI completes, keeping the proof scoped to TUI repaint behavior rather than Gateway, provider, session persistence, or live streaming coverage.

Do we have a high-confidence way to reproduce the issue?

Do we have a high-confidence way to reproduce the issue? Yes at source level: current main has three final-event early returns that change visible TUI state and only request an unforced repaint, and the pi-tui contract shows requestRender(true) is the forced redraw path.

Is this the best way to solve the issue?

Is this the best way to solve the issue? Yes, the PR is the narrowest maintainable fix because it changes only the affected early returns and adds focused regression assertions without altering unrelated render paths.

AGENTS.md: found and applied where relevant.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 62550710bfec.

Label changes

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. Sufficient: the updated PR body/comment include redacted after-fix terminal output from a real tui --local PTY run and a runTui() command-final proof showing final output visible without /verbose.
  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): Sufficient: the updated PR body/comment include redacted after-fix terminal output from a real tui --local PTY run and a runTui() command-final proof showing final output visible without /verbose.
  • remove rating: 🦪 silver shellfish: Current PR rating is rating: 🦞 diamond lobster, so this older rating label is no longer current.
  • remove status: 📣 needs proof: Current PR status label is status: 👀 ready for maintainer look.

Label justifications:

  • P1: The linked bug affects visible final TUI chat output for real users until another action forces a repaint.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): Sufficient: the updated PR body/comment include redacted after-fix terminal output from a real tui --local PTY run and a runTui() command-final proof showing final output visible without /verbose.
  • proof: sufficient: Contributor real behavior proof is sufficient. Sufficient: the updated PR body/comment include redacted after-fix terminal output from a real tui --local PTY run and a runTui() command-final proof showing final output visible without /verbose.
Evidence reviewed

PR surface:

Source 0, Tests +22. Total +22 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 4 4 0
Tests 1 24 2 +22
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 28 6 +22

What I checked:

  • Repository policy read: The full root AGENTS.md was read and applied, including the OpenClaw PR review and real-behavior proof requirements. (AGENTS.md:1, 62550710bfec)
  • Scoped TUI policy read: The scoped TUI guide was read and applied; it allows local PTY proof while warning not to overclaim Gateway, provider, session persistence, or live streaming coverage. (src/tui/AGENTS.md:1, 62550710bfec)
  • Current main still has the bug path: On current main, the local BTW empty final, no-message final, and command final branches still mutate/drop/add visible state and return after plain tui.requestRender() calls. (src/tui/tui-event-handlers.ts:492, 62550710bfec)
  • PR implements the targeted forced repaint: At the PR head, EventHandlerTui.requestRender accepts force?: boolean and all three affected final-event early returns call tui.requestRender(true). (src/tui/tui-event-handlers.ts:492, e1dcd27a01bb)
  • Regression assertions cover the patched branches: The PR head asserts forced repaint for the local BTW empty final, command final, and local no-message final cases. (src/tui/tui-event-handlers.test.ts:499, e1dcd27a01bb)
  • Dependency contract supports the call: The pinned @earendil-works/pi-tui 0.75.5 package declares requestRender(force?: boolean) and its runtime resets previous line, dimension, cursor, and viewport state when force is true. (package.json:1800, 62550710bfec)

Likely related people:

  • shakkernerd: GitHub history for src/tui/tui-event-handlers.ts shows recent same-file fixes for late source reply finals, watchdog active runs, and replayed empty finals. (role: recent TUI event-handler contributor; confidence: high; commits: 59b8aea09e69, 84d278ad81b9, 17fc1d11433a; files: src/tui/tui-event-handlers.ts)
  • RomneyDa: Recent TUI history includes prompt/run-state and startup/runtime changes in the same TUI surface, making this a plausible routing candidate for repaint behavior review. (role: adjacent TUI runtime contributor; confidence: medium; commits: 5f373ae4d3db, b79effefee92, d91ef6bb171d; files: src/tui/tui-event-handlers.ts, src/tui/tui.ts)
  • steipete: Recent TUI commits include direct or co-authored work around the TUI runtime and tests, so this person is a useful review/routing signal without assigning fault. (role: adjacent TUI co-author and recent area contributor; confidence: medium; commits: 30e59b409015, 6842d72a9c7b; files: src/tui/tui.ts, src/tui/tui-event-handlers.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. labels May 27, 2026
@clawsweeper

clawsweeper Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

✨ Hatched: 🥚 common Cosmic Signal Puff

Hatch command

Comment @clawsweeper hatch when this PR is hatchable.

Hatchability rules:

  • Merged PRs are hatchable.
  • Open PRs are hatchable when they are status: 👀 ready for maintainer look, status: 🚀 automerge armed, or labeled clawsweeper:automerge.
  • Closed unmerged PRs are hatchable only when one of those hatchable labels is still present in the durable record.

Rarity: 🥚 common.
Trait: stacks clean commits.
Image traits: location green-check meadow; accessory review stamp; palette moonlit blue and soft silver; mood watchful; pose curling around a status light; shell translucent glimmer shell; lighting gentle morning glow; background tiny artifact crates.
Share on X: post this hatch
Copy: My PR egg hatched a 🥚 common Cosmic Signal Puff in ClawSweeper.

What is this egg doing here?
  • Eggs appear after the PR passes real-behavior proof. It is here for vibes, not verdicts: it does not change labels, ratings, merge decisions, or automation.
  • The shell reacts to review momentum: open follow-up work warms it up, re-review makes it wobble, and a clean final review lets it hatch.
  • Hatchability usually comes from sufficient real-behavior proof, no blocking P0/P1/P2 findings, no security attention needed, and clean correctness. A merged PR is already final, so merge makes the egg hatchable independently.
  • The hatch is seeded from this repository and PR number, so the same PR keeps the same creature; the reviewed head SHA can only change safe visual details.
  • Rarity is just collectible sparkle: 🥚 common, 🌱 uncommon, 💎 rare, ✨ glimmer, and 🌈 legendary.

@MonkeyLeeT MonkeyLeeT changed the title [codex] Fix TUI final event forced render Fix TUI final messages not repainting until forced refresh May 27, 2026
@MonkeyLeeT MonkeyLeeT changed the title Fix TUI final messages not repainting until forced refresh fix(tui): force repaint final chat events May 27, 2026
@MonkeyLeeT

Copy link
Copy Markdown
Contributor Author

Added stronger real terminal proof to the PR body.

What changed in proof:

  • Ran the slower local-backend TUI PTY lane: OPENCLAW_TUI_PTY_INCLUDE_LOCAL=1 node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.ts passed, 14 tests.
  • Reran the real tui --local PTY smoke with mirrored terminal output and included a redacted copied transcript showing LOCAL_PTY_RESPONSE visible after one prompt with no /verbose command.
  • Ran a temporary command-final PTY proof against the real runTui() loop that emits a command: true final chat event, one of the patched early-return paths. The copied transcript shows VISIBLE_COMMAND_FINAL_PROOF rendered in the terminal with no /verbose command.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels May 27, 2026
@MonkeyLeeT MonkeyLeeT marked this pull request as ready for review May 27, 2026 17:43
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels May 27, 2026
@Takhoffman

Copy link
Copy Markdown
Contributor

@clawsweeper automerge

@clawsweeper

clawsweeper Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

🦞🔧
ClawSweeper automerge is enabled.

Draft PRs stay fix-only until GitHub marks them ready for review. Pause with /clawsweeper stop.

Automerge progress:

  • 2026-05-27 21:00:58 UTC review queued e1dcd27a01bb (queued)

@clawsweeper clawsweeper Bot added the clawsweeper:automerge Maintainer opted this PR into bounded ClawSweeper-reviewed automerge label May 27, 2026
@clawsweeper

clawsweeper Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper 🐠 reef update

Thanks for the contribution here. ClawSweeper tried the original lane first, but branch permissions blocked the push, so a replacement PR is carrying the fix forward.

Why replacement: ClawSweeper could not update the source PR branch directly; GitHub did not grant sufficient push rights to the bot for that branch.
Replacement PR: #87423
Why close: this run explicitly closes the superseded source PR after the credited replacement PR is open, so review continues in one place.
Closing this one because the run was configured to close superseded source PRs after opening the replacement.
Attribution is preserved in the replacement PR body and release-note trail.
Co-author credit kept:

fish notes: model gpt-5.5, reasoning high; reviewed against 8ac3192.

@clawsweeper clawsweeper Bot closed this May 27, 2026
@MonkeyLeeT MonkeyLeeT deleted the codex/86871-tui-force-render branch June 4, 2026 06:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clawsweeper:automerge Maintainer opted this PR into bounded ClawSweeper-reviewed automerge P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: XS status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[TUI] Assistant messages don't render until /verbose on/off — handleChatEvent early-returns skip requestRender(true)

2 participants