Skip to content

feat(web-shell): named session groups and color tags in the sidebar#6350

Merged
wenshao merged 4 commits into
QwenLM:mainfrom
wenshao:feat/web-shell-session-groups
Jul 6, 2026
Merged

feat(web-shell): named session groups and color tags in the sidebar#6350
wenshao merged 4 commits into
QwenLM:mainfrom
wenshao:feat/web-shell-session-groups

Conversation

@wenshao

@wenshao wenshao commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Adds named session groups to the web-shell session sidebar — create, rename, and delete a group, and assign a session to one — alongside the existing quick color tags, and surfaces pin / archive state. The grouping data (group id / name / color, pinned / archived state) is plumbed end-to-end through the daemon so every web-shell client sees the same organization. It also unifies the group action label: the trigger button and the popover it opens now share a single Group string (sidebar.sessionGroup), dropping the redundant Move to group wording.

Why it's needed

Color tags alone are ad-hoc; users juggling many sessions want durable, named buckets they can rename and reuse. Separately, Move to group was both inaccurate and inconsistent — the popover it opens is already titled Group, and its actions are broader than "move" (assign a color tag, ungroup, or create a group), so a single Group label is clearer and matches the panel it opens.

Reviewer Test Plan

How to verify

The backend and daemon plumbing are covered by unit tests (see Evidence). For the UI: run qwen serve --web, open the session sidebar, and use the group action (the group icon on a session row, or the "…" overflow menu). Confirm that (1) the action reads Group and matches the title of the popover it opens; (2) the popover can create / rename / delete a named group, assign a color tag, ungroup, pin, and archive a session; (3) assigning a session to a group persists across a sidebar reload.

Evidence (Before & After)

Label — Before: the group button/menu read Move to group while the popover it opened read Group (two strings for one action). After: both read Group, asserted by the updated WebShellSidebar.test.tsx (selector switched to [aria-label="Group"]). The broader grouping UI and daemon plumbing are verified by the unit suites below rather than screenshots.

Unit suites run locally, all green:

  • packages/coresession-organization-service.test.ts (23)
  • packages/cliserve/server.test.ts (35), serve/acp-http/transport.test.ts (14), serve/acp-http/dispatch.test.ts (14), serve/routes/session.test.ts (23), serve/server/session-list.test.ts (5)
  • packages/web-shellWebShellSidebar.test.tsx (31)

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

Local unit tests via vitest (no dev server or sandbox). Ran on macOS (Darwin); Windows and Linux are covered by CI.

Risk & Scope

  • Main risk or tradeoff: the group action button and its popover container now share the Group aria-label; screen readers still disambiguate them by ARIA role (button vs. menu), so the redundancy is cosmetic. The rest of the change is additive.
  • Not validated / out of scope: live cross-browser web-UI screenshots — the change is covered by unit tests instead of a recorded UI session.
  • Breaking changes / migration notes: none. The new session-list fields (groupId / groupName / groupColor / archivedAt) are optional, and the label change is internal to web-shell i18n.

Linked Issues

None.

中文说明

What this PR does

在 web-shell 会话侧栏加入命名分组——新建、重命名、删除分组,并把会话归入某个分组——与已有的快捷颜色标签并存,同时展示置顶 / 归档状态。分组数据(分组 id / 名称 / 颜色、置顶 / 归档状态)经 daemon 端到端打通,任何 web-shell 客户端看到的组织方式都一致。此外统一了分组按钮文案:触发按钮与它弹出的面板现在共用同一个 Groupsidebar.sessionGroup),去掉多余的 Move to group

Why it's needed

只有颜色标签是临时性的;管理大量会话的用户想要可重命名、可复用的持久命名分组。另外 Move to group 既不准确也不一致——它弹出的面板本就叫 Group,而且能做的不止"移动"(选颜色标签、移出分组、新建分组),因此统一为 Group 更清晰、也与它弹出的面板一致。

Reviewer Test Plan

How to verify

后端与 daemon plumbing 由单元测试覆盖(见 Evidence)。UI 部分:运行 qwen serve --web,打开会话侧栏,使用分组操作(会话行上的分组图标,或 "…" 溢出菜单)。确认:(1) 操作显示为 Group,与它弹出面板的标题一致;(2) 面板可新建 / 重命名 / 删除命名分组、选颜色标签、移出分组、置顶、归档;(3) 把会话归入分组后,刷新侧栏仍保留。

Evidence (Before & After)

文案——Before:分组按钮 / 菜单显示 Move to group,而它弹出的面板显示 Group(一个操作两套词)。After:两者都显示 Group,由更新后的 WebShellSidebar.test.tsx 断言(选择器改为 [aria-label="Group"])。更大范围的分组 UI 与 daemon plumbing 由下方单元套件验证,而非截图。

本地运行的单元套件,全部通过:

  • packages/coresession-organization-service.test.ts (23)
  • packages/cliserve/server.test.ts (35)、serve/acp-http/transport.test.ts (14)、serve/acp-http/dispatch.test.ts (14)、serve/routes/session.test.ts (23)、serve/server/session-list.test.ts (5)
  • packages/web-shellWebShellSidebar.test.tsx (31)

Tested on

见上方表格(macOS ✅ 本地单元测试;Windows / Linux ⚠️ 由 CI 覆盖)。

Environment (optional)

本地用 vitest 跑单元测试(无 dev server 或 sandbox)。在 macOS(Darwin)上运行;Windows 与 Linux 由 CI 覆盖。

Risk & Scope

  • 主要风险 / 取舍:分组按钮与其弹出面板现在共用 Group 这个 aria-label;屏幕阅读器仍能靠 ARIA role(button vs. menu)区分,冗余仅是外观层面。其余为纯增量改动。
  • 未验证 / 不在范围:跨浏览器的真机 web-UI 截图——本次以单元测试代替录屏验证。
  • 破坏性变更 / 迁移说明:无。会话列表新增字段(groupId / groupName / groupColor / archivedAt)均为可选,文案改动仅限 web-shell i18n 内部。

Linked Issues

None.

Extend web-shell session organization with named groups (create / rename /
delete, assign a session to a group) alongside quick color tags, and surface
pin / archive state. The grouping data is plumbed end-to-end through the
daemon.

- core: session-organization-service carries group id / name / color and
  pin / archive metadata on organized-list entries
- sdk / acp-bridge: session-list entries gain groupId / groupName /
  groupColor / archivedAt; add SessionGroupColor and list-session-groups
  result types
- cli/serve: dispatch + session routes expose listing and assigning groups
- web-shell: sidebar group management UI (create / rename / delete groups,
  color picker, pin, archive) and reuse the shared "Group" label for the
  group action, dropping the redundant "Move to group" string

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the PR!

The PR body doesn't follow the pull request template. The template requires these sections:

  • What this PR does / Why it's needed — the PR uses a single ## Summary heading instead
  • Reviewer Test Plan — with How to verify, Evidence (Before & After), and the Tested on table. This is the section maintainers rely on to verify the change actually works
  • Risk & Scope — main risk, what's out of scope, breaking changes
  • Linked IssuesCloses #N or Related Issues

Could you update the PR body to match the template? It doesn't need to be long — just the right headings so reviewers can find what they need.

中文说明

PR 正文没有按照 PR 模板 填写。模板要求以下几个部分:

  • What this PR does / Why it's needed — 目前只有一个 ## Summary,需要拆开
  • Reviewer Test Plan — 包括 How to verifyEvidence (Before & After)Tested on 表格。这是 reviewer 确认改动是否生效的关键依据
  • Risk & Scope — 主要风险、不在范围内的内容、破坏性变更
  • Linked IssuesCloses #N 或相关 issue

请按模板更新 PR 正文,不需要写很长,只要把对应的 heading 补齐即可。

Qwen Code · qwen3.7-max

@wenshao

wenshao commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

Updated the PR body to follow the current template — What this PR does, Why it's needed, Reviewer Test Plan (How to verify / Evidence / Tested on), Risk & Scope, and Linked Issues. My local checkout's template was a few commits behind main, which is why the first version used the older ## Summary layout. Thanks for the catch.

中文

已按当前模板更新 PR 正文——What this PR doesWhy it's neededReviewer Test Plan(How to verify / Evidence / Tested on)、Risk & ScopeLinked Issues。之前本地检出的模板落后 main 几个提交,所以初版用了旧的 ## Summary 结构。感谢指出。

@wenshao

wenshao commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓ — all required sections present: What this PR does, Why it's needed, Reviewer Test Plan (How to verify / Evidence / Tested on), Risk & Scope, Linked Issues.

Problem: This is a genuine UX gap — users with many sessions in the web-shell sidebar have only named groups for organization, but no lightweight per-session color tagging. The PR adds a first-class color field to session organization, giving users a quick visual-bucket option alongside named groups.

Direction: Aligned. Session management is on the roadmap (roadmap/session-management), and per-session color tagging is a natural extension of the existing group/pin system. Comparable session-management UIs offer this kind of lightweight color categorization.

Approach: The scope feels right — adding color as a third organization dimension (alongside group and pin) without restructuring the existing system. Each layer does the minimum: core adds color to the data model with validation, the CLI API validates at the boundary, and web-shell integrates into the existing group popover as a unified "Group" action. The single-choice rule (color XOR named group in the UI) is a good call — it keeps the sidebar taxonomy simple. The core service stays independent (records whatever fields callers provide), and the mutual exclusivity is enforced client-side only. That separation is correct.

One note: SESSION_GROUP_COLORS is hardcoded in WebShellSidebar.tsx, mirroring core's GROUP_COLOR_OPTIONS. The comment explains the rationale (client never imports from core), and the component uses the daemon's color catalog when available with the hardcoded list as fallback. Acceptable duplication for the architectural boundary.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓ — 所有必需章节齐全。

问题: 这是一个真实的 UX 缺口——web-shell 侧栏有大量会话的用户只能通过命名分组来组织,缺少轻量级的单会话颜色标签。PR 在会话组织模型中加入一等公民的 color 字段来解决。

方向: 对齐。会话管理在路线图上(roadmap/session-management),每会话颜色标签是现有分组/置顶系统的自然延伸。

方案: 范围合理——将 color 作为第三个组织维度加入,不重构现有系统。每层只做最少的事。UI 中颜色与命名分组互斥是正确的——保持侧栏分类简洁。核心服务保持独立,互斥由客户端执行。

进入代码审查 🔍

Qwen Code · qwen3.7-max

Comment thread packages/cli/src/serve/server/session-list.ts

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

⚠️ Downgraded from Request changes to Comment: self-PR.

— qwen3.7-max via Qwen Code /review

Comment thread packages/web-shell/client/components/sidebar/WebShellSidebar.tsx
@wenshao

wenshao commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

Suggestions — commit 5b2a7381a

File Issue Suggested fix
packages/core/src/services/session-organization-service.ts:477 Unknown session colors silently normalized to null in readStore() with no warnOnce — unlike the group color path at line 586 which does log. A corrupted or forward-version store file loses session color data with no diagnostic trail. Add a warnOnce call when an unsupported session color is encountered, mirroring the group color path: this.warnOnce(unknown-session-color:${sessionId}\0${color}, Session ${sessionId} uses unsupported color "${color}"; normalizing to null)

— qwen3.7-max via Qwen Code /review

wenshao added 2 commits July 6, 2026 02:02
Color / named group / recent are mutually exclusive buckets in the web-shell
sidebar — a color-tagged session shows in its color section, not "recent".
But the organized session-list `group=ungrouped` filter only checked
`groupId == null`, so a color-tagged session with no named group leaked into
ungrouped results for REST/ACP consumers, disagreeing with the UI taxonomy.

Align the server filter: ungrouped means no named group and no color tag.
Adds an ACP session/list test asserting a color-tagged session is excluded
from group=ungrouped (fails on the old filter, passes on the new one).
saveGroupEditor's create-with-target path assigned the new group but left any
existing color tag in place, unlike the sibling assignSessionGroup /
assignSessionColor paths that keep color and named group mutually exclusive.
Because color takes precedence in the sidebar's section bucketing, the session
stayed in its color section and the group assignment had no visible effect.

Send `color: null` alongside `groupId` on that path, and extend the
create-group dialog test to assert the assignment clears the color.
@wenshao

wenshao commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Code review

Independent proposal before reading the diff: add a color field to session organization, validate at API boundary using existing GROUP_COLOR_OPTIONS, render colored sections in the sidebar, and add quick-pick color options in the existing group popover. Color and named group should be mutually exclusive in the UI but the store should record whatever callers provide.

The PR's approach matches this almost exactly. A few observations:

  • Mutual exclusivity is UI-only, not enforced in core. SessionOrganizationService.updateSessionOrganization() happily accepts both groupId and color in the same call. The web-shell client clears one when setting the other. This is the right separation — the core service stays generic, and the taxonomy rule lives in the presentation layer. SDK consumers could theoretically set both, but the store handles it gracefully (both fields coexist in storage, the client shows color-takes-preference in rendering).

  • Backward compatibility is clean. The color field is optional in SessionOrganization and the SessionOrganizationView normalizes unknown colors to null via isSupportedGroupColor(). Old clients that don't know about color just see undefined.

  • Color sections render only when non-empty while named group sections stay visible even when empty. This makes sense: empty named groups are persistent containers the user created; empty color buckets are just unused palette slots.

  • No critical bugs found. The viewOrganization function correctly handles the color field, the serializeOrganization function omits null colors (saving space), and the dispatch validation matches core's GROUP_COLOR_OPTIONS.

  • Filter consistency: session-list.ts correctly excludes color-tagged sessions from both ungrouped and named-group filters, matching the sidebar's "color takes precedence" bucketing. This keeps REST/ACP consumers aligned with the UI.

  • Minor diagnostic gap (non-blocking): Unknown session colors are silently normalized to null in readStore() without a warnOnce, while the group-color path does log one. A corrupted or forward-version store file would lose session color data without a diagnostic trail. Consider adding a warnOnce mirroring the group color path — not a blocker.

  • Label unification (sidebar.organizesidebar.sessionGroup) is a clean i18n consolidation — two keys for one concept becomes one.

Tests

packages/core:
  session-organization-service.test.ts — 23 passed ✓

packages/cli:
  server.test.ts — 599 passed ✓
  acp-http/transport.test.ts — 264 passed ✓

packages/web-shell:
  WebShellSidebar.test.tsx — 31 passed ✓

Build ✓  Typecheck ✓  Lint ✓  Total: 917 tests passed

Real-scenario testing

This is a web-shell browser UI feature (sidebar session groups/colors). It cannot be meaningfully tested via tmux capture-pane — the feature lives in a browser, not a terminal. The unit tests above cover the data flow end-to-end (core → CLI API → web-shell component), including the color-tagging UX, mutual exclusivity, section rendering, and keyboard navigation. The PR author ran the suites locally on macOS; CI covers Windows and Linux.

中文说明

代码审查

独立方案(在读 diff 之前):在会话组织中加入 color 字段,在 API 边界用现有 GROUP_COLOR_OPTIONS 校验,在侧栏渲染颜色分区,在现有分组弹出面板中加入颜色快选。颜色与命名分组在 UI 上互斥,但存储层只记录调用方提供的值。

PR 的做法与此基本一致。

  • 互斥仅在 UI 层。 核心服务保持通用,分类规则由展示层负责。
  • 向后兼容干净。 color 可选,未知颜色归一化为 null
  • 颜色分区仅在有会话时渲染。 合理。
  • 无严重 bug。
  • 过滤一致性: session-list.ts 正确地将带颜色标签的会话从 ungrouped 和命名分组过滤中排除。
  • 小诊断缺口(非阻塞): readStore() 中未知会话颜色静默归一化为 null,没有 warnOnce。建议添加。
  • 文案统一 是干净的 i18n 整合。

测试

所有 917 个测试通过。构建 ✓ 类型检查 ✓ Lint ✓

真实场景测试

浏览器 UI 功能,无法通过 tmux 测试。单元测试端到端覆盖。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

This is a well-scoped feature PR that does what it says: adds lightweight per-session color tagging alongside the existing named-group system, plumbs it through the daemon, and integrates it into the web-shell sidebar. The mutual-exclusivity design (color XOR named group in the UI, both allowed in storage) is the right call — it keeps the core service generic while enforcing a clean taxonomy at the presentation layer.

Every layer does the minimum it needs to. The core change is ~40 lines of source code. The API boundary validates with existing GROUP_COLOR_OPTIONS. The web-shell component reuses the existing group popover rather than adding a new UI surface. The label unification (Move to groupGroup) is a small but welcome cleanup.

Build, typecheck, lint, and all test suites pass cleanly (917 tests total across core, CLI, and web-shell).

The only testing gap is that this is a browser UI feature — I can't meaningfully capture the sidebar rendering via tmux. The unit tests cover the data flow and component behavior, but a visual check in a real browser would be the final confirmation. That's a standard limitation for web-shell PRs and CI will catch cross-browser issues.

One minor suggestion (non-blocking): the readStore() path normalizes unknown session colors to null without a warnOnce, unlike the group-color path which logs. Adding a matching warnOnce would make forward-version store corruption easier to diagnose.

No concerns. Ships the feature cleanly, the code is straightforward, the tests are thorough.

中文说明

这是一个范围合理的 feature PR,做了它说要做的事:在现有命名分组系统旁加入轻量级每会话颜色标签,通过 daemon 打通,并集成到 web-shell 侧栏。互斥设计(UI 中颜色或命名分组二选一,存储层都允许)是正确的——保持核心服务通用,在展示层执行干净的分类。

每一层只做最少需要做的事。核心改动约 40 行源码。API 边界用现有的 GROUP_COLOR_OPTIONS 校验。web-shell 组件复用现有的分组弹出面板。文案统一(Move to groupGroup)是小而受欢迎的清理。

构建、类型检查、lint 和所有测试套件均通过(共 917 个测试)。

一个小建议(非阻塞):readStore() 路径在归一化未知会话颜色时缺少 warnOnce,建议添加以便诊断。

没有顾虑。功能干净交付,代码直接,测试充分。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, looks ready to ship. ✅

@qwen-code-ci-bot qwen-code-ci-bot added scope/session-management Session state and persistence scope/web-shell type/enhancement Non-bug improvement or optimization labels Jul 5, 2026

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Downgraded from Request changes to Comment: self-PR. The inline comment still marks the blocking issue.

Comment thread packages/cli/src/serve/server/session-list.ts Outdated
Follow-up to the ungrouped filter fix: the per-group filter (group=<id>) also
ignored color precedence. Core and the REST/ACP update paths can persist both
groupId and color, and the sidebar renders such a session in its color bucket,
so group=<id> API consumers saw a session the web-shell shows elsewhere.
Require `color == null` there too, matching the sidebar taxonomy
(color > group > recent).

Adds an ACP session/list test for a session with both groupId and color set.

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No review findings. Downgraded from Approve to Comment: CI still running.

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, looks ready to ship. ✅

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review Summary

Clean, well-structured feature addition. Color tagging is plumbed end-to-end (Core → ACP → REST → SDK → UI) with consistent mutual-exclusivity semantics and solid test coverage across all layers.

Highlights

  • Three-layer input validation chain (ACP → REST → Core) prevents invalid colors from reaching storage
  • viewOrganization normalizes unknown stored colors to null — good forward-compatibility hygiene
  • Color-first bucketing in session-list.ts filtering matches the sidebar's visual priority, keeping REST/ACP consumers aligned with the UI
  • Tests cover: assignment, clearing, invalid input rejection, ungrouped/group filter exclusion, and sidebar rendering

Optional Suggestions (non-blocking)

  1. Duplicated color listSESSION_GROUP_COLORS in the sidebar mirrors GROUP_COLOR_OPTIONS from core. The isolation comment is fair, but a future palette expansion requires updating both. Consider exporting from the SDK if this becomes a maintenance concern.

  2. DaemonSessionOrganizationResult.color is optional (?:) while SessionOrganizationView.color is required. Current sidebar code handles this gracefully via session.color && SESSION_GROUP_COLORS.includes(...), but future code paths should be aware of the distinction.

  3. Menu height estimation34 * (groups.length + SESSION_GROUP_COLORS.length + 2) + 25 packs several magic numbers; extracting named constants would improve readability (very minor).

LGTM — approve.

@wenshao wenshao added this pull request to the merge queue Jul 6, 2026
Merged via the queue into QwenLM:main with commit edc0555 Jul 6, 2026
49 checks passed
@wenshao wenshao deleted the feat/web-shell-session-groups branch July 6, 2026 02:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope/session-management Session state and persistence scope/web-shell type/enhancement Non-bug improvement or optimization

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants