Skip to content

feat(app): group sessions and tabs by project - #57888

Closed
liambreen wants to merge 10 commits into
anomalyco:devfrom
liambreen:sessions-by-project
Closed

liambreen wants to merge 10 commits into
anomalyco:devfrom
liambreen:sessions-by-project

Conversation

@liambreen

Copy link
Copy Markdown

Sessions and tabs grouped by project, opt-in on both surfaces.

Summary

Two default-off features, both driven by feedback in #55446 and #51759:

1. Group session tabs by project (Settings → General → Group tabs by project)

Session tabs cluster under per-project headers in the tab strip, in both horizontal and vertical layouts — like browser tab groups. All three strip mounts (titlebar, vertical sidebar, mobile drawer) share the implementation.

Horizontal Vertical
Group header One-character badge in the project's color; full name on hover Compact 20px row: chevron + project avatar + name
Member tabs Carry the group's color as a thin accent strip Drop the per-row project icon (the header already names the project); a grouping toggle sits on the panel next to New session
Collapse Chevron rotates; members unmount Same, and collapsed members leave the ctrl+tab / alt+arrow / mod+N cycle

Membership is derived from data, never managed by hand: each tab resolves through the project inventory (loaded session → persisted tabs.info fallback; drafts → their directory), so worktree and sandbox tabs join their parent project's group instead of splitting it. Group ids include the server, so the same path on two servers stays separate. Unresolvable tabs trail the groups unheadered.

Drag and drop keeps working while grouped: dragging a header reorders its whole project (collapsed members follow); dragging a tab reorders it within its group. Cross-group drops snap back — a tab cannot change projects by dragging. The grouped view is a stable partition of the persisted tab order, so turning grouping off restores the user's own arrangement exactly.

Group colors use the project's own icon color when set, otherwise a stable per-group hash onto the non-gray avatar palette, exposed via a data-project-variant attribute so badges and tab accents reuse the avatar tokens. Tooltip gains a triggerAs option: the horizontal badge's hover-name needs a trigger inside a button, which Kobalte's default div trigger would make invalid HTML, and wrapping the button itself would trip the tooltip component's expanded-disclosure suppression.

2. Group Home sessions by project

A folder-icon toggle in the Home search row switches the sessions list between the existing date groups and per-project groups: collapsible compact (20px) headers, groups ordered by most recent session, rows packed tight and indented under their header's chevron column. Grouped rows drop the per-row project suffix (search results keep it — that list spans projects). Mode and collapsed state persist per user.

Closes #55446. Refs #51759 (this clusters the strip; that issue's "projects as top-level tabs" IA is a larger change).

Screenshots

Tab groups, horizontal — one-char badges, color-matched accent strips:

Horizontal tab groups

Horizontal badge with the project name on hover:

Badge name on hover

Tab groups, vertical — compact headers, icon-free rows, panel toggle:

Vertical tab groups

Vertical with one group collapsed:

Vertical collapsed

Home sessions grouped by project:

Home grouped

Implementation notes

  • New leaf modules keep the semantics unit-testable without the component graph: shell/titlebar/tab-groups.ts (partition, identity, variant hashing, constrained reorder) and home/sessions/grouping.ts (Home partition).
  • The strip renders grouped content through positional Index so group rebuilds never recreate tab slots mid-gesture, and a sequence memo mirrors the rendered order to keep sortable indexes and drag results in sync with the DOM.
  • Grouped drag results write through to the persisted tab order (reorderGroupedStrip returns the full key order or undefined to snap back), reusing the existing onReorder path; ungrouped drag behavior is untouched.
  • English i18n keys only, per the localization workflow.

Testing

  • Unit (12 + 3 cases): stable partition and first-occurrence order, sandbox sessions joining their parent group, per-server keying, variant stability, grouped reorder semantics (header moves with collapsed members, within-group moves, cross-group and boundary rejection), invalid-preference recovery.
  • E2E keepers: tabs.spec.ts gains clustering/collapse/cycle-skip/reload cases plus a pointer-driven drag case (within-group reorder, header-drag group swap, cross-group snap-back) in both layouts; home.spec.ts gains the toggle/order/collapse/persistence case with compact-header assertions; settings.spec.ts gains the preference row case.
  • bun run check is green at HEAD; app and ui typechecks green. On this dev machine the only failing keepers are five keybind-driven tests that fail identically on dev (mac chromium environment; they pass in CI's Linux runner).

Notes for review

  • Membership being derived is the main deliberate trade-off: zero group management, but tabs can't be dragged between projects. desktop/web: group sidebar sessions by project like Codex Projects view #55446 asks for Codex-style groups; this gets the grouping without a second source of truth to keep in sync with session directories.
  • CONTRIBUTING flags UI features for design review — visuals deliberately follow the existing compact sidebar language (20px/11px headers, avatar color tokens, standard icon sizes), and every surface stays default-off. Happy to adjust details.

An opt-in toggle in the Home sessions header switches the list between
the existing date groups and per-project groups. Project groups show
the project's avatar and name, order by most recent session, resolve
worktree and sandbox sessions to their parent project, and collapse
individually. Grouped rows drop the per-row project suffix; search
results keep it. The mode and collapsed groups persist per user.

Refs anomalyco#55446
The project group header's avatar and name started further right than
the session rows below it, so the tree read inverted. The header chevron
now sits in a fixed-width column and grouped rows indent by the same
column, aligning each row's avatar under its group's project avatar in
both normal and rename layouts.
An opt-in Settings > General preference clusters session tabs under
their project in the tab strip, in both horizontal and vertical layouts,
like browser tab groups. Group headers show the project avatar and name
and collapse individually per window; collapsed members leave the
visible-tab cycle. Membership derives from each tab's session directory
(or the draft's directory), so groups need no management: the grouped
view is a stable partition of the persisted order and drag-reorder
pauses while grouping is on, restoring the user's own order when off.

Refs anomalyco#51759
…ped drag

Groups now key by the resolved project instead of the session directory,
so worktree, sandbox, and draft tabs join their project's group instead
of splitting it. Horizontal groups badge with a stable project color and
member tabs carry it as an accent strip; vertical groups drop the
per-tab project avatar (the header names the project), shrink their
headers to 20px, and gain an on/off toggle on the panel that shares the
Settings preference. Drag and drop works while grouped: dragging a
header reorders its whole project and dragging a tab reorders it within
its group, with cross-group drops snapping back.
Adds reorder tests for the boundary contracts the review flagged: a tab
dropped at a foreign group boundary is rejected, a tab dragged to its
group's first and last slot reorders it, and a header drop that leaves
the header order unchanged is a no-op. Documents the one-frame
visibility window in the sortable sequence and the vertical/horizontal
header design split.
Horizontal tab groups show a compact one-character badge — the project
mark in the group color — with the project name on hover; the tooltip
wraps the badge mark because the header's own expanded state suppresses
tooltips on its trigger. Grouping toggles use the standard 16px icon,
matching the New session affordances they sit beside. Home project
headers compress to 20px with 11px labels and the grouped rows pack
close: one pixel of lead-in and 8px between groups.
The badge's hover tooltip rendered Kobalte's div trigger inside the
group header button, which the HTML content model forbids. Tooltip
gains a triggerAs option and the badge passes "span", keeping the
trigger phrasing content. Declined with rationale: the post-click
tooltip block is the component's behavior on every tooltipped control,
and the badge avatar stays aria-hidden because the button's aria-label
carries the name.
@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label Oct 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 72 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@opencode-agent opencode-agent Bot closed this Oct 21, 2026
@opencode-agent

Copy link
Copy Markdown
Contributor

Thanks for putting this together, @liambreen. The grouping logic, tests and screenshots are really thorough!

At ~950 changed lines (not counting tests), this is too big to review well as one pull request. It also contains two features that work on their own. Could you split it into smaller, focused pull requests? For example:

  • Group Home sessions by project: home/sessions/grouping.ts, the controller/view/region changes, the i18n keys for it and the home.spec.ts case.
  • Group session tabs by project: shell/titlebar/tab-groups.ts, the tab strip/nav/titlebar changes, the Group tabs by project setting, the project-avatar.css variant tokens and the tabs.spec.ts / settings.spec.ts cases. If this one is still large, the small Tooltip triggerAs change in packages/ui could go first as its own PR, and grouped drag-and-drop could follow separately on top of basic grouping and collapsing.

You're welcome to open those, and you can link back here and to #55446 for context.

@liambreen liambreen mentioned this pull request Oct 21, 2026
3 of 6 tasks
@liambreen

Copy link
Copy Markdown
Author

Split per the review feedback: #57894 (tooltip triggerAs), #57895 (Home sessions grouping), #57896 (tab strip grouping, stacked on #57894). Closing this one in favor of those.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs:compliance This means the issue will auto-close after 2 hours.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

desktop/web: group sidebar sessions by project like Codex Projects view

1 participant