Repository navigation
feat(tabs): start a session beside any tab from an opt-in + in its actions - #712
JDProfresh wants to merge 2 commits into
Conversation
…tions Opening another session in the case you are already working in meant re-picking the case and the mode in the toolbar. An opt-in + now sits with each tab's gear and close icons (App Settings, Tabs, New Session Button on Tabs: per device, off by default everywhere). It opens a menu of the CLIs that tab's case can start, each shown with its logo and name as in the Run menus, and a pick starts one session in that tab's case, or the managed worktree it runs in, and in its named group, without changing the Run button's mode or case. The CLIs offered follow the Run menu's own rule for the case, so a Docker case lists what its container has rather than what the host has. A tab outside any named group sends no group, so the case's default session group places the new session the way it places a toolbar Run. Wherever a tab has a row menu (the vertical rail, the sidebar) that menu gains "New session here…", the keyboard route to the same picker, since Enter on the + itself does nothing. On a phone the + joins gear and close on the active tab only, and the label reserve grows to 68px while the setting is on, so the tab's centre stays off the icons. The shared tab layout menu no longer closes when focus leaves an item for nowhere: Safari never focuses a clicked button, so pressing an item blurred the focused one with no related target and the click was lost. This is the tile count menu's rule, and it fixes the named-group menu as well.
|
CI's one red here is The test outwaits the server's settings cache with a fixed sleep: |
|
Thank you @JDProfresh for this. It adds an opt-in + to each session tab that starts one session in that tab's case (or worktree) and named group, in any CLI the case can run, without touching the Run button. It follows the answers from #700 closely, and the tests are unusually careful: real Three small things, none of them a blocker:
The one red CI job is If you push these, I will merge once CI is green. If not, I will apply them on top at merge time. |
…container The + announces a menu but Enter and Space did nothing on it, and the header strip has no row menu to fall back on. A + on a Docker tab the toolbar has not selected read only the link-time mode list, since nothing probed its container. Opening the picker from the keyboard focuses an entry, so a held Enter or Space repeated onto it and launched that CLI; the shared tab-layout menu now ignores a repeated Enter or Space.
|
Thanks for the careful read. All three are in 4f6481b, a new commit on top (nothing rewritten), plus one thing I found along the way.
Tests, in
I removed each guard in turn: either key arm, the key check, the inline Gate: typecheck, lint, format, frontend syntax and browser-excludes are clean. |
|
Thank you @JDProfresh for the follow-up commit. The PR adds an opt-in + to each session tab (and a "New session here…" row-menu entry) that starts one session of any CLI the tab's case can run, in that case or worktree and its named group, without touching the Run button. All three points from the first review are in, and the held-key guard is a good catch. Testing the toolbar side more closely turned up two more things:
If you push these, I will merge once CI is green. If not, I will apply them on top at merge time. |
Opening another session in the case you're already working in meant re-picking the case and the mode in the toolbar. An opt-in + in each session tab's action row now starts one session beside that tab, in its case and its group, and leaves the toolbar alone. Built to your answers in #700.
What it does
Where and when. Per-device setting "New Session Button on Tabs" (App Settings, Tabs), next to
showTabDetachButton, off by default on every device. When it's on, a + sits with the tab's gear and close icons and uses the same reveal. On a phone it shows on the active tab only, and the label reserve grows from 44 to 68px while the setting is on. That keeps your arithmetic (gear 32 ++28 + close 20, less close's 2px margin) under the 80pxmax-width, andtest/mobile-tab-tap-zones.test.tspins it.Labels. The picker lists each CLI as its logo and name, in the Run menu's markup and order: agents in catalog order, the shell last.
Docker. The list is gated by
_runModeAvailableForCase, the helper the toolbar Run menu already uses. So a Docker case offers what its container has (the live probe, thendocker.availableModes, and no gating for an owned container).Which case. A tab's case is resolved by identity, not by matching paths across locations:
GET /api/casesgives remote and Docker cases display paths (user@host:/p,box:/p), so a raw path comparison would miss them, or worse, match a local case that sits at the Docker workspace's path. A tab in a managed worktree launches in that worktree.Group. A pick starts one session in the tab's named group. An ungrouped tab sends no
tabGroupIdon purpose, soTabLayoutService.sessionCreated()puts the new session in the case's default session group (feat(tabs): per-case default session group #676) when the case has one, the same as a toolbar Run.test/routes/case-default-group-routes.test.tspins both cases end to end (the real frontend in JSDOM against the real routes): an ungrouped tab lands in the default group, and a grouped tab lands in its own group.Keyboard route. While the setting is on, the session row menu offers "New session here…" wherever it shows (vertical rail, sidebar; session tabs only, never tiles), and it opens the same picker.
The toolbar is not touched.
run()takes the mode as an option (options.mode), so a + launch never swaps_runMode. The worktree launcher had the same swap, and both now go through one_launchOnce(). A side effect: picking another mode in the Run menu while a worktree or + launch is still pending used to be overwritten when the launch finished; now it stays. After a launch that names its case, the case list reloads onto the toolbar's own case. Before,runClaude()re-selectedlastUsedCase, so a Claude + in a remote or Docker case moved the toolbar to the tab's case.lastUsedCase. As you noted,POST /api/quick-startstill writeslastUsedCase(src/web/routes/session-routes.ts:4588, for an admin or in single-user mode). So a + launch through quick-start (any CLI other than Claude or Shell, or Claude or Shell in a remote or Docker case) leaves the toolbar on the tab's case after a page reload. Worktree launches already do the same. It no longer moves within the page.Safari.
_openTabLayoutMenuno longer closes on a focusout with norelatedTarget, which is the tile count menu's rule. Safari never focuses a clicked button, so the picker closed before the click landed. My iPhone 17 Pro Max (iOS 26.6.1) confirmed both the failure and the fix. The same rule now applies to the named-group menu. The "Every way in has a keyboard path" bullet indocs/architecture-invariants.mdis updated to match.Also in the PR: zh-CN for the label, description and "New session here", a
Settings-Reference.mdrow, a paragraph inThe-Dashboard.md, and the phone-reserve and group-placement sentences indocs/architecture-invariants.mdandsrc/web/CLAUDE.md.The phone overview's Run picker still gates on the host's CLIs; that's the separate fix you mentioned, not touched here.
Screenshots
Screenshots to follow: the desktop strip with the + revealed, the desktop picker, and the phone tab and picker at 440px.
Tests
test/tab-new-session.test.ts, plus end-to-end placement tests incase-default-group-routes.test.tsand additions totab-group-launch,tab-layout-editing,mobile-tab-tap-zonesandworktree-ui(the last now pinsrun({ count: 1, worktree, mode })with_runModeuntouched). The case fixtures use realGET /api/casesrecords. They include a local case at a Docker workspace's path, the same container on another engine host, the same path on another remote host, and a nested local case.npm test: 12,594 passed. Two failures also fail on unmodified master here, and neither file is touched:deepseek-transcript"decodes every frame": a Node 24.22+/26.10+ behaviour, fixed in test(deepseek): stop pinning Node's one-shot zstd decoder to the first frame #710.tui-switch-guardline 239: tmux 3.2a exits 0 onshow-options -gvof an unset option.npm run test:mobile -- test/mobile/tabs.test.ts: 30 passed and 1 failed on both the branch and master. It's the same failure: "switching tabs with the keyboard closed does not leave the terminal textarea focused".