Skip to content

Fix/UI session history sync#57077

Open
bbddbb1 wants to merge 5 commits intoopenclaw:mainfrom
bbddbb1:fix/ui-session-history-sync
Open

Fix/UI session history sync#57077
bbddbb1 wants to merge 5 commits intoopenclaw:mainfrom
bbddbb1:fix/ui-session-history-sync

Conversation

@bbddbb1
Copy link
Copy Markdown
Contributor

@bbddbb1 bbddbb1 commented Mar 29, 2026

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: The Chat session picker could display a stale session after navigation or deep-link rehydration even while the loaded thread content was already for the correct session.
  • Why it matters: Users could see mismatched session metadata in the UI and lose trust in which thread they were reading or about to send into.
  • What changed: The session picker now explicitly marks the active option as selected during rerender, and the hidden mobile picker no longer reuses the desktop session-control class.
  • What changed: Added regression coverage for desktop and mobile session pickers when the current session is injected as a fallback option after rerender.
  • What did NOT change (scope boundary): No gateway protocol/API changes, no session-store schema changes, no auth/token behavior changes, no navigation behavior changes, and no chat-history loading logic changes.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause / Regression History (if applicable)

  • Root cause:
    1. The chat session <select> relied on .value while its option list could be rebuilt during rerender.
    2. When the active session only existed via fallback option injection, the browser could keep showing a previously selected option instead of the current session.
    3. The hidden mobile picker reused the same chat-controls__session class as the desktop picker, which made selector targeting ambiguous.
  • Missing detection / guardrail: No regression test covered rerendering into a fallback session option, and no test distinguished the desktop and mobile session pickers.
  • Prior context (git blame, prior PR, issue, or refactor if known): Earlier investigation focused on async history/nav flows because the symptom looked like session-state drift; the exact repro showed the thread content was already correct while only the selector was stale.
  • Why this regressed now: The mismatch becomes visible when Chat rerenders after navigation or when opening /chat?session=... and the session options are reconstructed around the current session.
  • If unknown, what was ruled out: Not a gateway storage corruption issue, not a chat.history overwrite, and not a session reset caused by auth/token handling.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file:
    • ui/src/ui/views/chat.test.ts
  • Scenario the test should lock in:
    • The desktop session picker must keep the current session selected after rerendering to a fallback session option.
    • The mobile session picker must keep the current session selected after rerendering to a fallback session option.
  • Why this is the smallest reliable guardrail: The bug is in selector rendering, so a focused UI unit test covers the exact failure mode without depending on gateway, navigation, or async history timing.
  • Existing test that already covers this (if any): Existing grouped-session selector tests already covered option labeling/fallback generation but not selected-option persistence.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

  • The visible Chat session picker stays aligned with the currently loaded thread after navigation or deep-link rehydration.
  • Desktop and mobile session pickers are now distinguishable in the DOM.
  • No intended change to chat history loading, session routing, or sidebar navigation behavior.

Diagram (if applicable)

Before:
[session = test-b] -> [session options rerender]
-> [fallback option injected]
-> [picker can still show old selected option test-e]
-> [thread content remains test-b]

After:
[session = test-b] -> [session options rerender]
-> [current option explicitly marked selected]
-> [desktop/mobile pickers are distinct]
-> [picker and thread content both show test-b]

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: Linux (dev workspace)
  • Runtime/container: Node 22.x
  • Model/provider: N/A
  • Integration/channel (if any): Control UI chat session selector
  • Relevant config (redacted): Control UI loaded with a non-default session query param or after returning to Chat with a previously selected session

Steps

  1. Open Control UI Chat with a non-default session, for example /chat?session=agent%3Amain%3Atest-b.
  2. Trigger a rerender or re-hydration path that rebuilds the session options.
  3. Observe the selected session in the picker and the rendered chat thread.

Expected

  • The selected session in the picker and the rendered chat thread match the same session.

Actual

  • Previously: the picker could still show a stale session such as test-e while the rendered thread was already test-b.
  • With this PR: the picker and rendered thread stay aligned.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • [x ] Screenshot/recording
  • Perf numbers (if relevant)

Local verification:

  • pnpm test -- ui/src/ui/views/chat.test.ts ui/src/ui/controllers/chat.test.ts
  • Manual verification: selected a non-default session, navigated away from Chat, returned to Chat, and confirmed the session picker stayed aligned with the loaded thread content across repeated page switches.

before
image
With this PR:
image

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios:
    • Read through the Chat session-picker render path and reduced the branch to a selector-only fix.
    • Added regression coverage for desktop and mobile picker rerender paths where the active session is a fallback option.
    • Ran pnpm test -- ui/src/ui/views/chat.test.ts ui/src/ui/controllers/chat.test.ts.
    • Manually switched between Chat and other pages multiple times with a non-default session selected and confirmed the picker and rendered thread stayed in sync.
  • Edge cases checked:
    • Desktop picker rerender with a newly active fallback session.
    • Mobile picker rerender with a newly active fallback session.
    • Returning to Chat after navigating away with a non-default session selected.
    • Opening Chat directly with a session query param and verifying repeated page switches do not desync the picker.
  • What you did not verify:
    • Full browser end-to-end automation coverage after the branch cleanup.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: Browser <select> behavior can be sensitive when options are rebuilt dynamically.
    • Mitigation: The active session is now explicitly marked selected on rerender, and regression tests cover both desktop and mobile pickers.
  • Risk: DOM-targeting code or tests could still hit the wrong picker if selectors stay ambiguous.
    • Mitigation: The hidden mobile picker now uses a distinct class and explicit data-chat-session-select markers.

@bbddbb1 bbddbb1 marked this pull request as ready for review March 30, 2026 14:55
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 30, 2026

Greptile Summary

This PR fixes two related session-history bugs in the Chat UI: (1) a race condition where an older in-flight chat.history response could overwrite the UI state after the user had already switched to a different session, and (2) sidebar/top-nav navigation to Chat forcing the session back to main instead of preserving the user's selection.

Key changes:

  • chat.ts — Introduces a WeakMap-based serial counter (chatHistoryRequestSerial) that tags each loadChatHistory invocation. Responses whose serial no longer matches the current value (i.e., a newer request has started) are silently discarded, preventing stale overwrites. The finally guard is also correctly scoped so chatLoading stays true until the latest request settles.
  • app-render.helpers.ts — Removes resolveSidebarChatSessionKey / resetChatStateForSessionSwitch and the renderTab block that reset the session to main on Chat navigation. Expands switchChatSession to clear chatAttachments, chatMessages, chatToolMessages, and chatThinkingLevel (fields that were previously left stale).
  • app-render.ts — Adds the same missing field clearing (chatMessages, chatToolMessages, chatThinkingLevel) to the chat-tab and overview-panel onSessionKeyChange handlers.
  • Tests — New unit test covers the stale-response serial guard end-to-end; the navigation browser test is updated to assert session-preservation behavior (previously it asserted the now-removed reset-to-main).

One P2 inconsistency to be aware of: The overview-panel onSessionKeyChange handler (in app-render.ts) now clears chat message/tool/thinking state but still omits chatStream, chatStreamStartedAt, and chatRunId — fields that switchChatSession and the chat-tab handler both clear.

Confidence Score: 5/5

Safe to merge — both bugs are correctly fixed with proper guards and updated tests; the only remaining finding is a minor state-clearing inconsistency in an edge-case path.

The stale-history serial guard is implemented correctly (WeakMap per state, finally-scoped loading flag, error path also guarded). The nav reset-to-main removal is straightforward and the updated test locks the intended behavior. All remaining findings are P2: the overview-panel handler's incomplete stream-field clearing is a pre-existing gap that this PR makes more notable by adding other field clearing, but it doesn't affect the primary user flows or introduce data loss.

ui/src/ui/app-render.ts — overview-panel onSessionKeyChange handler is missing chatStream/chatStreamStartedAt/chatRunId clearing compared to switchChatSession and the chat-tab handler.

Important Files Changed

Filename Overview
ui/src/ui/controllers/chat.ts Adds a module-level WeakMap serial counter to guard against stale chat.history responses. Implementation is correct: serial is captured before the async call, incremented per request, and compared on resolution/rejection/finally. Stale responses are silently dropped; chatLoading correctly stays true until the latest in-flight request settles.
ui/src/ui/app-render.helpers.ts Removes the forced reset-to-main logic that ran when clicking Chat in the sidebar, and adds missing fields (chatAttachments, chatMessages, chatToolMessages, chatThinkingLevel) to switchChatSession's state clear. Both changes are correct.
ui/src/ui/app-render.ts Adds clearing of chatMessages, chatToolMessages, chatThinkingLevel (and chatQueue in the overview handler) on session change. Chat-tab handler is now complete; overview-panel handler is still missing chatStream/chatStreamStartedAt/chatRunId clearing.
ui/src/ui/controllers/chat.test.ts New test correctly validates the stale-response guard: first request is held pending while second resolves, then first is resolved; assertions confirm only the second (newer) response's data is applied to state.
ui/src/ui/navigation.browser.test.ts Test description and assertions updated to reflect the new intended behavior: session is preserved (not reset to main) when clicking Chat from the sidebar. URL encoding of colons is correctly asserted.

Comments Outside Diff (1)

  1. ui/src/ui/app-render.ts, line 650-665 (link)

    P2 Incomplete state reset in overview onSessionKeyChange

    This handler now clears chatMessages, chatToolMessages, chatThinkingLevel, and chatQueue, but leaves chatStream, chatStreamStartedAt, and chatRunId untouched. The sibling switchChatSession function in app-render.helpers.ts and the chat-tab's own onSessionKeyChange (line ~1415) both clear those streaming fields. If a stream is in flight when the user switches session via the overview panel's selector, those values will persist and could surface a stale streaming bubble when the user returns to the Chat tab.

    Consider aligning with switchChatSession:

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: ui/src/ui/app-render.ts
    Line: 650-665
    
    Comment:
    **Incomplete state reset in overview `onSessionKeyChange`**
    
    This handler now clears `chatMessages`, `chatToolMessages`, `chatThinkingLevel`, and `chatQueue`, but leaves `chatStream`, `chatStreamStartedAt`, and `chatRunId` untouched. The sibling `switchChatSession` function in `app-render.helpers.ts` and the chat-tab's own `onSessionKeyChange` (line ~1415) both clear those streaming fields. If a stream is in flight when the user switches session via the overview panel's selector, those values will persist and could surface a stale streaming bubble when the user returns to the Chat tab.
    
    Consider aligning with `switchChatSession`:
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: ui/src/ui/app-render.ts
Line: 650-665

Comment:
**Incomplete state reset in overview `onSessionKeyChange`**

This handler now clears `chatMessages`, `chatToolMessages`, `chatThinkingLevel`, and `chatQueue`, but leaves `chatStream`, `chatStreamStartedAt`, and `chatRunId` untouched. The sibling `switchChatSession` function in `app-render.helpers.ts` and the chat-tab's own `onSessionKeyChange` (line ~1415) both clear those streaming fields. If a stream is in flight when the user switches session via the overview panel's selector, those values will persist and could surface a stale streaming bubble when the user returns to the Chat tab.

Consider aligning with `switchChatSession`:

```suggestion
              onSessionKeyChange: (next) => {
                state.sessionKey = next;
                state.chatMessage = "";
                state.chatAttachments = [];
                state.chatMessages = [];
                state.chatToolMessages = [];
                state.chatThinkingLevel = null;
                state.chatQueue = [];
                state.chatStream = null;
                state.chatStreamStartedAt = null;
                state.chatRunId = null;
                state.resetToolStream();
                state.applySettings({
                  ...state.settings,
                  sessionKey: next,
                  lastActiveSessionKey: next,
                });
                void state.loadAssistantIdentity();
              },
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "UI: keep selected chat session when retu..." | Re-trigger Greptile

@bbddbb1 bbddbb1 force-pushed the fix/ui-session-history-sync branch 2 times, most recently from 18e3e7a to 1948579 Compare March 31, 2026 11:47
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1ccb5d0947

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ui/src/ui/app-render.helpers.ts Outdated
@bbddbb1
Copy link
Copy Markdown
Contributor Author

bbddbb1 commented Apr 2, 2026

Hi @BunsDev , could you please take a look at this PR? The CI failure seems unrelated to my changes

@BunsDev
Copy link
Copy Markdown
Member

BunsDev commented Apr 4, 2026

Please resolve conflicts @bbddbb1

@bbddbb1 bbddbb1 force-pushed the fix/ui-session-history-sync branch from b57adfe to 5826e57 Compare April 5, 2026 08:54
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5826e57a6b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ui/src/ui/app-render.helpers.ts Outdated
Fix P2 Badge: The mobile picker now uses .selected=${live(...)} instead of
?selected attribute binding to reliably force the live selected option
after rerender, preventing stale selection display when state.sessionKey
changes externally (navigation/deep-link rehydration).
@bbddbb1
Copy link
Copy Markdown
Contributor Author

bbddbb1 commented Apr 5, 2026

Please resolve conflicts @bbddbb1

Conflicts have been resolved. I’ve verified the changes locally. Ready for another look!

@openclaw-barnacle
Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 30, 2026
@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented May 1, 2026

Codex review: needs changes before merge.

Summary
The PR changes Chat UI session picker rendering, mobile picker selectors/CSS, and UI tests so desktop and mobile session selects keep an active fallback session selected after rerenders.

Reproducibility: yes. A focused Lit render test can render the session select, change state.sessionKey to a fallback option, rerender, and assert both value and selectedOptions[0].value match the new session for desktop and mobile.

Next step before merge
This is a narrow mechanical repair: port a useful stale contributor PR onto the current refactored Chat UI selector modules and add focused tests.

Security
Cleared: The PR patch only touches Lit UI rendering, mobile CSS selectors, and UI tests; it does not change CI, dependencies, secrets, permissions, package resolution, or code execution surfaces.

Review findings

  • [P2] Port selector binding to the current controls — ui/src/ui/app-render.helpers.ts:147
Review details

Best possible solution:

Port the selector-state fix onto current main by updating ui/src/ui/chat/session-controls.ts and the mobile picker in ui/src/ui/app-render.helpers.ts, then add focused desktop/mobile fallback-session rerender tests without changing gateway protocol, session schema, auth, or history loading behavior.

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

Yes. A focused Lit render test can render the session select, change state.sessionKey to a fallback option, rerender, and assert both value and selectedOptions[0].value match the new session for desktop and mobile.

Is this the best way to solve the issue?

No for the PR branch as currently proposed, because it targets an obsolete desktop render location. The underlying .selected=${live(...)} or equivalent live option-state approach is a narrow maintainable fix once applied to the current selector modules.

Full review comments:

  • [P2] Port selector binding to the current controls — ui/src/ui/app-render.helpers.ts:147
    This branch updates the old desktop selector in app-render.helpers.ts, but current main renders the desktop session select from ui/src/ui/chat/session-controls.ts and still uses the old .value/?selected pattern. Rebase and move the live selected-option binding plus regression coverage to the current desktop module and mobile helper before merge.
    Confidence: 0.91

Overall correctness: patch is incorrect
Overall confidence: 0.9

Acceptance criteria:

  • pnpm test -- ui/src/ui/views/chat.test.ts ui/src/ui/controllers/chat.test.ts ui/src/ui/app-render.helpers.browser.test.ts
  • pnpm exec oxfmt --check --threads=1 ui/src/ui/chat/session-controls.ts ui/src/ui/app-render.helpers.ts ui/src/ui/views/chat.test.ts ui/src/ui/app-render.helpers.browser.test.ts ui/src/styles/layout.mobile.css CHANGELOG.md
  • pnpm check:changed

What I checked:

  • Current desktop selector still has the old binding shape: Current main renders the desktop session select from ui/src/ui/chat/session-controls.ts; the select is driven by .value=${state.sessionKey} and options still use ?selected=${entry.key === state.sessionKey}, with no live() option-property binding or PR data marker. (ui/src/ui/chat/session-controls.ts:37, 4655ee803d27)
  • Current mobile picker still uses the shared class and old selected attribute: The mobile dropdown remains in renderChatMobileToggle with label class="field chat-controls__session", .value=${state.sessionKey}, and ?selected=${opt.key === state.sessionKey}, so the branch's mobile selector/class change is not present on current main. (ui/src/ui/app-render.helpers.ts:493, 4655ee803d27)
  • Current tests do not include the PR's fallback selected-option regression: The current chat session-control tests cover model/thinking selector behavior, but there is no data-chat-session-select helper or selectedOptions assertion for desktop/mobile fallback-session rerenders. (ui/src/ui/views/chat.test.ts:730, 4655ee803d27)
  • Broader stale chat-history response guard is already on main: Current main already versions loadChatHistory requests, checks the active session before applying results, and has coverage for ignoring stale history responses after switching sessions; the remaining useful work is the selector rerender state fix. (ui/src/ui/controllers/chat.ts:25, 4655ee803d27)
  • GitHub discussion already identified stale-branch handling: The PR discussion includes a maintainer request to resolve conflicts and a later Codex review noting that current main had refactored desktop session controls into ui/src/ui/chat/session-controls.ts, so the branch needs to be ported rather than merged as-is.
  • Feature-history routing: Local history points to recent Chat session-control split work by Peter Steinberger (1fad8efa12cf..., 24f8d6470eba...) and earlier picker/session stabilization work by Bradley Priest and Tyler Yust. (ui/src/ui/chat/session-controls.ts:23, 4655ee803d27)

Likely related people:

  • steipete: Recent history shows Peter Steinberger split and tested the Chat session-control surface now used by current main, and current blame for the selector lines routes through that maintained UI area. (role: recent maintainer; confidence: high; commits: 1fad8efa12cf, 24f8d6470eba, bb294bcd20eb; files: ui/src/ui/chat/session-controls.ts, ui/src/ui/views/chat.test.ts, ui/src/ui/app-render.helpers.ts)
  • Bradley Priest: History for chat session navigation/picker stability includes ui(chat): persist session in URL and stabilize picker, which is adjacent to the reported navigation/deep-link session selector behavior. (role: introduced adjacent behavior; confidence: medium; commits: 5b0684ebcfe5; files: ui/src/ui/app-render.helpers.ts, ui/src/ui/views/chat.test.ts)
  • Tyler Yust: History includes earlier Chat session dropdown and refresh behavior work, which is adjacent to fallback session option rendering and picker refresh semantics. (role: adjacent owner; confidence: medium; commits: 6372242da756; files: ui/src/ui/app-render.helpers.ts, ui/src/ui/views/chat.test.ts)
  • BunsDev: The PR discussion routes conflict-resolution review attention to BunsDev, though the strongest code-history ownership still points to the current Chat UI maintainers. (role: reviewer context; confidence: low; files: ui/src/ui/chat/session-controls.ts, ui/src/ui/app-render.helpers.ts)

Remaining risk / open question:

  • Merging the branch as-is would not update the current desktop session-control module, so the reported selector desync can remain on current main.
  • The mobile picker still shares the desktop session-control class on current main, keeping selector targeting ambiguous until the port is completed.

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

@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label May 1, 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]: Chat UI State Desync: Rendered messages do not match active session after route navigation

2 participants