Skip to content

feat(ui): move chat sessions into the sidebar#52746

Closed
bobashopcashier wants to merge 1 commit intoopenclaw:mainfrom
bobashopcashier:feat/chat-sidebar-sessions
Closed

feat(ui): move chat sessions into the sidebar#52746
bobashopcashier wants to merge 1 commit intoopenclaw:mainfrom
bobashopcashier:feat/chat-sidebar-sessions

Conversation

@bobashopcashier
Copy link
Copy Markdown
Contributor

@bobashopcashier bobashopcashier commented Mar 23, 2026

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: webchat session switching was split across multiple desktop controls, including a header selector and an in-chat session switcher, which made multi-chat navigation feel redundant and harder to scan.
  • Why it matters: a sidebar session list matches the Control UI navigation model better and makes recent chat switching more discoverable without adding another session surface inside the chat canvas.
  • What changed: this PR moves chat session switching into the left CHAT sidebar section, adds a sidebar + action backed by real sessions.create, and removes the desktop chat header session selector while keeping the model picker and existing chat controls.
  • What did NOT change (scope boundary): this does not add rename/delete session management, backend session/schema changes, or a new storage/API contract; broader session management still lives on the Sessions page.

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

User-visible / Behavior Changes

  • Desktop chat sessions are listed directly under the CHAT sidebar section.
  • The CHAT section header now has a + action that creates a real new chat session.
  • The desktop chat header no longer shows a session selector; it keeps the model picker and the existing chat control buttons.
  • The CHAT sidebar section can be collapsed and expanded independently.

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:

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: local pnpm dev build
  • Model/provider: not specific to the UI change
  • Integration/channel (if any): none
  • Relevant config (redacted): local Control UI / gateway dev flow

Steps

  1. Open the Control UI chat page with multiple sessions for the active agent.
  2. Confirm the session list appears under the left CHAT sidebar section and the chat canvas does not show a second desktop session-switching surface.
  3. Click a different sidebar session, then use the sidebar + button to create a new session.
  4. Collapse and re-expand the CHAT section.

Expected

  • Session switching happens from the sidebar list.
  • The header keeps the model picker but not a second session selector.
  • Creating a session from + switches to the created session.
  • The CHAT section collapse button hides and restores the session list.

Actual

  • Matches expected behavior in the updated UI implementation and targeted automated coverage.

Evidence

Attach at least one:

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

Human Verification (required)

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

  • Verified scenarios: targeted automated verification for chat sidebar rendering, session switching, create-session behavior, and collapse/expand behavior.
  • Edge cases checked: missing current session fallback, duplicate/disambiguated labels, create-button pending state, and focus mode parity.
  • What you did not verify:

Diff Summary

Current diff against upstream/main:

File Added Deleted Total changed
ui/src/styles/layout.css 61 0 61
ui/src/ui/app-render.helpers.ts 168 34 202
ui/src/ui/app-render.ts 6 3 9
ui/src/ui/app-view-state.ts 1 0 1
ui/src/ui/app.ts 1 0 1
ui/src/ui/views/chat.browser.test.ts 0 1 1
ui/src/ui/views/chat.test.ts 358 173 531
ui/src/ui/views/chat.ts 0 15 15

Totals:

  • Files changed: 8
  • Insertions: 595
  • Deletions: 226
  • Total changed lines: 821

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:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: revert commit 9854527612.
  • Files/config to restore: the chat sidebar/header UI files in this PR branch.
  • Known bad symptoms reviewers should watch for: duplicate session-switching UI reappearing, sidebar session list not collapsing, or inability to switch/create sessions from the sidebar.

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk: the sidebar list may become crowded for agents with many sessions.
    • Mitigation: this PR keeps the Sessions page as the fallback for broader session management and stays scoped to current-agent session navigation.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 23, 2026

Greptile Summary

This PR moves chat session switching from a horizontal pill row and desktop header dropdown into the left sidebar's CHAT section, replacing the old renderChatSessionSelect with a new renderChatSidebarSection that renders per-agent session items as sidebar buttons. A sidebar + button is wired to a new createNewChatSession helper that calls sessions.create directly. The model picker is kept in the header; the in-canvas "New session" toolbar button is removed. Tests are comprehensively updated to cover the new sidebar rendering, session switching, creation flow, and collapse/expand behaviour.

Key changes:

  • renderChatSidebarSection replaces renderChatSessionSelect; sessions are filtered to the currently active agent via resolveActiveChatAgentId
  • createNewChatSession wires the + button to a real sessions.create API call with an optional window.prompt() label; guarded by newChatSessionPending flag
  • onNewSession callback removed from ChatProps and the chat toolbar
  • newChatSessionPending: boolean added to both AppViewState and OpenClawApp
  • renderChatModelSelect exported so the header can use it standalone

Issues found:

  • window.prompt() is a synchronous blocking dialog that freezes the JS thread; an inline input or modal would be more consistent with the rest of the UI
  • chatAttachments can bleed into the newly created session when the user edits the composer message while sessions.create is in-flight, because switchChatSession does not clear attachments and the guard only clears them when the message text is unchanged

Confidence Score: 4/5

  • Safe to merge; both flagged issues are edge-case UX concerns rather than data-loss or correctness blockers on the primary path.
  • The primary feature — moving sessions into the sidebar with a real sessions.create call — is correctly implemented and well-tested. The two P2 findings (window.prompt() blocking and chatAttachments leak when the draft changes mid-flight) are unlikely to affect normal usage but worth addressing in a follow-up.
  • ui/src/ui/app-render.helpers.tscreateNewChatSession contains the window.prompt() call and the attachment-clearing edge case.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: ui/src/ui/app-render.helpers.ts
Line: 619

Comment:
**`window.prompt()` blocks the UI thread**

`window.prompt()` is a synchronous, browser-native blocking dialog that freezes all JS execution and rendering while open. It also can't be styled, may be suppressed in embedded or restricted browser contexts, and is generally inconsistent with the rest of the app's UI language.

Consider replacing it with a lightweight inline input field or a small modal — something like a named input that appears in the sidebar header itself, which would be more in keeping with the sidebar mental model this PR introduces.

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

---

This is a comment left during a code review.
Path: ui/src/ui/app-render.helpers.ts
Line: 636-640

Comment:
**Attachments can leak into new session when message changed during creation**

`switchChatSession` clears `chatMessage` but does **not** clear `chatAttachments`. The current guard only clears attachments when the message text is unchanged:

```typescript
if (state.chatMessage === composerSnapshot.message) {
  state.chatAttachments = [];
}
switchChatSession(state, result.key);   // clears chatMessage, but NOT chatAttachments
```

If the user edits the message draft while the async `sessions.create` request is in-flight (i.e. `state.chatMessage !== composerSnapshot.message`), `chatAttachments` is left untouched, so the old session's attachments silently bleed into the new session's composer.

Consider clearing attachments unconditionally before calling `switchChatSession`, or alternatively adding `chatAttachments` clearing inside `switchChatSession` itself for consistency:

```suggestion
    state.chatAttachments = [];
    switchChatSession(state, result.key);
```

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

Reviews (1): Last reviewed commit: "feat(ui): move chat sessions into the si..." | Re-trigger Greptile

if (!client || !state.connected || state.newChatSessionPending) {
return;
}
const raw = window.prompt("Session name (optional):");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 window.prompt() blocks the UI thread

window.prompt() is a synchronous, browser-native blocking dialog that freezes all JS execution and rendering while open. It also can't be styled, may be suppressed in embedded or restricted browser contexts, and is generally inconsistent with the rest of the app's UI language.

Consider replacing it with a lightweight inline input field or a small modal — something like a named input that appears in the sidebar header itself, which would be more in keeping with the sidebar mental model this PR introduces.

Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/ui/app-render.helpers.ts
Line: 619

Comment:
**`window.prompt()` blocks the UI thread**

`window.prompt()` is a synchronous, browser-native blocking dialog that freezes all JS execution and rendering while open. It also can't be styled, may be suppressed in embedded or restricted browser contexts, and is generally inconsistent with the rest of the app's UI language.

Consider replacing it with a lightweight inline input field or a small modal — something like a named input that appears in the sidebar header itself, which would be more in keeping with the sidebar mental model this PR introduces.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

UX issue

Comment thread ui/src/ui/app-render.helpers.ts Outdated
Comment on lines +636 to +640
return;
}
if (state.chatMessage === composerSnapshot.message) {
state.chatAttachments = [];
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Attachments can leak into new session when message changed during creation

switchChatSession clears chatMessage but does not clear chatAttachments. The current guard only clears attachments when the message text is unchanged:

if (state.chatMessage === composerSnapshot.message) {
  state.chatAttachments = [];
}
switchChatSession(state, result.key);   // clears chatMessage, but NOT chatAttachments

If the user edits the message draft while the async sessions.create request is in-flight (i.e. state.chatMessage !== composerSnapshot.message), chatAttachments is left untouched, so the old session's attachments silently bleed into the new session's composer.

Consider clearing attachments unconditionally before calling switchChatSession, or alternatively adding chatAttachments clearing inside switchChatSession itself for consistency:

Suggested change
return;
}
if (state.chatMessage === composerSnapshot.message) {
state.chatAttachments = [];
}
state.chatAttachments = [];
switchChatSession(state, result.key);
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/ui/app-render.helpers.ts
Line: 636-640

Comment:
**Attachments can leak into new session when message changed during creation**

`switchChatSession` clears `chatMessage` but does **not** clear `chatAttachments`. The current guard only clears attachments when the message text is unchanged:

```typescript
if (state.chatMessage === composerSnapshot.message) {
  state.chatAttachments = [];
}
switchChatSession(state, result.key);   // clears chatMessage, but NOT chatAttachments
```

If the user edits the message draft while the async `sessions.create` request is in-flight (i.e. `state.chatMessage !== composerSnapshot.message`), `chatAttachments` is left untouched, so the old session's attachments silently bleed into the new session's composer.

Consider clearing attachments unconditionally before calling `switchChatSession`, or alternatively adding `chatAttachments` clearing inside `switchChatSession` itself for consistency:

```suggestion
    state.chatAttachments = [];
    switchChatSession(state, result.key);
```

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

UX issue

@bobashopcashier
Copy link
Copy Markdown
Contributor Author

image

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: 374df9f9ea

ℹ️ 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
Comment thread ui/src/ui/app-render.helpers.ts
@bobashopcashier bobashopcashier force-pushed the feat/chat-sidebar-sessions branch 2 times, most recently from aae50ec to a9a19b9 Compare March 23, 2026 09:12
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: 57ee056a08

ℹ️ 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 on lines +635 to +639
const restoreCurrentDraft = state.chatMessage !== composerSnapshot.message;
const draftToRestore = restoreCurrentDraft ? state.chatMessage : composerSnapshot.message;
const attachmentsToRestore = restoreCurrentDraft
? [...state.chatAttachments]
: composerSnapshot.attachments;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve attachment-only edits during pending session creation

In createNewChatSession(), the restore path only checks whether chatMessage changed while sessions.create was in flight. If the user adds or removes attachments without editing the text, restoreCurrentDraft stays false and the stale composerSnapshot.attachments array is written back after switchChatSession(), so those attachment changes disappear when the new session opens. This is reachable because the new sidebar + leaves the composer interactive during the RPC.

Useful? React with 👍 / 👎.

Comment on lines +196 to +200
export function renderChatSidebarSection(state: AppViewState) {
if (state.settings.navCollapsed) {
return html`<section class="nav-section nav-section--chat">
${renderTab(state, "chat", { collapsed: true })}
</section>`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Respect drawer-open state when deciding whether CHAT is collapsed

renderApp() treats a collapsed sidebar as temporarily expanded whenever the drawer is open (navCollapsed = settings.navCollapsed && !navDrawerOpen), but renderChatSidebarSection() still branches on state.settings.navCollapsed. For users who keep the nav collapsed, opening the drawer expands every other section while CHAT remains icon-only, which hides both the session list and the new-session button exactly in the view meant to expose them.

Useful? React with 👍 / 👎.

@bobashopcashier bobashopcashier force-pushed the feat/chat-sidebar-sessions branch from 57ee056 to 6343752 Compare March 23, 2026 09:44
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: 6343752143

ℹ️ 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".

? [...state.chatAttachments]
: composerSnapshot.attachments;
state.chatAttachments = [];
switchChatSession(state, result.key);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Open Chat after creating a sidebar session

Because the new + action lives in the global sidebar, it can now be triggered from Overview, Sessions, or any other tab. createNewChatSession() only calls switchChatSession() here and never state.setTab("chat"), so the RPC succeeds but the main pane stays on the old tab and the newly created conversation remains hidden until the user navigates manually. handleSessionClick() in the same file already has to special-case state.tab !== "chat" for exactly this reason.

Useful? React with 👍 / 👎.

Comment on lines +252 to +258
<button
type="button"
class="nav-item nav-item--session ${isActive ? "nav-item--active" : ""}"
data-chat-session-key=${entry.key}
title=${entry.title}
aria-current=${isActive ? "page" : "false"}
@click=${() => handleSessionClick(entry.key)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Disable sidebar session switches while offline

These sidebar session buttons stay active even when state.connected is false. In that state switchChatSession() still clears chatMessage, queued items, and persists the new key (ui/src/ui/app-render.helpers.ts:586), but loadChatHistory() immediately returns offline (ui/src/ui/controllers/chat.ts:67), so a disconnect can leave the old transcript on screen under a different session while also dropping the current draft. The previous desktop selector was disabled while disconnected, so this is a real regression for gateway-drop scenarios.

Useful? React with 👍 / 👎.

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.

1 participant