Repository navigation
Conversation
Tooltip's Kobalte trigger renders as a div, which the HTML content model forbids inside buttons. triggerAs lets a call site request a span trigger; upcoming tab-group badges use it to show the project name on hover from inside their header buttons.
An opt-in Settings > General preference clusters session tabs under per-project headers in both horizontal and vertical layouts. Horizontal groups badge with a stable project color and members carry it as an accent strip; vertical groups stay compact and drop the per-row project icon, with a panel toggle sharing the setting. Membership derives from each tab's session directory, so worktree and sandbox tabs join their project. Dragging a header reorders the whole project, dragging a tab reorders it within its group, and cross-group drops snap back; the grouped view is a stable partition of the persisted order. Collapsed members leave the tab cycle, and collapse state persists per window.
There was a problem hiding this comment.
Grouping works as described. In the e2e harness (Chromium) the PR's new tabs.spec.ts and settings.spec.ts grouping cases pass in both layouts. The unit tests and typecheck for packages/app and packages/ui are clean.
Public API: unchanged. The only new keys are appearance.tabGroups in the app's local settings and a window-scoped tabs.groups persisted store.
Two behaviour gaps with collapsed groups are worth fixing before this lands (see the inline comments):
- Collapsing the group that holds the current tab, or opening a session whose group is already collapsed, hides the active tab, and nothing shows where it went. I reproduced this: the URL stays on the session, but no tab or header is marked current (screenshots below).
- Collapsed members are unmounted. Their background refresh (permissions, forms, pending work) and unread/busy tracking stop, and the header doesn't show any of that state, so a collapsed project can need attention without the user seeing it.
|
|
||
| if (!result) return props.tabs | ||
| return [ | ||
| ...result.groups.flatMap((group) => (isGroupCollapsed(group.id) ? [] : group.tabs)), |
There was a problem hiding this comment.
When the current tab belongs to a collapsed group, it's dropped from the strip and from visibleTabs. Nothing then marks where the user is, and the keyboard cycle starts from a tab that isn't in the list. This happens when you collapse the active tab's group, and also when you open a session (from Home, a new session, a link) whose group is already collapsed. Could the current tab stay visible inside a collapsed group (as browsers do), or could navigating to it expand the group? At minimum, the header should show that it holds the active tab.
| vertical={vertical()} | ||
| onToggle={() => toggleGroupCollapsed(group().id)} | ||
| /> | ||
| <Show when={!isGroupCollapsed(group().id)}> |
There was a problem hiding this comment.
Collapsing unmounts each member's SessionTabEntry. That entry is what runs the background refresh of permissions, forms and pending work for inactive tabs, and what reports unread (which the unread-tab cycle uses). So a collapsed project stops being refreshed, and its unread/busy state is frozen. The header doesn't summarise the members' state either. Could you keep the entries' state tracking alive while hidden, and show an aggregate busy/unread indicator on the collapsed header?
|
Hey @liambreen, I saw your PR got closed inadvertently — the opencode-agent review was positive and the implementation looks really solid. The two UX issues flagged (collapsed-group-with-active-tab visibility, and collapsed-group unmount vs hide) seem fixable. This feature would make a huge difference for those of us coming from Codex who work across multiple projects daily. Any chance you'd consider re-opening this (or #57895 for Home grouping)? Happy to help test if that moves things forward. |
|
Hey, Yes - the PR was submitted prematurely - I will finish it off this weekend and re-submit.Thanks!LiamOn Oct 24, 2026, at 02:45, TylonHH ***@***.***> wrote:TylonHH left a comment (anomalyco/opencode#57896)
Hey @liambreen, I saw your PR got closed inadvertently — the opencode-agent review was positive and the implementation looks really solid. The two UX issues flagged (collapsed-group-with-active-tab visibility, and collapsed-group unmount vs hide) seem fixable.
This feature would make a huge difference for those of us coming from Codex who work across multiple projects daily. Any chance you'd consider re-opening this (or #57895 for Home grouping)? Happy to help test if that moves things forward.
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: ***@***.***>
|


Issue for this PR
Refs #55446, refs #51759. Split out of #57888 per review feedback, together with #57895 (the Home list half of the same feedback). Stacked on #57894 — its one
packages/uicommit shows in this diff until that merges first.Type of change
What does this PR do?
Adds a
Settings → General → Group tabs by projectpreference (default Off) that clusters session tabs under per-project headers in both horizontal and vertical layouts. All three strip mounts (titlebar, vertical sidebar, mobile drawer) share the implementation.tabs.infofallback; drafts use their directory), so worktree and sandbox tabs join their parent project, and group ids include the server so the same path on two servers stays separate. Unresolved tabs trail the groups unheadered.data-project-variantattribute inproject-avatar.css; the hover tooltip uses thetriggerAsspan trigger from feat(ui): allow span tooltip triggers inside buttons #57894.shell/titlebar/tab-groups.ts, unit-tested without the component graph.How did you verify your code works?
bun typecheckinpackages/appandbun run checkat the repo root, both green.tab-groups.test.ts(partition order, per-server identity, variant stability, header drags carrying collapsed members, within-group drags, cross-group and boundary rejection) plus the settings model recovery case.tabs.spec.ts(clustering with a sandbox session unified, collapse, cycle-skip, reload persistence, badge/tooltip/accent assertions, and a pointer-driven drag case covering within-group reorder, group swap by header drag, and cross-group snap-back — in both layouts) and a preference row case insettings.spec.ts. 61 keepers pass locally; the only failures are five keybind-driven ones that fail identically ondevon this machine (mac chromium environment).Screenshots / recordings
Horizontal — one-char badges, color-matched accent strips:
Horizontal badge with the project name on hover:
Vertical — compact headers, icon-free rows, panel toggle:
Vertical with one group collapsed:
Checklist
Added by the opencode bot (AI-generated).
Fixes #44589
Related: #51759, #55446