feat(cmux-next): saved environments on the New Tab page, behind Labs (cx-ncc.104)
Closed
TE
17d ago+10401+10401+01234567890123456789012345678901234567890123456789-117-117-012345678901234567890123456789
Bead: cx-ncc.104. Stacked on #20452 (Labs); this diff includes its commits until #20452 lands.
What changed
New Tab gets saved environments behind the Labs flag Saved Environments (labs.savedEnvironments, nightly-next cohort). With the flag off, nothing changes.
- Choose environment ⌄ in the New Tab composer strip opens a searchable menu.
- Saved environments are grouped Local / SSH / Cloud, with a check on the picked one.
- The menu also has No environment, Edit environment and New environment.
- Once picked, the strip shows the environment's name in place of Folder · Computer.
- The chip's right-click menu has Edit / No environment / New / Remove.
- New environment opens an inline panel on the page, not a modal. It has tabs:
- Local: name, folders (recent folders or Choose folder…), a per-folder ⋮ with Use worktree / Remove, and environment variables.
- SSH: name, a host (the machines the Computer menu lists, plus SSH…), folders on that host, and environment variables.
- Cloud: name, network access (None / Limited with allowed domains / Full) and a setup script.
- Remote: a short line plus Connect a machine…, which runs the existing
remote.connect.
- Asking with an environment picked goes through the existing
chat.new, so it works for every harness:- Local starts in the first folder, or in a new worktree of it via
git.worktree.create. - SSH starts on the environment's machine (
peer). - Cloud runs the existing cmux Cloud… action.
- Local starts in the first folder, or in a new worktree of it via
- Host storage:
environment.list/environment.save/environment.remove, New Tab page only.- Fixed param keys; unknown keys are refused at any depth.
- Caps: name 80, 16 folders, 32 env vars with POSIX keys, 64 domains, setup script 16 KiB.
- Stored in
<App Support>/<bundle id>/environments.json, written 0600 (temp file, fsync, rename), with file IO in an actor.
Not yet (follow-ups on the bead):
- Env vars and extra folders: saved, but not yet applied to the agent. acpmux takes per-session env only from the app's unix socket and does not forward
additionalDirectories. The follow-up PR covers that, with its security analysis. - Select branch: hidden until the New Tab page can list a folder's branches and
git.worktree.createtakes a base. - Cloud network settings: saved, but not applied to the machine yet.
Red / green
webviews/test/new-tab-saved-environments.test.tsxcovers five behaviors:- flag off: no chip and no calls
- grouped and searchable menu
- Local with two folders, one in a worktree, saves the contract shape and names the strip
- SSH asks with
peer - No environment clears it
- Red on 56794b8a3783 (tests only): 1 pass, 4 fail. The flag-off test passes by design. Green on d43d609bd8ee: 5 pass.
SavedEnvironmentsTests(Swift, at the page-request boundary) covers five behaviors:- a save gets an id and time, newest first
- contract breaks are refused
- remove deletes
- a chat page cannot read or edit environments
- the file is 0600
- Red: it does not build without the feature. Green runs in hosted CI.
Verification
sbx build(CmuxNext sources): clean.- God-file check against base: passes. Wiring avoids the over-budget
AppServices/AgentTabStore. - Crash ratchet: passes.
lint-ios-package-conventions.sh: passes.gen-settings-sources.py --checkandgen-strings.mjs --check(all pages): pass.ui-rulesand title-tooltip rules: pass.- Concurrency check: reds only in base's
IdleOwnerRestart.swift/DaemonConnection+QuietHandoff.swift, which #20767 fixes. - Typecheck and lint run in CI.
- Real-app captures (light, dark and a clip) follow in this body.
Localization
- 36
environment.*pane strings and the Labs title/help in all 21 locales (needs_review). agentPane.error.environmentin 21 locales.- Generated strings regenerated for all pages.
Changelog
- Added: Labs: saved environments on the New Tab page: pick a saved set of folders and machine when you start a chat.
🤖 Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Adds saved environments to the New Tab page behind the Labs flag savedEnvironments (nightly-next cohort), and carries the Labs flag registry from #20452; with the flag off, nothing on the New Tab page changes.
Saved environments
- "Choose environment" in the composer strip opens a searchable menu grouped Local / SSH / Cloud, plus Edit and New environment; until one is picked it is a quiet glyph, so Folder and Computer keep their room; "New environment" opens an inline panel with tabs per kind.
- Environments store a name, folders (a folder may ask for a new worktree), environment variables, and for SSH a host; Cloud adds network access and a setup script.
- Starting a chat with an environment picked reuses the existing
chat.new: Local begins in the first folder (in a new worktree viagit.worktree.create), SSH on the environment's machine (peer), Cloud via the cmux Cloud action. - Environments persist in
<App Support>/environments.jsonthroughenvironment.list/environment.save/environment.remove, New Tab pages only; writes are atomic in an actor, the file is 0600, and at most 200 environments are kept inCmuxNextAgentPane. - Caps: name 80, 16 folders, 32 env vars, 64 domains, setup script 16 KiB; unknown param keys are refused at any depth.
- Follow-ups: env vars, extra folders and Cloud network settings are saved but not yet applied to the agent; branch selection is hidden until
git.worktree.createtakes a base.
Labs flags
schemas/settings/source/labs.jsonis the single registry for opt-in features (owner, created, expires, cohort, intent); the generator emits a SwiftLabsFlagregistry and the web flag table.- Swift reads a flag with
snapshot.lab(_:teamBuild:), pages readdata-labson<html>; the existinglabs.previewFeaturesmoves onto the registry. - Flags are agent-settable, appear in Settings > Labs, flip from the palette, and Help > Labs… opens the list; in team builds the New Tab page shows quick toggles.