Skip to content

fix(web-chat): prevent blank chat pane after assistant response (#67035)#77418

Closed
carlytwozero wants to merge 1 commit into
openclaw:mainfrom
carlytwozero:fix/chat-blank-after-response
Closed

fix(web-chat): prevent blank chat pane after assistant response (#67035)#77418
carlytwozero wants to merge 1 commit into
openclaw:mainfrom
carlytwozero:fix/chat-blank-after-response

Conversation

@carlytwozero

Copy link
Copy Markdown

Problem

After a turn completes, the chat UI could render blank — showing nothing where the assistant's response should be — until the user manually refreshed the page. The content was there in gateway history, just not displayed.

Root cause

Two gaps in the chatLoading flag lifecycle when history reloads are triggered by terminal chat events:

Gap 1 — pre-set missing before void calls
In handleTerminalChatEvent and handleChatGatewayEvent, loadChatHistory is called with void (fire-and-forget). chatLoading = true is set inside the async function synchronously before the first await, but there's no guarantee a reactive render won't fire in the gap between the call site and function entry. Explicitly pre-setting chatLoading = true at each call site eliminates this window.

Gap 2 — early-exit path never cleared chatLoading
If loadChatHistory returned early due to !state.client || !state.connected, any chatLoading = true that had been pre-set externally was never reset. This left the UI stuck showing a loading state instead of cached messages. Adding state.chatLoading = false to the early-exit branch fixes this.

Together these close the render window where:
chatStream = nullchatMessages not yet updated → chatLoading still false → isEmpty = true → blank pane.

Changes

ui/src/ui/app-gateway.ts — pre-set chatLoading = true at all three void loadChatHistory() call sites (tool-turn path in handleTerminalChatEvent, final-event-needs-reload path, and deferred session-message reload path).

ui/src/ui/controllers/chat.ts — reset chatLoading = false in the early-return path so pre-set loading flag doesn't get stuck.

Test plan

  • Send a message and confirm response appears without refresh
  • Tool-call turn: response and tool results appear without refresh
  • Send a message while briefly offline, reconnect — no stuck loading spinner
  • Silent-reply / heartbeat turns: no blank flash in visible sessions

Fixes #67035

🤖 Generated with Claude Code

After a turn completes, the UI could briefly or permanently show blank
content. Two related issues:

1. When loadChatHistory is triggered by a final event (tool turns or
   non-renderable final messages), chatLoading was only set inside the
   async function body — synchronously, but still after the void call
   site returned. In practice loadChatHistory sets it before the first
   await, but explicitly pre-setting chatLoading = true at every call
   site in handleTerminalChatEvent and handleChatGatewayEvent makes the
   loading state visible to any synchronous re-render that fires between
   the call and the function body running.

2. When loadChatHistory returned early because the client was
   disconnected (!state.client || !state.connected), any chatLoading =
   true that had been pre-set externally was never cleared, leaving the
   UI stuck in a loading state rather than showing cached messages.

Together these close the window where: streaming ends → chatStream = null
→ chatMessages not yet updated → chatLoading still false → UI renders
empty (isEmpty = true) before the history reload finishes.

Fixes openclaw#67035

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@clawsweeper

clawsweeper Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge.

Summary
The branch pre-sets chatLoading before terminal WebChat history reloads and clears it when loadChatHistory exits without a connected client.

Reproducibility: no. Source inspection confirms the current reload and empty/loading render paths, but there is no high-confidence live reproduction and the claimed async call-site gap remains inferred.

Real behavior proof
Needs real behavior proof before merge: The PR body has only an unchecked manual test plan and prior CI/review context; no redacted after-fix WebChat screenshot, recording, logs, terminal output, live output, or linked artifact demonstrates the real behavior. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, ask a maintainer to comment @clawsweeper re-review.

Next step before merge
Needs contributor-supplied redacted real WebChat proof and maintainer review of the narrow loading-state scope versus the broader linked issue; automation cannot supply proof from the external author's setup.

Security
Cleared: The diff only changes Control UI chat loading-state flags and does not touch secrets, dependencies, CI, install, release, or other code-execution surfaces.

Review details

Best possible solution:

Review this as a narrow WebChat loading-state fix only after redacted real browser proof is added, and keep #67035 open or rescope it for the remaining IME/streaming symptoms.

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

No. Source inspection confirms the current reload and empty/loading render paths, but there is no high-confidence live reproduction and the claimed async call-site gap remains inferred.

Is this the best way to solve the issue?

Unclear. The disconnected early-return cleanup is narrow, but the call-site pre-set duplicates behavior already performed synchronously by loadChatHistory on the connected path, so real browser proof or focused regression coverage should validate the fix before merge.

Acceptance criteria:

  • node scripts/run-vitest.mjs ui/src/ui/app-gateway.node.test.ts ui/src/ui/controllers/chat.test.ts ui/src/ui/views/chat.test.ts
  • Redacted real WebChat proof for a normal response without refresh, a tool-call turn, and a reconnect/no-stuck-loading case

What I checked:

  • Live PR state and proof status: Live PR data still shows one code commit, no reviews, one prior ClawSweeper comment requesting real behavior proof, and no added screenshot, recording, logs, terminal output, or linked after-fix artifact. (6efe3f1008fd)
  • PR diff scope: The diff only changes ui/src/ui/app-gateway.ts and ui/src/ui/controllers/chat.ts, adding chatLoading = true before three loadChatHistory reloads and adding one disconnected early-return cleanup. (ui/src/ui/app-gateway.ts:593, 6efe3f1008fd)
  • Current main still lacks the app-gateway pre-set: Current main still calls loadChatHistory directly for the tool-final reload, final-event reload, and deferred session-message replay paths without the branch's explicit pre-set loading flag. (ui/src/ui/app-gateway.ts:693, b7e8f6da6ac1)
  • Current main still returns early while disconnected: loadChatHistory currently returns on !state.client || !state.connected before touching chatLoading, while setting chatLoading = true only after the connected guard. (ui/src/ui/controllers/chat.ts:304, b7e8f6da6ac1)
  • Render surface matches the empty/loading distinction: The chat view renders empty state when chatItems.length === 0 && !props.loading and a skeleton when loading is true with no chat items, so the loading flag affects the claimed blank-pane surface. (ui/src/ui/views/chat.ts:1006, b7e8f6da6ac1)
  • Adjacent tests do not cover the new loading flag behavior: Existing gateway/controller tests cover history reload calls and loadChatHistory loading transitions, but the inspected cases assert loadChatHistory calls rather than the proposed call-site pre-set or disconnected early-return cleanup. (ui/src/ui/app-gateway.node.test.ts:1059, b7e8f6da6ac1)

Likely related people:

  • BunsDev: Recent merged Control UI/WebChat work touched run-status cleanup, chat/session responsiveness, and the same gateway/controller/view surfaces. (role: recent area contributor; confidence: high; commits: 4935e24c7a7f, 098b72910dea, 05c9492bff0f; files: ui/src/ui/app-gateway.ts, ui/src/ui/controllers/chat.ts, ui/src/ui/views/chat.ts)
  • vincentkoc: Merged adjacent WebChat final-reload reconciliation and pending-state cleanup that overlap the invisible-until-refresh and active-run lifecycle paths. (role: feature-history owner; confidence: high; commits: cff991c88d04, 02908db62b30; files: ui/src/ui/app-gateway.ts, ui/src/ui/controllers/chat.ts)
  • scotthuang: Authored the active-send history reload deferral that overlaps the session-message reload and optimistic-message path discussed by the linked issue. (role: introduced adjacent behavior; confidence: high; commits: 7734a40a5650; files: ui/src/ui/app-gateway.ts, ui/src/ui/app-gateway.node.test.ts, ui/src/ui/app-gateway.sessions.node.test.ts)
  • steipete: Recent commits touched canonical session events, stale optimistic assistant tails, inline image preview handling, and chat display projection near this WebChat controller/gateway surface. (role: recent adjacent contributor; confidence: medium; commits: dd457474b391, f739edcf4c7e, 8cddb6ce7d06; files: ui/src/ui/app-gateway.ts, ui/src/ui/controllers/chat.ts, ui/src/ui/views/chat.ts)

Remaining risk / open question:

  • No live browser, Windows, IME, reconnect, or tool-turn proof was provided or run for this read-only review.
  • The connected-path call-site pre-set rationale is weak because an async function body runs synchronously until its first await; the useful change may be narrower than the PR description claims.
  • The linked issue covers broader composer, streaming, typing-indicator, and active-memory-adjacent symptoms that this PR does not address.

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

@RomneyDa

Copy link
Copy Markdown
Member

Heads up: this PR needs to be updated against current main before the new required Dependency Guard check can pass.

@osolmaz

osolmaz commented Jun 3, 2026

Copy link
Copy Markdown
Member

Thanks for taking this on. I’m closing this now because the linked bug path has been superseded by #89530, which landed on main as 17a285f.

What changed since this PR was opened:

Why I’m not landing this PR separately:

If there is still a blank WebChat pane on current main, please open a fresh issue or PR with a current-main repro and proof. The most useful proof would be a short WebChat recording or logs showing the prompt, the terminal event/history reload, and the blank state after 17a285f.

@osolmaz osolmaz closed this Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: 2026.4.14 Windows chat UI regression: input text swallowed, streamed replies often invisible until refresh, typing indicator flashes then blanks

3 participants