Skip to content

Session tabs: readable widths, all-tabs list, context menu, pinning, spawned-session groups (#2154) - #2161

Draft
jacktrnr wants to merge 3 commits into
mainfrom
tabs-navigation-2154
Draft

jacktrnr wants to merge 3 commits into
mainfrom
tabs-navigation-2154

Conversation

@jacktrnr

@jacktrnr jacktrnr commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Closes #2154. Follow-up: #2160 (open spawned sessions as tabs on their own).

What changes for the user

With many chats open, the tab strip used to squeeze every tab toward 28px, clip titles with a fade, hide them entirely under 64px, and only show the full title after a 2-second hover. Now:

  • Readable widths. Tabs never shrink below 160px (about 18 characters), and the active tab keeps its full 224px. Past that the strip scrolls; a vertical mouse wheel scrolls it too. Truncated titles end in "…".
  • The active tab is always in view, whichever way it became active (click, keybind, palette, Sessions dropdown).
  • All-tabs list. A chevron appears once tabs overflow. It opens a searchable list of every open tab with its status dot and project; spawned sessions are indented under their parent and pinned tabs are labelled. You can also open it with mod+alt+a or from the palette ("Show all tabs"). Arrow keys plus Enter work.
  • Right-click menu on tabs: Rename, Pin/Unpin, Close, Close other tabs (N), Close tabs to the right (N), Close finished tabs (N), and Close group (N) when the tab has spawned sessions.
    • This also fixes a bug: right-clicking a tab used to open the titlebar's Customize/Reset menu, because the code checked for the wrong slot name.
  • Pinned tabs sit at the left as 36px status-dot tabs. They have no close button, middle-click doesn't close them, the bulk closes skip them, and the pin survives a reload.
  • Spawned sessions (metadata.spawned_by, written by amicode_session) are shown right after their parent, joined by a neutral underline. Dragging the parent moves the group.
  • Faster hover preview: it opens after 400ms instead of 2s, and adds a "Spawned from …" line for spawned tabs.

How it works

  • Display order is derived, not stored. titlebar-tab-arrange.ts (pure, unit-tested) computes pinned first, then each parent followed by its spawned children. The persisted order only changes when you drag. After a drop the arrangement is re-derived, so a drop across the pinned boundary settles back on its own side.
  • Store changes (context/tabs.tsx):
    • an optional pinned flag on SessionTab, kept by migrateTabs;
    • TabInfo.spawnedBy, a cached spawn parent;
    • setPinned;
    • closeMany, which skips pinned tabs, pushes every closed tab onto the reopen stack (mod+shift+t brings them back), and re-navigates only if the active tab was closed.
  • Spawn stamp lookup. The sync layer resolves sessions through the v2 API, and normalizeSessionInfo drops metadata. So each session tab asks the legacy GET /session/:id once and caches the answer on its tab info. "Not spawned" is only cached for sessions older than 2 minutes, because a fork gets its stamp in a follow-up PATCH.
  • "Close finished" closes only tabs whose status is idle (done and already seen). It never closes the active tab, or a tab that is running, waiting on you, errored, unread, or whose status isn't known yet.
  • Upstream file copied into the overlay: titlebar-tab-popover.tsx (delay plus the note line).

Verification

  • tsgo -b (app typecheck) is clean. I also confirmed it catches a deliberately planted error.

  • oxlint on the touched files: 0 errors, and no new warnings from new code.

  • Unit tests: bun test over src/components, src/context and src/pages/layout gives 1348 pass and 3 fail. The 3 failures (sync-optimistic ×2, server-session live file-edit route) are in upstream-only tests of sync code this PR doesn't touch, and they also fail when run on their own.

  • E2E: the new e2e/regression/tab-strip-organize.spec.ts has 7 tests covering:

    • the width floor and overflow list with search and select;
    • spawned-session grouping;
    • pin from the menu, compact rendering, and persistence across a reload;
    • close others keeping pinned tabs, plus reopen;
    • close group;
    • the keybind opening the list without overflow;
    • right-click showing the tab menu.

    It passes 14/14 with --repeat-each 2 (Playwright with the local Chrome channel).

  • tab-dblclick-close.spec.ts was stale: it asserted that double-click closes a tab, which the .18 rework replaced with rename. It is replaced by tab-close-gestures.spec.ts (middle-click, close button), 2/2. The extension's titlebar_dblclick_close.test.ts source check still passes.

  • tab-navigate-mousedown.spec.ts › "keyboard navigation follows the visible tab order" also fails on main (it never navigates there). With this branch it navigates to the still-loading tab, because that tab renders a visible slot. I left it as is; it needs its own look.

  • Visual check: screenshots in light and dark of the strip, hover preview, menu and list with 12 tabs. The group underline uses --v2-border-border-strong (no yellow on light), and pinned and status dots use the existing activity tokens.

  • refresh_manifest.mjs and drift_gate.mjs pass.

Not in this PR

Jackson Turner added 3 commits November 8, 2026 10:28
…re (#2154)

Pure display-order derivation (pinned first, spawned sessions after their
parent) and bulk-close selectors, plus store support: an optional pinned flag
that survives migration, a cached spawnedBy on tab info, setPinned, and
closeMany (skips pinned tabs, records every closed tab for reopen).
- tabs keep a 160px floor (the active tab its full 224px); past that the
  strip scrolls (vertical wheel too) instead of squeezing to a dot
- truncated titles end in an ellipsis; the active tab is always scrolled
  into view, however it became active
- an all-tabs list (search, status, project) from a chevron once tabs
  overflow, and from mod+alt+a / the palette
- a tab context menu: rename, pin, close, close others / to the right /
  finished / group; fixes the selector that opened Customize on tabs
- pinned tabs: compact status-dot tabs at the left, no close affordance
- spawned sessions (metadata.spawned_by) shown after their parent with a
  shared underline
- hover preview after 400ms instead of 2s; tabs expose their full title
…k-close spec (#2154)

tab-dblclick-close asserted double-click closes a tab, which the .18 tab
rework replaced with rename; tab-close-gestures covers the current close
gestures (middle-click, the close button). Manifest refreshed.
@coderabbitai

coderabbitai Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Session tabs: readable widths, overflow list, context menu, pinning, spawned-session grouping

1 participant