cmux-next: saved environments in the New Tab environment picker (rebuilt from #20831)

Merged
TE
17d ago
+3385-70

Rebuilds #20831 on the environment picker that #22131 landed. #20831 was stacked on the closed Labs PR #20452 and no longer merges (704 conflicting chunks in the agent pane catalog), so this branch starts fresh from feat-cmux-next and keeps only what #22131 doesn't already do: saved, named environments.

What changes

With preview features on (labs.previewFeatures, off by default), the New Tab page's environment picker gains:

  • A Saved section over the machines. Each row is a saved environment: a name, plus either this computer and a folder (optionally a new git worktree of it for each chat), or an SSH machine (an acpmux ssh:// peer) and a folder there. Search covers them; ⌥⌘1–9 still number the machines only.
  • Create environment under New environment, and Edit environment while a saved one is picked. Both open an inline panel under the composer (name, Local / SSH, host, folder, Use worktree; Remove when editing). Escape cancels, ⌘↵ saves.
  • While a saved environment is picked, the strip names it and the folder and New Worktree controls step aside. Picking a machine, or a project anywhere on the page, clears it.

Asking with one picked:

  • SSH: chat.new with its peer and folder. The prompt is sent only once that start returns a chat (the page's chat.new now resolves to the chat it started), so a refused or held start doesn't land in a local chat.
  • Local: its folder goes through the usual folder check, even when the tab already sits there.
  • Local + Use worktree: the existing New Worktree path runs first. Its refusal shows above the card, the prompt stays in the field, and the create buttons keep working.

The host keeps the environments in environments.json beside the tag's sidebar snapshot.

  • Writes are 0600, through a temp file and rename, from an actor.
  • Entries are read one by one: one this build can't read is kept in the file, not dropped.
  • environment.list/save/remove answer only a New Tab page with no chat. Keys are fixed, and an unknown key at any depth is refused. Caps: name 80, host 256, path 4096. The id must be a UUID, the folder absolute or ~/…, and each kind keeps only its own fields.

Left out of #20831, on purpose

  • Environment variables, Cloud network access and setup script: #20831 saved them but never applied them to a chat (its own "Not yet" list).
  • Cloud kind: it only ran cmux Cloud…, which #22131's New environment already offers.
  • Labs infrastructure (labs.savedEnvironments, the New Tab Labs list, newTab.setLab): that came from the closed #20452. This uses #22131's preview gate.

Red / green

  • webviews/test/new-tab-saved-environments.test.tsx drives the real NewTabScreen and screenActions over a mock host. Red on 26b42e3382 (tests only): 1 pass, 7 fail; the previews-off test passes by design. Green on the head: 12 pass.

  • The tests added with the review fixes fail with those fixes stashed (3 fail, 8 pass, at 048ff177e9).

  • SavedEnvironmentsTests (Swift, at the page-request boundary) covers:

    • save, list order and replace
    • kind-specific fields
    • contract refusals
    • remove
    • a chat page is refused
    • entries a newer build wrote survive a save
    • the 0600 file

    Not run here (no Swift builds on this Mac); CI's Swift jobs are the first run.

Verification

  • Web tests: environment-picker.test.tsx plus every new-tab-* test: 150 pass, 0 fail.
  • Type check: bunx tsc --noEmit -p . is clean.
  • Format: vp check flags only base's own unformatted src/pages/settings/* files.
  • Gates: prepush-gate.py reports no new violations; lint-ios-package-conventions.sh is OK.
  • Base reds: check:text-focus and check:dialog-keys fail only on Swift files this PR doesn't touch. gallery-coverage fails only on base entries; the new SavedEnvironmentPanel has its gallery entry.
  • Localization:
    • check-l10n.sh: 0 errors. Every new key is in all 21 locales.
    • Ten keys reuse #20831's translated entries as they were: environment.create/edit/new/saveFailed/name/host/chooseHost/connect/useWorktree/remove, plus agentPane.error.environment in the Swift catalog.
    • composer.envSaved is new: English translated, the rest needs_review.
  • Review: a review subagent read 26b42e3382..cb4da4a045 and found 2 likely Swift compile errors and 7 behavior risks. All are fixed in 048ff177e9 except one nit: a peer chat ignores the model chip, as peer chats already do. A re-review of that fix commit found a missing public import Foundation and a held peer start that still sent; both are fixed at the head.

Observed / Not run

  • Observed: the web tests, type check, lints and gates above.
  • Not run: Swift compile and tests (CI runs them), real-app captures, and an end-to-end SSH start. Gallery entries (agent-pane.saved-environment-panel and two new agent-pane.composer-context variants) are for the gallery advisory run's screenshots.
  • Risk left: the page's chat.new action now resolves to the started session id instead of undefined; no caller read its old value.

Proof

Screenshots come from the gallery advisory run on this PR: agent-pane.composer-context (saved-environments, saved-environment-picked) and agent-pane.saved-environment-panel.

Changelog

  • Added: Preview: saved environments on the New Tab page. Name a folder on this Mac (optionally in a new worktree per chat) or a folder on an SSH machine, and pick it from the environment picker.

🤖 Generated with Claude Code