Skip to content

Render dashboard chat history incrementally#88685

Merged
vincentkoc merged 9 commits into
openclaw:mainfrom
alexzhu0:fix/dashboard-session-switching
Jun 3, 2026
Merged

Render dashboard chat history incrementally#88685
vincentkoc merged 9 commits into
openclaw:mainfrom
alexzhu0:fix/dashboard-session-switching

Conversation

@alexzhu0

@alexzhu0 alexzhu0 commented May 31, 2026

Copy link
Copy Markdown
Contributor

Fixes #87345

User-facing bug

Switching dashboard chat sessions could synchronously rebuild the full 100-message UI history window, causing 8-10s switches on long or heavy transcripts.

Exact repro

  1. Open the Control UI chat dashboard.
  2. Use or select a session whose chat.history returns the current 100-message tail.
  3. Switch between dashboard sessions.
  4. Before: the chat view can block while all loaded history items are built and rendered at once.
  5. After: newly loaded large histories start with the latest 30-message render window, expand in 30-message batches when the user is at the top, avoid programmatic bottom-scroll expansion, and auto-fill non-scrollable initial windows while preserving bottom anchoring.

Fix summary

  • Added optional historyRenderLimit to buildChatItems, preserving existing default 100-message behavior.
  • Added per-session render-window state in renderChat so newly loaded large histories initially render 30 messages.
  • Expand the render window on top scroll or exact top state, skip bottom auto-scrolls inside the top threshold, and auto-fill after render when the initial window does not overflow.
  • Keep auto-filled windows anchored to latest messages via the existing onScrollToBottom path.
  • Search with a non-empty query still renders the full loaded and capped history window.
  • Restored the explicit showSecondary: false at the composer renderChatRunControls call so the history-window refactor does not reintroduce the New session and Export buttons in the composer toolbar, with a regression test asserting neither control renders there.
  • Restored the Talk settings button's aria-expanded state, visible control label, and descriptive title/aria-label that the refactor had flattened, with a regression test asserting aria-expanded tracks realtimeTalkOptionsOpen.

Targeted tests

  • buildChatItems coverage proves the render limit preserves the newest window.
  • Chat view tests cover initial 30-message windows, top-scroll expansion, bottom-scroll suppression, auto-fill, and full rendering while searching.

Real behavior proof

Behavior addressed: Switching to a large dashboard chat history no longer forces an immediate 100-message render; the UI starts with a latest-message window and expands incrementally.

Real environment tested: Local OpenClaw checkout on branch fix/dashboard-session-switching, commit 415e0eaaf4, based on origin/main.

Exact steps or command run after this patch:

  • git diff --check origin/main...HEAD
  • OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=120000 node scripts/run-vitest.mjs ui/src/ui/views/chat.test.ts
  • ./node_modules/.bin/oxlint ui/src/ui/views/chat.ts ui/src/ui/views/chat.test.ts

Evidence after fix:

  • Full ui/src/ui/views/chat.test.ts suite passed after restoring the suppressed composer controls and the Talk settings button semantics, and adding both regression tests. Copied terminal output:
 RUN  v4.1.7 /Users/alex/PR/projects/openclaw__openclaw/worktrees/88685

 Test Files  1 passed (1)
      Tests  92 passed (92)
  • Both regression tests were verified to fail without their fix and pass with it:
    • removing showSecondary: false reproduces 1 failed | 90 passed;
    • removing the Talk button aria-expanded reproduces 1 failed | 91 passed;
    • with both fixes in place the suite returns 92 passed.
  • oxlint on the two touched files passed with exit code 0.
  • git diff --check origin/main...HEAD passed.

Observed result after fix: Newly loaded large histories render the newest 30 messages first, expand in 30-message batches from top-scroll/top state, preserve the previously visible top anchor across repeated expansion, avoid bottom auto-scroll expansion, and still render full loaded history for non-empty search.

What was not tested: Full UI suite, pnpm check, and browser screenshot/video were not run. This PR changes render-window behavior and is covered with focused jsdom view/controller tests plus UI tsgo; no visual recording was captured.

Not tested / known gaps

  • Full UI suite and pnpm check were not run.
  • No browser screenshot or video captured; this PR changes render-window behavior and is covered with focused jsdom tests, not visual layout changes.

AI-assisted disclosure

Prepared with AI assistance in Codex. I reviewed the diff, ran targeted tests, and resolved codex review findings before opening.

@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui size: M triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels May 31, 2026
@clawsweeper

clawsweeper Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed June 3, 2026, 3:07 AM ET / 07:07 UTC.

Summary
The PR adds an opt-in historyRenderLimit path, makes renderChat start large histories with a 30-message window that expands on top scroll/autofill, and adds focused chat/Talk/composer regression tests.

PR surface: Source +255, Tests +342. Total +597 across 8 files.

Reproducibility: no. live reproduction was run here. Source inspection supports the reported path: current main loads the capped chat history tail and builds that full window in the chat view, but the 8-10s browser stall still needs real-session proof.

Review metrics: none identified.

Merge readiness
Overall: 🦪 silver shellfish
Proof: 🦪 silver shellfish
Patch quality: 🐚 platinum hermit
Result: blocked until real behavior proof from a real setup is added.

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

Rank-up moves:

  • [P1] Add redacted browser or live Control UI proof showing a large-history session switch, top-scroll expansion, and preserved anchoring.
  • Cover the adjacent Talk settings and composer controls in that proof enough to catch visible regressions.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR body supplies focused jsdom/Vitest, lint, and diff-check output, but no real browser, screenshot, recording, redacted logs, or live Control UI proof of the changed session-switch behavior after the patch. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Mantis proof suggestion
A short visible Control UI proof would materially reduce the remaining merge uncertainty for the session-switching and scroll-anchor behavior. A maintainer can ask Mantis to capture proof by posting a new PR comment that starts with the OpenClaw Mantis account mention, followed by:

visual task: verify Control UI switches to a large chat history, initially renders the latest 30 messages, expands on top scroll, preserves anchoring, and keeps Talk/composer controls intact.

Risk before merge

  • [P1] The contributor proof still does not show a real browser/Control UI session switch with a large transcript, top-scroll expansion, or preserved anchoring after the patch.
  • [P1] The PR also touches visible Talk/composer controls, so focused jsdom tests reduce risk but do not replace a quick visual/browser proof before merge.

Maintainer options:

  1. Decide the mitigation before merge
    Land the view-layer incremental render-window approach after a redacted browser or live Control UI proof demonstrates large-history session switching, top-scroll expansion, anchor preservation, and no visible Talk/composer control regression.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P1] The remaining blocker is contributor-supplied browser/live Control UI proof and normal maintainer UI review, not a narrow automated code repair.

Security
Cleared: The diff is limited to Control UI TypeScript, CSS, tests, and i18n baseline metadata; I found no concrete security or supply-chain regression.

Review details

Best possible solution:

Land the view-layer incremental render-window approach after a redacted browser or live Control UI proof demonstrates large-history session switching, top-scroll expansion, anchor preservation, and no visible Talk/composer control regression.

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

No live reproduction was run here. Source inspection supports the reported path: current main loads the capped chat history tail and builds that full window in the chat view, but the 8-10s browser stall still needs real-session proof.

Is this the best way to solve the issue?

Yes, this looks like an acceptable narrow fix shape: the smaller window stays opt-in from renderChat while buildChatItems keeps its existing default 100-message behavior. The remaining gap is proof in a real browser session, not an obvious code-layer replacement.

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P2: This is a bounded Control UI performance bugfix for slow dashboard session switching, with limited blast radius but real user-facing impact.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🐚 platinum hermit.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body supplies focused jsdom/Vitest, lint, and diff-check output, but no real browser, screenshot, recording, redacted logs, or live Control UI proof of the changed session-switch behavior after the patch. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Source +255, Tests +342. Total +597 across 8 files.

View PR surface stats
Area Files Added Removed Net
Source 5 329 74 +255
Tests 3 342 0 +342
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 8 671 74 +597

Acceptance criteria:

  • [P1] After proof is added, rerun focused UI tests such as node scripts/run-vitest.mjs ui/src/ui/views/chat.test.ts ui/src/ui/chat/build-chat-items.test.ts.
  • [P1] Run the relevant UI lint/check lane or maintainer-selected Control UI validation before merge.
  • [P1] Capture redacted browser/live Control UI proof for a large-history session switch and top-scroll expansion.

What I checked:

  • Current main still renders the full capped history window: buildChatItems on current main stops only at CHAT_HISTORY_RENDER_LIMIT; renderChat passes no smaller render-window argument into the cached builder. (ui/src/ui/chat/build-chat-items.ts:480, 7ccbffcb1bd5)
  • Current history load still requests the 100-message tail: The chat controller requests chat.history / chat.startup with limit: CHAT_HISTORY_REQUEST_LIMIT, matching the linked slow-switching report's central loaded-history surface. (ui/src/ui/controllers/chat.ts:646, 7ccbffcb1bd5)
  • PR head adds the bounded builder knob: The proposed BuildChatItemsProps adds historyRenderLimit, clamps it to the existing 100-message cap, and uses it in resolveHistoryStartIndex. (ui/src/ui/chat/build-chat-items.ts:26, 2b2705c72403)
  • PR head keeps the smaller window in the view layer: renderChat resolves a 30-message initial window, expands it in 30-message batches on top scroll, auto-fills non-scrollable windows, and passes the limit to buildCachedChatItems. (ui/src/ui/views/chat.ts:620, 2b2705c72403)
  • Focused test coverage exists: The PR adds tests for initial 30-message rendering, top-scroll expansion, repeated anchor preservation, bottom-scroll suppression, autofill, and adjacent Talk/composer control regressions. (ui/src/ui/views/chat.test.ts:604, 2b2705c72403)
  • Real behavior proof remains mock/test-only: The PR body reports git diff --check, focused node scripts/run-vitest.mjs ui/src/ui/views/chat.test.ts, and oxlint, and explicitly says no browser screenshot or video was captured. (2b2705c72403)

Likely related people:

  • vincentkoc: Current-main blame for the central renderChat and buildChatItems paths points to Vincent Koc, and recent history includes a Control UI chat metadata performance commit. (role: recent area contributor; confidence: medium; commits: e59e65be67dc, c0b05a2100d3; files: ui/src/ui/views/chat.ts, ui/src/ui/chat/build-chat-items.ts, ui/src/ui/controllers/chat.ts)
  • steipete: Earlier available history shows Peter Steinberger expanding the Control UI dashboard and carrying multiple chat view/test refactors around the same surface. (role: adjacent feature-history owner; confidence: medium; commits: 6ff837125475, 76c8db376666; files: ui/src/ui/app-render.ts, ui/src/ui/views/chat.ts, ui/src/ui/views/chat.test.ts)
  • byungskers: The PR discussion includes a review note specifically requesting repeated top-scroll anchor coverage, which the current branch now tests. (role: reviewer; confidence: medium; files: ui/src/ui/views/chat.test.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. P2 Normal backlog priority with limited blast radius. labels May 31, 2026
@byungskers

Copy link
Copy Markdown

This feels like a good pragmatic step before pulling in virtualization. Keeping the default 100-message behavior in buildChatItems() and making the smaller window opt-in from the view layer is a nice boundary.

One follow-up I’d love to see covered, either here or in a later patch, is anchor preservation when the window expands repeatedly on top-scroll. The tests already cover the limit progression well; a regression around “user scrolls to top, window grows, but the previously visible top item does not jump unexpectedly” would make me even more comfortable with the UX on very heavy sessions.

@alexzhu0
alexzhu0 marked this pull request as ready for review June 1, 2026 02:05
@alexzhu0
alexzhu0 force-pushed the fix/dashboard-session-switching branch from 9163738 to 401cb3b Compare June 1, 2026 02:23
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jun 1, 2026
@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 Jun 1, 2026
alexzhu0 added a commit to alexzhu0/openclaw that referenced this pull request Jun 1, 2026
Constraint: Keep openclaw#88685 focused on dashboard session switching responsiveness and accepted review feedback.
Rejected: Replacing chat controls with a broader component rewrite | too broad for this remediation PR.
Confidence: high
Scope-risk: moderate
Directive: Keep lazy history expansion tied to explicit scroll/search paths and preserve existing composer/Talk controls.
Tested: git diff --check origin/main...HEAD; focused chat/app Talk Vitest; build-chat-items Vitest; UI tsgo; codex review --base origin/main.
Not-tested: Full UI suite and browser visual recording not run.
@alexzhu0
alexzhu0 force-pushed the fix/dashboard-session-switching branch from 054f5bb to 0e65213 Compare June 1, 2026 16:39
@clawsweeper clawsweeper Bot added merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Jun 1, 2026
@vincentkoc vincentkoc self-assigned this Jun 3, 2026
@clawsweeper clawsweeper Bot removed the merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. label Jun 3, 2026
alexzhu0 added 5 commits June 2, 2026 23:42
Constraint: OpenClaw dashboard issue openclaw#87345 reports 8-10s switches from full chat history rendering.
Rejected: Adding a new virtualization dependency | too broad for a targeted stability PR.
Confidence: high
Scope-risk: moderate
Directive: Keep chat history window expansion tied to explicit user scroll-up or search, not automatic full rebuild.
Tested: node node_modules/oxfmt/bin/oxfmt --write --threads=1 ui/src/ui/chat/build-chat-items.ts ui/src/ui/chat/build-chat-items.test.ts ui/src/ui/views/chat.ts ui/src/ui/views/chat.test.ts; git diff --check; CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=120000 OPENCLAW_VITEST_NO_OUTPUT_HEARTBEAT_MS=10000 node scripts/run-vitest.mjs --run --maxWorkers 1 ui/src/ui/chat/build-chat-items.test.ts ui/src/ui/views/chat.test.ts
Not-tested: pnpm tsgo:test:ui and node scripts/check-changed.mjs both blocked during pnpm install; sandbox hit npm ENOTFOUND and escalated retry hit Darwin optional package timeout error 23.
Constraint: codex review found hidden history could be unreachable when the initial render window did not overflow the chat thread.
Rejected: Making the hidden-count notice a bespoke button | would require changing generic grouped message rendering for a small reachability fix.
Confidence: high
Scope-risk: narrow
Directive: Preserve the scroll-up expansion path, but keep a post-render fill path for non-overflowing windows.
Tested: node node_modules/oxfmt/bin/oxfmt --write --threads=1 ui/src/ui/views/chat.ts ui/src/ui/views/chat.test.ts; git diff --check; CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=120000 OPENCLAW_VITEST_NO_OUTPUT_HEARTBEAT_MS=10000 node scripts/run-vitest.mjs --run --maxWorkers 1 ui/src/ui/chat/build-chat-items.test.ts ui/src/ui/views/chat.test.ts
Not-tested: pnpm tsgo:test:ui and node scripts/check-changed.mjs both blocked during pnpm install; sandbox hit npm ENOTFOUND and escalated retry hit Darwin optional package timeout error 23.
Constraint: Maintainer feedback requested repeated top-scroll anchor coverage for the dashboard render window path.
Confidence: medium
Scope-risk: narrow
Directive: Keep render-window expansion anchored to the previously visible top item when adding older messages above the viewport.
Tested: git diff --check; node scripts/run-vitest.mjs --run --maxWorkers 1 ui/src/ui/views/chat.test.ts -t "chat history render window".
Not-tested: Full ui/src/ui/views/chat.test.ts still has unrelated existing composer/Talk assertion failures in this worktree.
Constraint: Keep openclaw#88685 focused on dashboard session switching responsiveness and accepted review feedback.
Rejected: Replacing chat controls with a broader component rewrite | too broad for this remediation PR.
Confidence: high
Scope-risk: moderate
Directive: Keep lazy history expansion tied to explicit scroll/search paths and preserve existing composer/Talk controls.
Tested: git diff --check origin/main...HEAD; focused chat/app Talk Vitest; build-chat-items Vitest; UI tsgo; codex review --base origin/main.
Not-tested: Full UI suite and browser visual recording not run.
The history-window refactor dropped the explicit showSecondary: false at
the composer renderChatRunControls call, so it fell back to the default
true and reintroduced the New session and Export buttons in the composer
toolbar. Restore the explicit suppression and add a regression test that
asserts neither control renders in the composer.
alexzhu0 and others added 4 commits June 2, 2026 23:42
The history-window refactor also flattened the Talk options button: it
lost its aria-expanded reflection of the open panel state, its visible
control label, and the descriptive Talk settings title/aria-label.
Restore the accessible expanded-state semantics and label, and add a
regression test asserting aria-expanded tracks realtimeTalkOptionsOpen.
@vincentkoc
vincentkoc force-pushed the fix/dashboard-session-switching branch from 62bfd29 to 2b2705c Compare June 3, 2026 06:58
@vincentkoc

Copy link
Copy Markdown
Member

Land-ready proof for head 2b2705c72403fb529fd57db36077f336f26110a3.

What changed during maintainer closeout:

  • preserved existing composer/Talk controls while keeping the incremental chat history render window
  • restored the Talk settings callback guard and aria-expanded behavior
  • kept native Talk selects while preserving static, title-case copy for i18n/raw-copy checks
  • refreshed the raw-copy baseline after rebasing onto current main

Proof run:

  • node scripts/run-vitest.mjs run ui/src/ui/views/chat.test.ts ui/src/ui/chat/build-chat-items.test.ts ui/src/ui/app.talk.test.ts -> passed
  • node scripts/run-vitest.mjs run --config test/vitest/vitest.ui-e2e.config.ts --configLoader runner ui/src/ui/e2e/chat-flow.e2e.test.ts -> passed
  • node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.test.ui.json -> passed
  • ./node_modules/.bin/oxfmt --check ui/src/ui/views/chat.ts -> passed
  • ./node_modules/.bin/oxlint ui/src/ui/views/chat.ts ui/src/ui/views/chat.test.ts ui/src/ui/chat/build-chat-items.ts ui/src/ui/chat/build-chat-items.test.ts ui/src/ui/app.ts ui/src/ui/app.talk.test.ts ui/src/styles/chat/layout.css -> passed
  • git diff --check origin/main...HEAD -> passed
  • local raw-copy phase passed with control-ui-i18n: raw-copy: baseline entries=757; linked-worktree local pnpm formatter handoff still hits the known no-TTY dependency reconciliation trap, so CI is the full i18n/build-artifacts proof
  • Testbox changed gate: tbx_01kt63th9k4085eehr4b3wq2xq, Actions run 26868505948, pnpm check:changed, exit 0
  • final autoreview: .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main -> autoreview clean: no accepted/actionable findings reported
  • PR CI for run 26868944239 is green after rerunning failed jobs: 122 success, 9 skipped

Visual mocked Control UI proof covered a 105-message session windowing from 30 visible messages to 90 after upward expansion, with latest and earlier messages both reachable.

@vincentkoc
vincentkoc merged commit 85e5d48 into openclaw:main Jun 3, 2026
243 of 254 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui P2 Normal backlog priority with limited blast radius. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: L status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dashboard: slow session switching (8-10s) — no virtual scrolling, full re-render on every switch

3 participants