Skip to content

ci: warm Vite before browser tests, per-file limits, 40 min cap (cx-v6fbz) - #20733

Merged
teamleaderleo merged 19 commits into
feat-cmux-nextfrom
web-check-flakes
Oct 15, 2026
Merged

teamleaderleo merged 19 commits into
feat-cmux-nextfrom
web-check-flakes

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

web / react-apps-check flaked on PRs (e.g. #20697 at 3764122, and the gallery-frame tests #20681 hit). Three causes, all from load on the 8 vCPU runner, none in the code under test:

  • Cold Vite servers. Vite prints its port before it has transformed a module, so a browser test's first page load paid for the whole module graph inside its 30 s navigation timeout. chat-find and text-field-focus failed that way: first load past 30 s, the second load 10-27 s, the rest about 2 s. The gallery frame starts every *.gallery.tsx load on first paint (src/gallery/entryStore.ts), about 2,300 modules, so the gallery-frame and agent-pane browser warm-ups hit the same wait.
  • editor-page's own size. 24 browser tests in two engines take about 115 s on CI, against the runner's 120 s per-file limit. The kill at 120 s closed the browser under the remaining WebKit tests ("Target page, context or browser has been closed").
  • The job cap. The test step alone took 22 min (277 files, about 90 of them dev-server files two at a time). The --with-deps engine install took 2.5 min on one attempt and 11.5 on the next, which left the tests 17 min, so the rerun was cancelled at 30.

Fixes, no assertion changed or loosened:

  • test/support/warmDevServer.ts: warmDevServer(base, page) / warmViteServer(vite, page) request the page and every module it imports, as the browser will, so the server caches each transform before a test's own timeouts start. 80 test files call it once their server listens (70 in-process servers, 10 spawned vp dev).
  • scripts/check-browser-test-lanes.ts (run by bun run check) now fails a test that starts a dev server without warming it. On base it flags 80 of 240 test files; on this head none.
  • scripts/ci/run-webviews-tests.sh: a file may declare a longer limit with a // cmux-web-test-timeout: SECONDS header line; editor-page declares 240.
  • diff-virtualization: the tree-row click checks the row is still under the pointer after the 150 ms settle, else finds it again (the WebKit failure read null for a header the click never opened).
  • The react-apps-check job cap raise (30 -> 40 min) moved to ci: give react-apps-check 40 minutes (cx-v6fbz) #20854, so this PR does not run the web/ unit shards, which are red on base.

Measured on the private Linux builder (idle 96 cores), cold then warm:

page modules cold warm
agent pane / 802 7.1 s 0.17 s
gallery frame 2,325 35.4 s 4.4 s
editor 1,552 2.8 s 0.36 s
diff test page 485 2.8 s 0.13 s

Verification

  • Runner: python3 -m unittest tests.test_webviews_tests_per_file (7 tests). The new per-file limit test was committed first and failed (TIMEOUT test/slow-declared.test.ts at the 2 s default), then passed.
  • Warm rule: red on base (80 files flagged), and bun run check passes on this head with the rule (format, lint, types on 1,379 files, plus layers, type scale, focus rings). Both ran on the Linux builder.
  • warmDevServer: checked against real Vite (table above) and a local fixture with an import cycle, a relative entry and a lazy import.

Not verified locally: the browser test files themselves. Big-red may not run browser suites; this PR's react-apps-check run is their first run with the change.

Follow-up

The gallery frame loads every entry to find one; loading only the requested entry would cut a cold gallery load from about 2,300 modules. That is a gallery change, not part of this fix.

Changelog

none

Bead: cx-v6fbz

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Fixes flaky browser and webviews CI tests that timed out or failed because the cold Vite dev server compiled the whole module graph during a test's first page load. Tests now warm the server before launching browsers, can declare a longer per-file limit, and the diff-virtualization test no longer clicks a tree row that has moved.

  • Added warmDevServer/warmViteServer to request a page and its imports before the first navigation; called in 81 test files (settings-scroll warms only after its readiness poll).
  • check-browser-test-lanes now fails any test that starts a dev server without warming it.
  • Test files can set a // cmux-web-test-timeout: SECONDS header for a longer per-file limit; editor-page, text-field-focus, chat-find, diff-virtualization, files-panel-coverage, ime-composition-keys, menu-focus, settings-scroll, and ui-a11y declare longer limits.
  • Fixed diff-virtualization to click a tree row only while it is still under the pointer after the 150 ms settle, climbing out of shadow roots, and re-find it when it moved.
  • Spawned-server tests and menu-focus now warm the whole gallery graph, so dev-server files declare 240 s limits; failed warm-up visits no longer leave unhandled rejections.

Written for commit 893282f. Summary will update on new commits.

View guided diff Turn on auto-fix

teamleaderleo and others added 8 commits October 14, 2026 12:47
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…imits (cx-v6fbz)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(cx-v6fbz)

Vite prints its port before it has transformed a module, so a test's first
goto paid for the whole module graph inside its 30 s navigation timeout. On a
busy CI runner that failed chat-find and text-field-focus (#20697), and the
gallery-frame and agent-pane browser warm-ups hit the same wait (#20681).
warmDevServer requests the page and every module it imports first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er the pointer (cx-v6fbz)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	webviews/test/agent-pane-session-pages.test.ts
…o (cx-v6fbz)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The browser-lane check now fails a test file that starts a dev server
(vite.listen() or a vp dev spawn) without warmViteServer/warmDevServer.
On base it flags 80 of 240 test files; on this head none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 9cb38f92-cfe9-4e43-99af-eabe0338e72e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • 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

Comment @coderabbitai help to get the list of available commands.

teamleaderleo and others added 3 commits October 14, 2026 13:42
…6fbz)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s (cx-v6fbz)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l; the diff row check climbs out of shadow roots (cx-v6fbz)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review follow-ups pushed:

  • A declared per-file limit never shortens a longer CMUX_WEB_TEST_FILE_TIMEOUT (test first: red, then green).
  • settings-scroll warms after its readiness poll, outside the poll's catch, so a warm-up failure is reported as such; hook timeout 180 s.
  • diff-virtualization's row check climbs out of shadow roots inside the row.

Reds not from this PR (this PR edits ci-web.yml, so the whole web workflow runs; most PRs skip these jobs):

  • web / Web tests (1/4, 3/4): web/tests/vm-image-manifest.test.ts fails the two devbox source-digest tests. The same two fail on base content (run on the Linux builder); this PR does not touch web/ or the devbox sources.
  • web / diff-sidecar-check: cancelling_rpc_terminates_its_process_group_and_removes_partial_patch got "No such process" for the git process group, a timing race in Native/DiffSidecar, which this PR does not touch.

@blacksmith-sh

This comment has been minimized.

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

all web/size checks green

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Passes: CI passes on 893282faf9.

CI passes on 893282faf9 (run 38665683594 attempt 1).

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

CI fast guards passes on 893282faf9 (https://github.com/manaflow-ai/cmux/actions/runs/38665683594). This covers only the fast guards, not CI: CI's result is the ci-status check, and the CI failure attribution comment names any failing test.

@cursor

cursor Bot commented Oct 14, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

teamleaderleo and others added 4 commits October 14, 2026 15:17
… 22 loads took about 115 s (cx-v6fbz)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ci-web.yml edit widens this PR's web scope to the web/ unit shards,
which are red on base. The cap raise lands separately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 15, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

Review follow-up: the spawned-server files and menu-focus warm the whole
gallery graph before their browser warm-up, which can pass the default
120 s file limit on a loaded runner. A failed warm-up visit no longer
leaves the other in-flight visits as unhandled rejections.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo merged commit f0b44b2 into feat-cmux-next Oct 15, 2026
113 checks passed
@teamleaderleo
teamleaderleo deleted the web-check-flakes branch October 15, 2026 03:13
@github-actions

Copy link
Copy Markdown
Contributor

The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/38677180348 at 70edcd5 failed: cmux-next Release compile (Xcode 26).
Those jobs last passed at 4f33baa. This pull request is one of 4 merged since: #20733, #20784, #20752, #20736.
If the failure is in your change, fix forward on feat-cmux-next. Pull requests run only the tiers their paths reach (docs/ci/cmux-next-tiers.md); label a batch PR full-ci to run them all before merging.

teamleaderleo added a commit that referenced this pull request Oct 15, 2026
check:browser-lanes (#20733) requires warmViteServer after listen; #20752,
#20780 and #20785 landed tests without it, so react-apps-check is red on base.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 15, 2026
The browser-lane check (#20733) requires a test that starts a dev server to warm
it; warmViteServer replaces the play this test used as its warm-up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 15, 2026
…r tests

check-browser-test-lanes (#20733) requires warmViteServer after listen;
claude-reasoning-default (#20592) landed without it and fails the base check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 15, 2026
check:browser-lanes (#20733) requires warmViteServer once the server listens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 15, 2026
* feat(cx-gakp): unify slash commands, skills and prompts

Keep the existing Milkdown editor and its URL, mention and attachment behavior. Label ACP command sources so skills and prompts share the same searchable picker.

* chore(next): PR autofix at cade19a

- vp fmt webviews/src/agent-session/acpmux/composer.gallery.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38535762014

* fix(cx-gakp): localize slash picker labels and cmux command descriptions

The Skill/Prompt labels and the four cmux command descriptions were
hard-coded English, which pane-english rejects. They now come from the
pane's table in all 21 languages; cmux commands carry a descriptionKey.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cx-gakp): keep Codex skills' $ prefix in the slash picker

codex-acp names each skill `$name` and passes `/$name` through to
Codex as a skill mention. Stripping the $ inserted `/name`, which ran a
same-named builtin or nothing, and deduped the skill behind cmux's own
commands. Keep the name and use the $ as the skill signal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at f4ed982

- regenerated schemas/defaults/cmux-next.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38541336447

* fix(cx-gakp): read the skill tag from ACP _meta in the slash picker

acpmux will tag Claude skills as _meta.kind (ACP keeps extensions in
_meta); read it alongside codex-acp's $ prefix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cx-gakp): slash picker play finds its rows by name; harness stays harness

- The gallery play waited for exactly 3 rows, but cmux's /import shares
  the menu; it now finds the fixture rows by name, asserts Skill and
  Prompt on their own rows and no label on a harness command, and clears
  the "/" before typing the draft.
- The comment no longer claims acpmux tags Claude skills.
- /continue's description keeps "harness" as the catalog does elsewhere
  (17 locales said "tool").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: regenerate pane strings after the base merge

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* webviews: regenerate pane strings after the base merge

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Chief registry: chief.profile.adopt makes an existing agent participant a Chief record under its own id

The owner (a trusted local Unix caller acting as the person) adopts an agent that is already in a
conversation: the record keeps the participant's id and display name, so its DMs, history and
credential stay its own, and gets chief.profile.create's defaults and only the fields given.
A page (WebSocket or the host's page relay, even with a gesture), a paired or remote device and a
bound agent get origin.forbidden; there is no page route. agent_mux is default_chief, a live record
already_chief, a deleted Chief deleted_chief (an adopt never revives its tombstone or kept memory),
and a person or an unknown id not_an_agent_participant. The write goes through the daemon's
registry ledger (idempotent per key) and the same chief_written hook as a create.
Capability chief-profile-adopt-v1; CLI cmux chief adopt AGENT [PROFILE], refused for an agent
caller. Real-daemon tests for each refusal and for an adopt that keeps the DM and its history.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chief.profile.adopt review fixes: owner gate first, any page origin refused, brain directory collisions refused, tests for the event and the limit

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chief.profile.adopt: refuse a bound participant, revoke its earlier credentials in the same write, and only the brain (or, with no supervisor, the person's terminal) mints the Chief's credential

The adopt claims the participant inside the registry write: under the bindings lock it refuses a
participant with a bound connection (participant_bound, "stop its current agent session first")
and otherwise revokes every credential issued for it. conversation-bind now checks the credential
and binds under the same lock, so a revoked credential never binds after the adopt. For a Chief
record other than agent_mux, conversation-agent-token is refused while a brain supervisor runs
(the supervisor mints the brain's credential in process); with no supervisor only the person's
own terminal mints it, never a page, a paired or remote device or an agent. Tests: a bound
participant is refused; after it unbinds the adopt succeeds and the brain starts; the old
credential is refused; the brain's credential binds; the person mints the post-adopt credential
on a daemon with no supervisor; a page, a device and an agent cannot mint it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chief.profile.adopt: the Chief-credential check and the mint run under the registry lock

Review finding: a mint that passed the Chief check before an adopt could overwrite the brain's
credential after it. conversation-agent-token now checks and mints inside one registry-locked step
(registry, then the conversation store, the adopt's order).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Regenerate bindings after merge

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chief.profile.adopt: clippy (one write struct for mutate, no redundant clone)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* resource_attach: chief.profile.adopt is the 54th connection operation

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-next: CmuxNextApp cap back to 113692 (frame-strip proof analysis and animation palette toggles leave the App target) (cx-me3xd)

The debug.new_tab frame_strip analysis (frame comparisons, PNGs, report fields) moves to
WindowFrameStrip in CmuxNextDesign beside WindowFrame and MotionFrameProgress; the drag_new
gesture moves to TabStripDebug.dragFirstTab in CmuxNextTabs. DebugFrameStrip keeps only the
wiring (record, send key or gesture, spans), so plus, drag_new and every report field stay.
The ui.animations.<kind> toggles and bounce bind through AnimationKindsSetting.bindActions
(CmuxNextSettings) on the shared SettingChoiceActions save, as ChromeStyleSetting does (ported
from nx-grow-land-fallback b1e0c989). CmuxNextApp 113810 -> 113692; the cap follows.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cx-tgf4h red: cmux chief -p under ACPMUX_AGENT_LAUNCH=deny fails at once with a typed refusal

* cx-tgf4h: the Chief says at once that agents are disabled in this build session

With ACPMUX_AGENT_LAUNCH=deny (the nx-remote gui/input default since HQ
#2130) or CMUX_AGENTS_DISABLED=1, no Chief turn can start an agent.
`cmux chief -p` started a daemon and a brain and waited 240 s for a reply
that could not come.

- cmux-tui-core agent_launch: one test of the kill switch (acpmux
  launch_policy.rs), the reason agent_launch_disabled.
- `cmux chief` (pipe and chat) refuses before it starts or sends anything:
  operation.failed, details.reason agent_launch_disabled, exit 1.
- The daemon's supervisor starts no Chief brain; chief.engine.* and
  chief.stop answer the typed refusal at once.
- optchat-chief host does not start (logs why, exit 0).
- Home: the local Chief conversation's notice says agents are disabled;
  the engine panel maps the typed refusal (localized, all Home languages).
- Every Chief proof script refuses at once under deny and names
  `nx-remote --allow-agents`.

* Channels view: gallery plays for the host's home.currentSelection (red: the page does not serve it yet)

The host-callable command type (hostActions.ts) and two plays: a conversation, the keyboard's
current message and the open thread; the docked list pane answers null. cx-hnmbt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels view: home.currentSelection for the Home dock's view switch

The page serves window.cmuxHomeActions["home.currentSelection"] (the agent page's
cmuxAcpmuxActions pattern; the host calls it with callAsyncJavaScript): {conversation,
message?, thread?} = the open conversation, the keyboard's current message or the message a
link marked, and the open thread's root; null on the narrow list pane or with nothing open.
The host never reads the page hash for the hand-off. cx-hnmbt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels view: serve home.currentSelection on the page bridge, not a window global

The host calls it with PageRouter.callPage (the typed host-to-page call the page bridge already
has; PageClient.handle on the page): {} -> {conversation, message?} | null. message = the
keyboard's current message, else the message a link marked, else the open thread's root. The
gallery plays drive a real BridgePageClient through __cmuxPageReceive, as the app does. cx-hnmbt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels view: home.currentSelection review follow-ups

Layout effect for the answered selection; null before the page's first measure (a docked list
pane never reports a hidden conversation); the gallery host uses a private bridge target; the
thread case is its own play (the gallery opens the thread). cx-hnmbt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* acpmux: Codex picks run the in-process bridge, not the npx codex-acp adapter (#20800)

* test(acpmux): the npx codex-acp adapter is offered beside the Codex bridge

Red: with codex and npx on PATH and no codex-acp, discovery keeps the
pinned npx adapter (@agentclientprotocol/codex-acp) as `codex-acp` next
to the in-process bridge. That adapter reads the account through codex's
account/read, which fails for a ChatGPT login behind a routing proxy
(subrouter): its sessions never start ("Authentication required").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* acpmux: Codex picks run the in-process bridge, not the npx codex-acp adapter

With codex installed and no codex-acp on PATH, discovery kept the pinned
npx adapter (@agentclientprotocol/codex-acp@1.10.0) as `codex-acp` beside
the in-process bridge. The adapter reads the account through codex's
account/read; with codex 0.162 a ChatGPT login behind a routing proxy
(subrouter) fails that ("selected workspace missing from routing
discovery"), so its chats never started ("Authentication required"),
while the bridge works on the same login.

Discovery no longer offers the npx adapter: an installed codex is the
bridge, and `codex-acp` names the bridge too (like `codex-app-server`), so
sessions, presets and model picks that recorded it run the bridge. A
codex-acp on PATH, an ~/.acpx entry and a config-written launch are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* acpmux: one description for the bridge's aliases (rustfmt)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* cx-tgf4h review: forward chief control to a running brain first; daemon-side deny test; engine/stop CLI text; guard before side effects; fmt

* test: warm the dev server in three agent pane browser tests (#20873)

check:browser-lanes (#20733) requires warmViteServer after listen; #20752,
#20780 and #20785 landed tests without it, so react-apps-check is red on base.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* acpmux tests: every acpmux process a test starts gets a hermetic environment (cx-s8wux)

One helper, tests/support/daemon_env.rs, starts the acpmux binary for every
integration test: env_clear, a bare PATH (python3 plus named host tools,
then the system directories), a scratch HOME, and ACPMUX_AGENT_LAUNCH=deny
unless the test drives a stand-in agent (Agents::StandIn keeps the bare
PATH). The 26 test files that started the binary with the host environment
use it; tests/hermetic_daemon_guard.rs refuses a test file that names
CARGO_BIN_EXE_acpmux anywhere else.

Root cause of cx-w014j: a fleet host's PATH holds real codex/claude, which a
test daemon discovered and ran (probes, sign-in checks, auto-updates).

* cx-tgf4h: fmt and clippy

* acpmux tests: the hermetic HOME is <scratch>/home, never a chat's folder

* acpmux tests: review fixes for the hermetic environment (cx-s8wux)

python3 links the interpreter itself (sys.executable), not a version
manager's shim; stale tool links from an earlier run are replaced; the guard
also flags daemon_env::bin() outside a listed use; the live codex test probes
only codex; codex_bridge_config's discovery test runs with launches denied.

* cmux-tui: agent-session-attach takes the agent pane's whole kind list (up to 32; the pane asks 9 since prompt bookmarks), so a remote chat shows after Trust instead of falling back to New Tab with bad request (cx-kpd0v)

* agent pane: the attach asks at most 8 kinds again (session offers come with transcript), so an older remote cmux-tui shows the chat (cx-kpd0v)

* cmux-tui: test fake needs no mut (cx-kpd0v)

* acpmux tests: router_install_secret starts the router through daemon_env (cx-s8wux)

* cmux-next composer: keep base's catalog order; regenerate strings on the merged base

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* acpmux: one typed protocol registry; generated web types and drift checks (hq#2025, no bead) (#20576)

* acpmux: typed protocol registry and IR (hq#2025, no bead)

One registry names every acpmux method and notification; sessions and
permissions carry serde/schemars types, and the daemon builds
SessionSummary and PermissionGroup from them. ir() emits
spec/acpmux-protocol.json for the web codegen. Tests fail when the
committed IR is stale or the dispatcher serves a name the registry lacks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* webviews: acpmux protocol codegen, typed request helper and drift check (hq#2025, no bead)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* acpmux protocol: committed IR, generated web types; permissions and session_changed use them (hq#2025, no bead)

The permission group client and permission_respond go through the typed
request; session_changed and permission_pending are narrowed by the
registry's notification map. The IR declares a peer summary's optional
fields and peer key, permission_pending's always-sent keys, and closed
param objects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* webviews: format direct.ts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* webviews: acpmux codegen lives beside the pane protocol codegen, outside the pane sources

Its developer-facing IR errors tripped pane-english, which scans
src/agent-session/acpmux for user-visible English.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at 1bb7576

- cargo fmt in Native/OptChat/optchat-chief

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38569912312

* chore(next): PR autofix at c601de5

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38593588859

* chore(next): PR autofix at 7a7fb66

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38606761975

* acpmux protocol: register offer_session and offer_respond

Base (#20305) serves two new methods; the registry's completeness test
caught them. Both are untyped requests for now. Spec and generated
TS are regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* acpmux protocol: register prompt_withdraw

Base serves _acpmux/prompt_withdraw; the completeness test caught it. Spec and
generated TS are regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at 4e1d3c9

- vp fmt webviews/src/pages/home-channels/HomeChannelsPage.tsx

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38637598208

* acpmux protocol: register the process methods

Base serves _acpmux/process/list and process/kill and sends
_acpmux/process_update; the completeness test caught them. Spec and generated
TS are regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at 64ca080

- vp fmt webviews/src/pages/home-channels/cloudHome.ts
- cargo fmt in Native/OptChat/optchat-chief

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38649959912

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>

* cmux-next: fix two Chief profile relay base reds (concurrency check, Release compile crash) (no bead) (#20876)

* cmux-next: mark the Chief profile relay retry loop as bounded (no bead)

check-concurrency flags its while true as a loop that can spin. It retries
once, only after a lost connection, and every other error throws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* cmux-next: nonisolated deinit for PageCallDeadline.Once (no bead)

The Release compile crashes swift-frontend 6.3.3 in EarlyPerfInliner on
the isolated deinit of this main-actor generic class (exit 65, no error
line), the same crash #20408 fixed elsewhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cmux-next): the Chief profile relay's retry is a two-attempt for loop, not while true with a wakeup-allow (cx-ebm.59.5)

* cmux-next sidebar perf round 3: Release-build hitch bench with real wheel input; O(1) hover-card hit

- scripts/cmux-next/sidebar-rows-perf-release.py: runs in a Release tagged app (no debug.* methods) inside
  nx-remote --needs input: CLI workspaces (50 with a terminal, 950 empty), HID-tap wheel events, Animation
  Hitches per gesture, A/B of two apps in one job (side by side, interleaved), traces and the recorder's ktrace
  files removed after each export, PTY count logged.
- SidebarListView+HoverCard: the hover-card hit reads the list's row dictionary instead of
  SidebarModel.isPlaceholder, an O(workspaces) section walk on every scroll frame (7-12% of main-thread samples
  while scrolling 1,000 rows in Release).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels: a primary or danger button keeps its fill under the pointer (a soft ring shows the hover); gallery play primary-hover

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels gallery: primary-hover asserts the hover ring and the unchanged fill; a budget with a reason for the Create frame

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-motion: Chrome tab strip tween (200 ms easeOutQuad, restart from value), tabStrip geometry tokens, damping <= 0 takes the fallback (hq-1d, no bead)

Additive: MotionTween {TabOpen, TabClose, TabMove}, TweenCurve::EaseOutQuad,
ease_out_quad, TweenParameters, TweenValue (f64 core: retarget restarts the
duration from the current value with no velocity; the same target does not
restart), Tween (f32, token-driven), MotionPolicy::tween (0 under speed off,
Reduce Motion, or tabOpen/tabClose with ui.animations.newTab off),
TabStripGeometry (design-tokens.json motion.tabStrip: max 200, open/close
18, min 56 active / 32 inactive / 64 pinned, reorder 16 x width / 200,
closing mode +40 below / +60 trailing / 0.3 s). cmux-app-ffi ABI 2 exports
cmux_motion_tween_{base,seconds,retarget,step}, cmux_motion_ease and
cmux_motion_tab_strip_geometry. SpringParameters::finite_or now treats a
damping fraction <= 0 like NaN (hq-4e nit). The parity corpus example
prints tween paths (60 Hz: 12 frames for 200 ms).

* acpmux: record a session's git branch and HEAD at start (hq#1920) (#20840)

* test(acpmux): record a session's git branch and head at start

A session should keep its folder's branch and HEAD commit as they were at
session/new, and a fork its own folder's state at fork time (hq#1920), so
agent spend can be joined to the pull request a session produced. These
fail today: neither the saved meta nor _acpmux/info carries gitBranch or
gitHead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(acpmux): record a session's git branch and head at start

session/new and session/fork now store the cwd's checked-out branch and
full HEAD commit in the session meta (gitBranch, gitHead), so a session
can later be joined to the pull request it produced (hq#1920). The values
are read once, as the session starts, from the repository's own files
(HEAD, loose refs, packed-refs, a linked worktree's gitdir and commondir),
never a git process, and off the async runtime. A detached HEAD records
only the head; a folder outside a repository records neither; a lookup
that fails never fails the session. A fork reads its own folder at fork
time instead of copying the parent's. _acpmux/info carries both fields.
Old session.json files load unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(acpmux): git state skips non-files, bad branch names and missing folders; follows symlinks

Review of #20840: a FIFO HEAD hung session/new, a symlinked folder was walked
lexically, a missing fork folder took its parent repository's state, and an
invalid ref name was stored as the branch. The test git also runs without the
machine's global or system config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(acpmux): git state reads only small regular files, the physical folder and valid ref names

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* cx-tgf4h: keep CmuxNextApp line count (App size cap)

* acpmux: a pooled agent host no longer spins on Linux reading its own record (cx-7z8yx)

The record watch used notify's inotify backend, whose watch mask always
includes open, close-write and modify, and ran its callback on every event.
The callback reads the host record; on Linux that read queued an open and a
close in the watched directory, which ran the callback again. Every pooled
host spun at 100% CPU (about 200,000 read calls a second).

Linux now has its own inotify watch, like the macOS kqueue watch: a mask of
entry changes only (create, delete, moved from/to) and the directory's own
delete or move, one thread in poll with no timeout, an eventfd to stop it.
A read of the record never queues an event. The notify fallback (other
platforms) ignores access events. macOS is unchanged.

Behavior test (tests/host_lifetime.rs, Linux): a real daemon pools a host;
idle for 3 s it makes fewer than 100 read calls and 30 CPU ticks (before:
599,042 reads, 298 ticks), a rename rewrite of its record keeps it, and the
record removed ends it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-tui smoke: sidebar drag checks the drawn sidebar document order

Since sidebar-v2 S6 the TUI draws the daemon's sidebar document, where a new
workspace goes to the top (workspaces.newPlacement default), and a workspace
drop is one sidebar.move that changes the document only. The smoke still
assumed the list-workspaces order: it dragged the drawn top row (the new
workspace) below the original, which the product did, and then read
list-workspaces, which a sidebar.move never changes.

The smoke now reads the order from sidebar.get, checks that the rail draws
it, drags the top row below the second (a real reorder), and drags again when
needed so the original workspace ends below the new one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PR dock chips; tooltips only where nothing else names the control (#20375)

* test: no tooltip on an element whose text shows

Red on base: 105 elements with visible text carry title/data-tooltip.
Command: bun test test/tooltip-rules.test.ts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: PR dock chips; tooltips only where nothing else names the control

The pull request dock's bars become content-sized chips (icon, #N, repo, title cut off at
360 px) with the dismiss button inside and no tooltip. Across the pages, a row, chip, link,
path or thought whose text shows carries no title tooltip any more; tooltips stay on
icon-only controls, shortcuts and text shown nowhere else (why a control is disabled or
failed, who reacted, policy detail), each marked tooltip-allow. Tooltips wait the native
hover delay (about a second) and show at once when moving straight on to the next one.

Green: bun test test/tooltip-rules.test.ts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: review of tooltip audit

Keep the PR dock's state colours, drop four more tooltips the lint now finds inside
components (a setting's summary, the branch and folder pickers, a video), let a click or
scroll end the quick next-tooltip window, and move two tests to the no-tooltip rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: Backdrop Art's line has no tooltip

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at 047c247

- vp fmt webviews/src/pages/history/HistoryPage.tsx

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38502564051

* test(mock): the worked chat opens its pull request, so mock mode shows the PR dock

Captures of the agent pane in a real window (--showcase runs the in-page
mock) now show the pull request dock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: the PR dock takes its own space; the transcript ends above it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at 39f3ca9

- vp fmt webviews/test/ime-composition-keys.test.ts
- vp fmt webviews/test/new-tab-omnibar-pointer.test.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38567629461

* chore(next): PR autofix at c7cb261

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38592980576

* fix: a channel image keeps its name tooltip (the name shows only when it fails)

Base added title={part.name} on channel image attachments (AttachmentViews.tsx).
A loaded image shows no name, so the tooltip is its only label; mark it with
tooltip-allow. tooltip-rules.test.ts: red before, green after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at f221864

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38633116142

* fix: keep the tool card scope hint and Steer shortcut tooltips

Both explain something shown nowhere else, so they stay under tooltip-allow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at 022cc8e

- vp fmt webviews/src/pages/home-channels/cloudHome.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38647981824

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>

* ci: nightly-next recovery initializes the notices submodule before continuing accepted submissions

The recovery job's checkout had no ghostty-next submodule, so the bundle
notice check that notarize-nightly-dmg.sh runs after Apple accepts failed,
and run 38669958906 skipped five accepted builds as 'Apple did not accept'.
Initialize the submodule like the signing job does, and make the skip notice
say that a post-acceptance check can also be the cause.

* ci: the cmux NIGHTLY-identity job also initializes the notices submodule

Run 38669958906's publish-nightly-identity-legacy job saw Apple accept,
then failed the bundle notice check for lack of ghostty-next and retired
the accepted submission of run 38609024986.

* agent pane: progress and stop for a running Claude subagent or workflow (cx-liswe) (#20718)

* test: a running subagent or workflow shows what it does and what it has spent

Red on base: both rows read only "Running" (bun test
test/subagent-progress.test.tsx).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* webviews: a running subagent or workflow shows what it does and what it has spent

The subagent fold keeps acpmux's subagent_progress fields (action, tokens,
tool uses, a workflow's agents done of started), and the transcript's
subagent row adds them to its status line through one shared helper
(subagentProgress.ts) the summary list and the Background tasks pane can use.
Three plural strings, translated for every pane locale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: a running subagent row has a stop button

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: stop a running Claude subagent or workflow from its row

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: restore the catalog's base order after the merge

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: stopping a subagent leaves its row closed, and a refused stop says so

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: a subagent's stop button waits for Claude and says when the stop failed

The button is off while Claude has the request and until the subagent
ends; a refused or unanswered stop (10 s) turns it into "Couldn't stop
<name>" and it can be tried again. The pane no longer drops the error.
Also moves the stop wiring above showSubagent's doc comment and corrects
two comments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style: format the subagent progress test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: stop rejects without English the pane would never show

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: restore the catalog's base order after the merge

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at 2fe6cc4

- vp fmt webviews/src/pages/home-channels/HomeChannelsPage.tsx

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38637059713

* test: the agent pane relays a subagent stop for its chat (cx-liswe)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: the agent pane may stop a subagent of its chat (cx-liswe)

Adds _acpmux/subagent_stop to the pane's method allowlist: session-scoped,
params sessionId and subagentSessionId, no gesture (it stops work, like
_acpmux/kill). Without it the host refused every stop click.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: mirror the subagent stop into the shared pane policy (cx-liswe)

The Swift allowlist took _acpmux/subagent_stop; policy.json, its parity
source for Rust hosts, gets the same request, session scope and params,
with cases for an in-scope stop, another chat and an unknown param.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gallery: a light window with no theme named renders light (#20794)

* test(gallery): a light window with no theme named should show the light default

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(gallery): a light window with no theme named shows Apple System Colors Light

The gallery defaulted the theme to the dark side of the app's default pair
whatever the window appearance, so colorScheme=light (or Appearance: Light)
rendered every stage in dark colors under a light media query. The default
now follows the appearance, as the app's does with no theme set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>

* Channels view: gallery plays for a message link with no link read (red)

A Cloud conversation has no link resolver (G4), so the Messages view's switch hand-off
#/conversation/<id>/message/<id> showed an error. Two plays: a thread reply and a timeline
message, opened through the store's openLink with an owner that has no resolver. cx-hnmbt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels view: open a known conversation's message link without the owner's link read

When conversation-link-resolve fails (a Cloud conversation, an older daemon) and the page knows
the conversation, it opens it, pages the timeline until the message shows, opens its thread when
it is a reply, and marks it. The Messages view's switch hands off Cloud selections this way.
cx-hnmbt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels view: format parseConversationLink (vp fmt)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Channels view: link fallback review follow-ups

Ids in a known conversation's link may hold '-' (the gallery's sample ids); the scheme check
follows the daemon (cmux[-<tag>], any case); the fallback runs only for a Cloud conversation or
an owner without a link read, so a refused local link (a deleted message) still shows its error.
cx-hnmbt

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* acpmux: host_lifetime idle test cleans up from the start and never signals a reused pid (cx-7z8yx review)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* cmux-tui: move the build identity into a leaf crate so an edit no longer recompiles cmux-remote

cmux-remote's build script hashed the dirty source tree into
CMUX_TUI_BUILD_IDENTITY, so every edit in the workspace reran it and
recompiled cmux-remote and every crate above it (about 8 s of a 12.6 s
one-line edit loop on rbx). The same build script now lives in
cmux-build-identity, which only cmux-tui reads; cmux-remote takes the
identity as an argument (SshBootstrapConfig::defaults,
pinned_linux_payload). The identity value, its inputs and format, and
every skew and SSH re-bootstrap comparison are unchanged.

* test: warm the dev server in the Claude reasoning default browser test (#20884)

#20592 landed after #20873 without warmViteServer, so check:browser-lanes is
red on base again.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* cmux-next: frame-strip report as an NSWindow method, not a namespace enum (no bead) (#20887)

The package conventions lint fails every PR on the new all-static public
enum WindowFrameStrip (cx-me3xd). The report reads its window, so it is an
extension on NSWindow; same body, same report.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Home channels: the run card is an inline disclosure (no frame, steps on an indented rail)

The live and done run cards under a Chief's reply lose their box: "Working
for" / "Worked for", the step count, cost and Open sit inline under the
message, and the opened steps hang on a thin rail under the status mark. The
narrow rules stay: the first line never wraps, and below 480 px of card width
the step count and cost move under it with no leading separator.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Home channels: the run rail and columns follow the author's picture

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Home channels: the run sheet keeps its error line; run card columns in logical properties

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Revert "cmux-motion: Chrome tab strip tween ..." (8a3ee62) until the app-ffi release App 403 is fixed (run 38682698310) (hq-1d, chief go 2026-10-15, no bead)

The release App could not publish cmux-app-ffi-854febb26527 (HTTP 403 on
POST /releases), so the app FFI pin check stays red on the tip. The tween
re-lands with the publish and repin after the App is fixed.

* test(ci): nightly jobs that run the license check must init ghostty-next (#20893)

* test(ci): nightly jobs that run the license check must init ghostty-next

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): nightly recovery initializes ghostty-next before the license check

Apple accepted, stapled and launch-tested each recovered nightly-next build,
then verify-app-bundle-licenses.sh failed on the missing
ghostty-next/build.zig.zon and the build was skipped as not accepted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* fix: keep catalog in base order after feat merge

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
Co-authored-by: safe-push <safe-push@cmux.local>
teamleaderleo added a commit that referenced this pull request Oct 15, 2026
* test: a pending command asks in one quiet row

Red on base: the pane draws a waiting command three times (the
transcript's "Ran" row, the card's bold title and its summary), under
"1 request from this turn", "ACP requests only · Isolation unverified"
and an "Expand details" button with a tooltip; its answers read
"Allow once", "Allow for this chat" and "Deny". Locally (jsdom, with a
pass-through askRows): "Expected: 1, Received: 3".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: a pending command asks in one quiet row

The tool permission card becomes the transcript's tool row: its glyph,
a verb and the command once (regular weight, one line, cut with an
ellipsis), on a thin rim with no shadow. While it waits, small Allow,
Always and Deny answers sit at the row's right end. A click on the row
opens the full command, its folder and the scope in plain words.

- The transcript leaves out a call while an ask waits on it, so the
  command is never drawn twice; answered, the call is back in place.
- No "1 request from this turn", no "ACP requests only · Isolation
  unverified", no Expand details button and no tooltips.
- An edit's proposed change still shows under its row (cx-d73v2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at 1718b8e

- regenerated schemas/defaults/cmux-next.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38545059793

* fix: hide only the calls the panel's rows name, after the turns

The transcript drops a pending call only while the grouped permission
panel shows it (not the legacy card, not behind folder trust), and
filters after turnViewFrom so a live turn's status still reads it.
A deny-only group says so inline. Logical CSS for RTL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: the ask row shows whole in the pane's bottom stack, in WebKit too

The DEV app (WebKit) drew the pending command's row as a 15px strip
under the composer: its flex fieldset collapsed inside the asks stack.
The harness now mounts the panel in that stack, and the look test runs
in WebKit as well as Chromium and checks the row is not cut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: the ask row keeps its height in the bottom stack; Always says what it grants

The row's fieldset was a flex box that shrank to 16px in the pane's
bottom stack (red at d12d889: cardHeight 16.19 in Chromium and
WebKit). It is a block now and never shrinks; its rows are the flex
lines.

From cg-design-judge's blind bouts: the scope sentence for Always shows
under the row while it can be chosen (hidden lost 0-16), and Always
gets a hairline rim beside the filled Allow so it does not read as
disabled in light. The coverage words stay in the expanded details.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: regenerate agent pane strings on the merged base; keep the base's catalog order

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at 6796380

- vp fmt webviews/test/ime-composition-keys.test.ts
- vp fmt webviews/test/new-tab-omnibar-pointer.test.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38567683198

* chore(next): PR autofix at aa1f1ee

- cargo fmt in Native/OptChat/optchat-chief

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38570608054

* test: whole permission ask shows when the stack height is definite

The app's WebKit resolved the panel's 45% cap against the bottom
stack, which is the panel's own height, and cut a lone ask through its
answers. Playwright's engines leave that height indefinite, so the
test pins it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: the ask card is its row plus the Always note

9dcfe06 added the note that says what Always grants under the row;
the look test still capped the whole card at one row's 40px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: the permission panel's cap is the pane's, not its own height

max-height:45% resolved against the bottom stack, whose height is the
panel's own once WebKit treats it as definite: a lone ask showed under
half of itself (row cut through its answers, the Always note hidden).
45vh is 45% of the pane's web view, as meant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at 2484ccd

- vp fmt webviews/src/pages/home-channels/cloudHome.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38647152752

* fix: the permission ask keeps the permission card's surface hooks

composer-edges.test.ts (#20454) reads .acpmux-permission-surface for the
card's hairline and its appearance.borders = none clearing; the ask card
now carries that class, draws the shared --cmux-hairline and clears its rim
when borders are off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at 9686b27

- vp fmt webviews/src/agent-session/acpmux/permissions/Panel.tsx

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38675379032

* fix: keep base's agent pane strings after the base merge

Rebuilt the catalog as base's text plus this PR's key changes and
regenerated strings.json; the clean merge had dropped base keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: warm the dev server in the permission ask row test

check:browser-lanes (#20733) requires warmViteServer once the server listens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: keep base's agent pane strings after the base merge

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: the permission ask's secondary rim uses the device hairline

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: keep base's agent pane strings after the base merge

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
teamleaderleo added a commit that referenced this pull request Oct 20, 2026
…every frame, jumps land, 250 ms card) (#22228)

* test: prompt bookmarks persist in the acpmux session (red)

Behavior tests for _acpmux/bookmark_get and _acpmux/bookmark_set before
they exist: restart, prompt-only seqs, fork, transfer, peer forwarding,
the pane relay's scope and params, and the minimap popover's store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* acpmux: prompt bookmarks persist in the session

_acpmux/bookmark_get and _acpmux/bookmark_set keep the thread minimap's
marks in SessionMeta.bookmarks: seqs of the chat's user_message records,
checked against its log and capped at 1000. A change logs a mux
bookmarks record, as tags do, so attached panes hear it; the chat keeps
its place in the session list. A fork carries the marks of the prompts
it copies under its own seqs; a move carries them (the log keeps its
seqs) and the source keeps none. Peers' chats forward to their owner.

The pane relay allows both methods for a chat in the pane's scope with
only sessionId, seq and bookmarked. The minimap's store reads them from
acpmux, toggles optimistically with rollback, and imports this pane's
old localStorage marks for the chat once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: thread minimap matches the Codex rail; bookmarks show and jump (red)

Behavior tests for the rail's thresholds, hover magnification, card timing,
keyboard focus, scrub, the bookmark dot and row button, and the bookmark
steps, plus a WebKit motion test and the Swift action/page-event test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* cmux-next: thread minimap matches the Codex rail; bookmarks show and jump

The rail now follows the Codex desktop app's measured behavior: it shows
from 4 prompts with 48 px left of the column, 16 px in and centered, with
10 px x 36 px rows and 6 px ticks that scroll past 64 rows or 70% of the
window. Hover magnifies the rows (26/20/14/10 px) in 160 ms on Codex's
spring, the preview card opens after 250 ms (at once within 300 ms of a
close, or on keyboard focus) and closes 100 ms after leaving. A click
scrolls on cubic-bezier(0.35, 0.05, 0, 1) (~400 ms for ~650 px), landing
the bubble 35 px below the top, and highlights it for 1400 ms; an
unmounted target jumps. Dragging scrubs. Every row is a tab stop.

Bookmarks show as a 2 px dot past the tick (a click on its spot toggles
it), in the card, and in a new button on the prompt's own row. Previous
and Next Bookmarked Prompt are palette actions with no default key; the
turn steps keep Ctrl-Opt-Up/Down, now on Codex's 200 ms scroll curve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* cmux-next: regenerate action exports for the bookmarked-prompt steps

Generated by scripts/measure/export-action-surfaces.sh on the fleet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: concurrent bookmark sets log the kept marks; legacy marks wait for acpmux (red)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* acpmux: log bookmark changes in order; keep legacy marks until acpmux holds them

set_bookmark changes, logs and saves the marks under the chat's move
lock, so concurrent sets log in the order they change the marks and a
move begins before or after a change. The prompt check scans the log on
a blocking thread. The pane forgets an old localStorage mark only once
bookmark_set succeeds for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: a clicked minimap row takes focus so Tab goes on from the rail

WebKit does not focus a clicked button, so in the app Tab after a click
left the rail for the transcript. Codex's rail (Chromium) focuses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: focus a clicked minimap row so Tab goes on from the rail

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: read the minimap hover transition by seeking it, not by frame samples

CI's headless WebKit paced frames unevenly, so a sample 40 ms into the
transition could come long after it ended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: regenerate action exports on the merged head

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at 568dba62ae7a

- vp fmt webviews/test/ime-composition-keys.test.ts
- cargo fmt in Native/OptChat/optchat-chief

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38566033765

* chore: regenerate action exports after merging #20556's head

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: the minimap's card follows the app hover intent (red)

Base's hover-intent and elevation tests now drive the rewritten rail
(.acpmux-minimap__card, the rows' pointerenter/pointerleave), and the
rail test pins the app hover intent (450 ms delay, warm 500 ms, gone as
the pointer leaves unless it enters the card) instead of CardTiming's
250/300/100 ms.

heavy --mem 3G bun test test/hover-intent-cards.test.tsx test/subtle-elevation.test.ts test/thread-minimap-rail.test.tsx
(fail) the card follows the app's hover intent: it waits the hover delay, follows the pointer at once while warm, and goes as the pointer leaves
  thread-minimap-rail.test.tsx:210 expect(card()).toBeNull() after 400 ms: the card opened at 250 ms
(fail) the minimap's turn preview waits the hover delay, then follows the pointer at once until the rail cools
  hover-intent-cards.test.tsx:117 expect(visible()).toBe(false) right after leaving: CardTiming keeps it 100 ms
(fail) one subtle elevation scale > .lg-sheet casts --elevation-2 (base red, ledger.css untouched)
The .acpmux-minimap__card casts --elevation-2 case passes (the merge already moved the card to it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: the minimap's card follows the app hover intent

The preview card now runs on ui/hoverIntent's useHoverIntent, as base's
minimap did: the first waits the hover delay, the card then follows the
pointer from row to row at once while the rail is warm, and it goes as
the pointer leaves the rail (moving into the card, or back, keeps it).
Keyboard focus still shows it at once; Escape dismisses it and focus-out
closes it on that frame. cardTiming.ts and the CARD_* constants are gone.

thread-bookmarks.test.tsx goes back to the PR's version: the merge took
base's half, which drives the old rail's pointermove on two prompts.

heavy --mem 3G bun test test/hover-intent-cards.test.tsx test/subtle-elevation.test.ts test/thread-minimap-rail.test.tsx
  46 pass, 1 fail (.lg-sheet casts --elevation-2: base red, ledger.css untouched)
heavy --mem 3G bun test test/thread-minimap-current.test.tsx test/thread-bookmarks.test.tsx test/thread-turn-keys.test.tsx test/hover-intent-parity.test.ts test/hover-row-sweep.test.ts test/thread-minimap-motion.test.ts
  7 pass, 0 fail (browser lanes skipped locally)
heavy --mem 4G bun run check: pass

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: regenerate action exports on the merged head

export-action-surfaces.sh on 6cf54078 (fleet step a2d9f3a7): 961 actions (base's
chat-identifier actions plus this PR's transcript actions).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: regenerate action exports on the merged head

export-action-surfaces.sh on eec94827 (fleet step fa811efc): 963 actions (base's
Music actions added).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* debug: a short seed of one-line turns for the thread minimap's density captures

debug.agent_pane seed_rows fixture "short": one-line prompts answered
"ok N", the same content as the Codex reference chat, so tick and highlight
density compare like for like.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* cmux-next: regenerate action exports after merging feat-cmux-next

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: warm Vite before the minimap motion and Claude reasoning browser tests

check-browser-test-lanes (#20733) requires warmViteServer after listen;
claude-reasoning-default (#20592) landed without it and fails the base check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at 0f6b92945b0c

- regenerated plans/cmux-next/action-surfaces.json
- regenerated plans/cmux-next/actions.md

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38683433717

* fix: a prompt's bookmark shows with the hover actions experiment at one

The hover-actions experiment hides a prompt's action row unless it has Copy,
versions or Edit; a prompt that can carry a mark keeps the row for its
bookmark.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at a97cff790a52

- regenerated plans/cmux-next/action-surfaces.json
- regenerated plans/cmux-next/actions.md

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38694299706

* fix: a drag's click on the minimap stays swallowed until the next press

The flag cleared on a 0 ms timer, which a slow frame could run between the
pointerup and its click, so the drag's click jumped (CI web shard 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* cmux-next: regenerate action exports after merging feat-cmux-next

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test: the thread minimap's feel in a long chat, in Chromium (red)

Jump landing for undrawn rows, the hover card's 250 ms, the band following the
scroll on its own frame, and older turns loading without moving the band.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: the thread minimap lands, previews, tracks and holds like the Codex rail

- A jump corrects its landing from the bubble while the rows it crossed measure.
- The card opens 250 ms after the pointer reaches the rail (Codex's delayOpen).
- The band of lit rows follows the scroll on the scroll's own frame, and counts
  the turns on screen as Codex's observer does (no turn below the view).
- Older turns loading above the reader leave the band where it was on screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: settle skips a scroller with no layout; the rail's card tests at 250 ms; gallery rest, scrub and long-chat states

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style: format

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: a jump settles only on a bubble with a box to measure

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: review findings on the minimap feel

- Older turns: the band reads the offset as the transcript will restore it, the
  keep-in-view scroll skips that commit, the hovered, focused, card and scrub
  rows follow their prompts, and a second page continues a running glide.
- The band's first turn is Codex's: a prompt within 35 + 16 px of the top ends
  the turn above it, so a prompt just jumped to is the first lit.
- A jump's landing correction ends on any input in the page; a pending turn step
  is tracked by its turn, not its offset.
- The scroll listener runs in capture; the gallery's synthetic scrub cannot throw.
- Tests: a scrolling rail taking 30 older turns, waits on frames instead of
  timers, a looser card window and one stray frame allowed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: the band reads the restored offset until the transcript's restoring scroll arrives

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at b030bd39f09d

- regenerated plans/cmux-next/actions.md
- regenerated cmux-tui/crates/cmux-app-host/generated/scopes.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/39347840624

* fix: the band reads the transcript's placement; jumps end with their chat; the card closes on focus out and Escape; the rail fits a short pane

- VirtualTranscript calls the rail once its placement pass has set the offset,
  so the band reads the restored place directly instead of guessing from a
  frame with no scroll event.
- A running jump stops when the chat changes or the rail unmounts.
- The card closes when focus leaves rail and card, and on Escape inside it.
- The rail's height is capped by the transcript's own height.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: a press released off the minimap ends there; a chat switch stops a jump in its own commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: the minimap card's Escape goes through ui/escapeDismiss, not a raw key handler (ui rules)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): PR autofix at a73c00f9cf02

- plans/cmux-next/action-surfaces.json
- plans/cmux-next/actions.md

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/39360464009

* chore(next): PR autofix at 3c3d0e0b6bcf

- Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/39364182964

* chore(next): PR autofix at 9d214de119ff

- plans/cmux-next/action-surfaces.json
- plans/cmux-next/actions.md

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/39380161552

* chore(next): PR autofix at 6e9a03929633

- plans/cmux-next/action-surfaces.json
- plans/cmux-next/actions.md
- schemas/defaults/cmux-next.json
- web/data/cmux-shortcuts.generated.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/39386397629

* agents: fence native updates to the captured machine (#21389)

* agents: expose machine platform capabilities

* agents: document machine capability metadata

* agents: model machine platform capabilities

* agents: format machine platform metadata

* agents: show machine platform metadata

* cloud: preserve machine capability contract

* test: cover cloud machine capabilities

* cloud: expose capability operation checks

* test: cover capability operation helpers

* cloud: gate actions on provider capabilities

* cloud: clarify capability refusal

* cloud: keep capability gates within app size budget

* agents: show machine capability summaries

* agents: show machine capability summaries

* test: cover agent capability summaries

* cloud-server: type capability checks

* cloud-server: centralize access gates

* cloud-server: centralize access gates

* cloud-server: carry machine capability metadata

* docs: specify machine capability metadata

* agents: exercise machine capability metadata

* testdata: add machine capability fixtures

* testdata: add machine capability fixtures

* testdata: add machine capability fixtures

* testdata: add machine capability fixtures

* testdata: add machine capability fixtures

* testdata: add machine capability fixtures

* testdata: add machine capability fixtures

* testdata: add machine capability fixtures

* agents: sanitize machine metadata at ingress

* agents: sanitize machine metadata at ingress

* test: cover agent metadata sanitization

* protocol: carry machine platform capabilities

* protocol: carry machine platform capabilities

* fix: restore provider runtime after upload truncation

* style: format machine capability protocol

* style: format provider runtime capability fields

* style: fix provider snapshot indentation

* protocol: retain provider metadata in machine catalog

* protocol: map provider metadata into machine catalog

* tests: update machine descriptors for provider metadata

* tests: update machine descriptors for provider metadata

* tests: update machine descriptors for provider metadata

* tests: update machine descriptors for provider metadata

* tests: update machine descriptors for provider metadata

* tests: update machine descriptors for provider metadata

* tests: update machine descriptors for provider metadata

* tests: update machine descriptors for provider metadata

* apps: reserve agent CLI provider family

* test: enforce agent CLI provider ownership

* agents: add verified read-only agent_cli owner

* agents: cover read-only agent_cli owner projection

* agents: register the read-only agent_cli owner

* agents: defer App provider adapter until feature ownership extraction

* agents: defer App provider adapter until feature ownership extraction

* agents: keep App provider wiring within its module size boundary

* agents: reproduce malformed machine capability crashes

* agents: validate capability wire types before rendering

* agents: specify cached harness inventory privacy and no-execution behavior

* agents: expose cached harness inventory without health side effects

Select only non-secret health fields and reject controls or machine selectors.
Advertise the view in initialize so older daemons cannot silently execute a
normal health request when a client expects a cache-only read.

Cmux-Daemon-Tests: run

* agents: retain prerelease and build metadata in cached inventory

Cmux-Daemon-Tests: run

* agents: negotiate cached inventory before sending health requests

Keep initialize and the inventory request on one transport. Unsupported
owners receive no health operation, and typed inventory decoding rejects
legacy or noncached responses. Preserve pipelining for existing calls and
enforce the documented frame limit before parsing complete replies.

* machines: import platform type in provider runtime

The hosted same-tree daemon build caught the missing import used by the machine catalog projection.

Cmux-Daemon-Tests: run

* test: specify trusted machine inventory routing

Exercise same-owner feature negotiation, cache-only responses, missing owners and refusal of machine selectors, controls and untrusted clients before adding the daemon command.

Cmux-Daemon-Tests: run

* machines: read cached agent inventory through the native owner

Add the agent-inventory-v1 daemon verb on trusted Unix connections. Negotiate harnessInventory on the exact owner socket before the cache-only health request, reject controls and machine selectors, bound per-daemon concurrency and serialize only non-secret DTO fields. Keep the conversations-only relay denied.

Cmux-Daemon-Tests: run

* test: unwrap successful provider snapshots before checking defaults

The hosted workspace test build caught direct accesses to a nonexistent ResponseEnvelope.result field in the legacy-metadata regression. Match its ProviderResponse::Success payload instead. Also use the already-imported AtomicUsize in the inventory worker bound.

Cmux-Daemon-Tests: run

* agents: share the inventory wire model with native machine requests

Move the cached observation DTO to the lower daemon protocol module, add the capability-gated AgentInventoryRequest and reuse that DTO in the direct acpmux reader. AgentPane now depends on Daemon, whose dependencies remain Compat and Wakeups; no cycle or app-target growth.

Cmux-Daemon-Tests: run

* chore(next): PR autofix at 48a59aac18e3

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/39024598894

* test: repair machine metadata fixture construction

The hosted workspace test build caught duplicate platform/capabilities assignments in MachineDescriptor fixtures and assignments intended for MachineDescriptor on ManagedMachineDescriptor. Remove the duplicate and wrongly typed pairs while preserving each descriptor's actual capabilities.

Cmux-Daemon-Tests: run

* test: reproduce false absence claims from partial agent inventory

Cover partial caches, launcher uncertainty, presence-only auth evidence, watch removals and all three rendered layouts using FakeHost. Before the fix the focused suite executes 16 tests: 15 fail and the complete-inventory control passes.

Command: bun test first-party-apps/agents/test/inventory.test.ts

* agents: preserve unknown evidence in cached machine inventories

Only an explicit complete:true inventory can establish missing CLIs. Preserve nullable installation and presence-only auth evidence, keep partial removals unknown, and show uncertain rows/cells without install or update actions in every layout. Empty account observations no longer imply signed out. Add translated uncertainty copy and regenerate synthetic preview fixtures and the app bundle.

Focused regression: 15 failures and 1 control pass at 9863b7dc; all 16 pass after the fix. Full Agents suite: 27 passed, 297 assertions. Current Swift inventory suites also passed all 10 tests at 48a59aac.

The repository-wide localization audit still reports errors in unchanged macOS catalogs; the Agents English/Japanese source-key and placeholder checks pass.

* test: preserve inventory assertions during owner fixture cleanup

Do not set a read timeout on the immediately closed cleanup wake socket (macOS returns EINVAL), and preserve the original assertion if the worker fails during panic cleanup. Real owner calls are already bounded and close their link; the fixture reads their EOF.

Cmux-Daemon-Tests: run

* apps: move injected capability handlers into their feature owner

Move the request, error, handler protocol and family router from the executable App target to CmuxNextApps, preserving dispatch and refusal behavior. The App keeps its concrete handlers and channel wiring. Add a focused Apps test target, regenerate the target graph and import the feature at the remaining PageFactory use.

The extraction removes 61 App-target lines and gives the agent inventory owner an injectable feature-level handler seam. Local Swift syntax, namespace, concurrency, graph freshness, App size and diff checks pass; hosted compilation and the new behavioral suite are pending.

* docs: scope duplicate handler ownership to the in-process router

Clarify the existing local dictionary replacement behavior without implying that a live provider registration may be taken over.

* test: reproduce missing canonical machine identity in identify

* machines: expose canonical owner identity in trusted daemon handshakes

* sdk: preserve optional machine owner IDs in raw identify results

* test: reproduce inventory refusal without a selected-owner fence

* agents: fence native inventory reads to the selected machine owner

* test: require inventory family evidence from cached executable and package

* agents: project CLI family from the cached executable and package evidence

* test: require an admitted read grant for agent CLI inventory

* agents: serve scoped CLI inventory through admitted machine owner routes

* test: use synthetic host labels in machine inventory fixtures

* apps: give provider registration its own connection adapter

Preserve captured-transport capability admission and legacy-family fallback
without growing the already oversized DaemonConnection type. Update the
wire-level registration test and the request documentation.

* test(agents): reproduce wrong-platform install hints

Exercise structured Linux metadata and unknown remote owners through the
packed app. Keep legacy OS and recognized-platform controls.

* fix(agents): choose install hints from the selected owner platform

Prefer structured machine metadata, retain legacy OS compatibility, and
keep unknown or unsupported systems unknown instead of offering Mac install
commands. Record the current inventory owner/provider integration in the
shared coordination lane.

Regression: platform.test.ts goes from 3 behavioral failures and 2 controls
to 5 passes. Full Agents suite: 32 passed, 340 assertions.

* test(machines): define verified remote-catalog publication behavior

Exercise the existing machine.list/get API, exact owner fencing, bounded
metadata, ordered replacement, exclusive publication and connection expiry.
Keep the local-only default as a control.

* test(machines): use the resource module's error type

Repair the fixture import before evaluating the behavioral catalog regression.

* machines: project verified remote routes through the native catalog

Accept bounded, sequenced publications on the verified local app's existing
connection, fenced to this daemon's public ID. Keep the publication in that
connection record so disconnect removes it atomically. Expose metadata through
machine.list/get while retaining the native local identity and session owner.
Reject competing publishers, local impersonation, stale replacement, unknown
fields, commands, paths and nonlocal/agent-bound carriers.

The app publisher is not wired yet. No new route, remote execution authority,
owner reconnect, disk schema or credential delegation is introduced.

* test(machines): cover SDK catalog decoding and retain outer disconnect denial

Correct the expired native client's expected outer remote_denied refusal.
Add a behavioral SDK regression for native platform/capability metadata and
remote origins, preserving legacy records and strict malformed-field checks.

* sdk: carry machine origins and owner capability metadata across resource clients

Extend the authoritative MachineSnapshot contract and all seven ergonomic
SDKs to preserve optional platform/capability metadata and Cloud, SSH and
server origins. Keep legacy omission defaults and strict typed metadata.
Extract the Rust and Zig machine models instead of growing oversized files;
Zig retains a bounded capability table without allocation.

Regenerate the app declaration and canonical SDK descriptors. Update the
boundary guard to validate the exact remote metadata shape and add rejection
coverage for command fields, arbitrary origins and an open platform schema.
Document the native publication bridge; its app publisher remains unwired.

Behavioral Python SDK regression: four assertion failures at 1552187f, then
all three tests pass. Existing Python resource suite: 69 passed. Focused TS:
three passed. Resource boundary guard suite: 34 passed. Native catalog tests:
eight passed at 1552187f (f5d2957368a6c4fbd973f19d). Compiled SDK lanes pending.

* test(java): preserve the SDK test runner executable bit

* test(machines): require native owner metadata in the handshake

The app must publish the owner's observed platform and machine capabilities,
not infer them from a UI route, provider label or its own local system.

* machines: publish admitted owner metadata from the app registry

Observe existing native registry routes on the captured local connection. Fence snapshots to the owner and handshake epoch, preserve live lease sequences across temporary source loss, withdraw removed routes and discard stale acknowledgements. Bound catalog records and frames, and negotiate publication without a legacy fallback.

Expose native platform and machine capabilities in trusted identify using the same snapshot helpers; regenerate all seven raw SDKs. Add deterministic publisher lifecycle and wire-contract coverage.

* machines: verify the publication transport and avoid catalog panics

Exercise actual connection negotiation, legacy refusal before write and owner mismatch propagation. Repair test-fixture compilation and remove three fallible expect sites from catalog projection and selection. Document event-driven publication and native owner metadata.

* test(machines): expose policy readmission observation regression

Extract the current registry projection unchanged from app composition and exercise it against a paired-server fixture. A blocked route must keep observing the policy that can readmit it. No host or connection is started by the fixture.

* machines: keep blocked catalog routes observing readmission

Read observable registry admission and carrier state before excluding blocked routes. This preserves the subscriptions needed when policy or a reconnect readmits the existing route, without opening a connection. Cover both policy and carrier invalidation.

* test(machines): cover curated CLI and MCP catalog discovery

Exercise read-only machine discovery, explicit metadata lookup and continued exclusion of session effects and arbitrary machine routing. Extract existing catalog tests instead of growing the large MCP test file.

* test(machines): preserve CLI paths after catalog test extraction

* machines: expose read-only catalog discovery to CLI and MCP

Offer curated machine list/show and generate machine_list/machine_get from the existing operation catalog. Keep session effects and other-tool machine routing excluded. Share operation-aware route defaults between CLI and MCP so the catalog socket's session is not serialized as metadata, and resolve machine prefixes on the same connection with no ancestor fields.

Extract the machine grammar instead of growing command.rs. Document the metadata-only contract and translate root discovery help in both native CLI locales. Add actual transport and route regression coverage.

* machines: clarify catalog tool authority and repair fixture lints

* test(machines): use an explicit name selector for reserved words

* test: reproduce Swift catalog ancestor selector mismatch

* fix: encode Swift catalog selectors for their operation

* test: exercise catalog trust with real native owner connections

* machines: exercise app discovery and scoped inventory through real server owners

* test: use the catalog's read risk in the app journey fixture

* test: retain owned app diagnostics for native journey failures

* test: preserve new app runs across replacement host exit

* fix: fence queued app runs from the outgoing host generation

* style: terminate app dispatch match arm consistently

* docs: distinguish machine catalog metadata from session authority

* test: require negotiated metadata for strict provider v1 clients

* machines: negotiate optional provider metadata per control generation

* machines: reproduce strict native reader catalog incompatibility

* machines: require reader opt-in for extended native catalogs

* chore(next): PR autofix at 1938babb0ef6

- regenerated cmux-tui/crates/cmux-app-host/generated/cmux-app.d.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/39046739528

* tests: preserve native field validation in MCP catalog plans

* sdk: expose machine reader options and reproduce missing wire opt-in

* tests: format the SDK catalog wire regression for hosted Zig

* sdk: serialize explicit machine catalog reads without changing defaults

* tests: cover the extracted machine facade and typed invalid inputs

* tests: reproduce strict identify reader compatibility and opt-in boundaries

* tests: exercise identify through its native command handler

* machines: negotiate owner identity metadata without changing legacy identify

* tests: preserve identify callers and validate explicit metadata options

* cloud: localize unavailable operation refusals

* tests: capture bounded catalog withdrawal receipts on journey failures

* tests: await the acknowledged withdrawn catalog instead of a fixed sequence

* test(agents): reproduce stale and cross-owner inventory refreshes

* test(agents): require explicit catalog discovery and local fallback controls

* fix(agents): discover admitted owners and fence inventory refreshes

* test(agents): reproduce missing cache-only inventory change feed

* feat(agents): publish bounded cache-only inventory changes

* test(agents): require owner-fenced native inventory watch leases

* feat(agents): fence native cache watches to owner connection leases

* test: reproduce inventory watch replacement cleanup race

Correct the native watch fixture's writer construction and qualification warnings so the barrier-controlled socket replacement case can execute before changing cleanup behavior.

* fix: fence inventory watches across socket replacement

Detach old leases while changing the configured socket and close only that detached set outside the lock. Capture a monotonic internal socket epoch in every watch so a reused path cannot admit stale work. Preserve the native wire contract and the pre-fix barrier regression.

* fix: document native inventory streams and guard handshake replies

Declare the targeted watch events as control streams and document their payload/lifecycle. Keep moved agent-session emitters in their existing internal wire family. Replace the computed metadata reply subscript with a bounded lookup that fails malformed responses closed.

Spec inventory and all32 checker tests pass after the previously observed missing-stream failure. Swift syntax, scoped conventions/size and crash ratchet pass; no crash baseline changed. Native watch Rust inputs are unchanged from the 25-case fleet pass at01418d27; final job cleanup remains pending.

* test: preserve inventory lease events through tree resyncs

Exercise native event decoding, DaemonStore side-event delivery behind a snapshot barrier, and EventInbox overflow. Inventory changed/ended events belong to the separate subscription; keep an unrelated unknown-event control. Regression only, before event-retention repair; fleet execution pending.

* fix: retain native inventory lease events behind tree snapshots

Classify the two targeted inventory lease notices as independent of workspace snapshots. Keep unrelated unknown events behind the existing tree barrier. EventInbox already retains unknown events; the new overflow cases preserve that control behavior.

Regression be913d2e precedes this fix. Syntax, scoped conventions and size checks pass; exact fleet red/green execution remains pending the matching native artifact. No app-facing watch advertisement or new authority.

* test: preserve exact native inventory watch revisions

Exercise actual DaemonEvent wire decoding across UInt64 boundary values and malformed owner/lease/revision inputs. Require bounded lease-only side-event payloads and retain an unrelated numeric-event control. Regression only; no runtime RED claim before fleet execution.

* feat: type native inventory watch requests and exact revisions

Add separately capability-gated owner-fenced enable/disable requests and a directly decoded UInt64 receipt. Normalize targeted side events through validated owner/lease fields, preserving revisions as decimal text before generic JSON can round them and discarding diagnostics or malformed notices. Keep unrelated events unchanged.

Revision regression8ddb7918 precedes this change. Add request framing and bounded real-connection bridge coverage for legacy refusal and exact UInt64 replies. Syntax, scoped conventions/size and crash ratchet pass; packet0 localization entries. Fleet runtime red/green remains pending matching artifact publication. App-facing subscriptions are not yet wired or advertised.

* test: fence delayed inventory disable to its original lease

Enable a new lease on the same native control connection after socket replacement, then deliver cleanup naming the retired lease. Assert idempotent no-op for stale cleanup, continued invalidations on the new lease, and successful conditional retirement of its exact ID. Regression only, before owner-side conditional disable.

* fix: condition inventory cleanup on its captured lease

Advertise agent-inventory-watch-fence-v1 and accept a canonical expected_watch_id only on disable. Atomically compare/remove only that client's matching lease; a stale ID or absent lease is an idempotent no-op, never a selector for another client. Keep the original explicit connection-wide disable.

Managed Swift enable/disable requests require fencing support before starting and include the exact receipt ID on cleanup, with no unfenced fallback. Add cross-client, malformed-input and capability coverage. Regressionedd780 executes the missing conditional-disable assertion in the selected run and alone (jobdb37d30741c3eecdeda6d36e, failed100, no leaks). Spec inventory/32checker tests, scoped syntax/conventions/size and diff checks pass; runtime GREEN pending.

* test: use the daemon JSON object case in the watch fixture

Correct the connection fixture's assumed AppJSON accessor to pattern-match CmuxNextDaemon.JSONValue.object. Current lease-fenced wire assertions remain intact. The earlier904577 candidate stopped at this fixture compile error; it provided no runtime GREEN. Production sources and native inputs are unchanged.

* test: consume admitted agent inventory invalidations

Exercise the real Agents store in FakeHost: agent_cli.changed must re-discover/re-read admitted owners, ignore claimed event payload rows/selectors, and coalesce bursts while retaining a follow-up. The same focused Bun command executes13controls passed and3expected failures before the fix. Keep this regression before the implementation; advance the public branch with both after local GREEN.

* fix: re-read admitted agent inventory on cache invalidations

Subscribe to agent_cli.changed and ignore event payload rows, selectors, commands and job results. Re-discover/re-read current owners without live checks; coalesce an active batch and preserve a follow-up. Remove the unsupported watch payload listener and its proposed stream operation. Preserve stale-read, withdrawal and local-fallback fences across all three views.

Regression154e9306 precedes this fix. The same focused Bun command first executes13pass/3expected failures, then passes all16original cases plus2refusal/removal cases. Full Agents suite50tests/416assertions, canonical bundle/check and manifest pass; existing execute-scope warning remains. Localization packet0entries/attention; global static baseline failures unchanged. Publish both commits together after local GREEN. App-facing native/provider invalidation production remains open; Agents is unbundled.

* machines: bind inventory watch lifecycle to the ready control session

Capture one manager per admitted native control transport, retain pending
receipts for cancellation cleanup, and serialize conditional retirement
before readmission. Failed cleanup cannot silently admit another lease.

Add bounded bridge coverage for cancellation, owner refusal and reconnect.
Extract handshake admission to keep DaemonConnection within its size ratchet.

Runtime validation follows on the immutable hosted source; no end-to-end
provider watch or Cloud journey is claimed.

* test: reject cached inventory watch admission after control reconnect

An old manager must not return its cached receipt after the original
transport closes, even when the same canonical owner reconnects.
Keep the new transport's live lease as the cleanup isolation control.

* fix: refuse retired inventory watch transports before returning receipts

Check the captured transport before cached admission and after pending
admission. Recheck retirement when another caller starts a cleanup while
an earlier cleanup await resumes.

The preceding reconnect regression executes one expected cached-receipt
failure with four passing controls on fleet job7c5042eb1d069c387c351d77.
No reconnect or alternate-owner cleanup fallback is introduced.

* machines: exercise watch manager admission refusal before native contact

The new manager refuses an owner with only the older watch capability
and an owner without canonical identity. Real bridge inspection confirms
no watch operation is sent in either case.

Production watch lifecycle at parent7c58329a passes all seven selected
Swift suites, fleet job1d9ad29f7b992e9612c26052, including the preceding
closed-transport regression.

* test: revalidate app stream authority before each queued delivery

Use the scripted host and an explicit queue barrier to hold the current
process alive across grant, enabled, host-state and manifest changes.
A saved subscription must not keep receiving events after those changes.
The readable running host remains the positive delivery control.

* apps: fence inventory invalidations to current readable demand

Recheck install, manifest, grant and host state under the supervisor lock
before queuing stream events. Keep fixed inventory invalidation out of the
generic daemon event feed.

Add opt-in inventory demand snapshots, targeted monotonic demand events,
and a strict verified non-agent Unix provider ingress taking only the
current demand sequence. Revoke, unsubscribe, host exit and provider
replacement fence stale callbacks without supplying rows or selectors.

The preceding real-host queue-barrier regression executes five expected
authority failures with a passing running-host control, selected and alone,
job b8b8381c06c4dd0c40a75d3a; no leaks. Current runtime validation follows.

* fix: validate inventory invalidations at the apps request entry

Move the strict frame check from cancel-request to try_handle, where the
command discriminator and optional response shape are available.

The prior candidate stopped during native compilation; no runtime result
is attributed to it. Inventory and cancel-request behavioral gates will
run on this corrected immutable source.

* test: preserve the page relay role while proving its inventory refusal

The generic verify helper sets role main. Set only the verified-app test
fact in this fixture so the request actually originates from page_relay.
Production inventory permission and demand behavior is unchanged.

* apps: satisfy demand test lint and document inventory provider opt-in

Add the two required test closure semicolons without suppressing warnings.
Document the additive demand snapshot and fixed provider carrier in the
owning module; legacy registration remains unchanged.

Prior f85cfc62 runtime executes42 passing inventory cases with one invalid
page-relay fixture failure, corrected in parent8859e57c. The queued8859
verification was cancelled before placement after these lint issues were
known; the final exact source is the verification target.

* machines: preserve source identity through side-event delivery

Tag native event envelopes with the connection instance and retain their
original control serial through the store, snapshots and inbox collapse.
A new envelope subscription rejects unsequenced or unstamped delivery;
existing event-only subscriptions remain compatible.

Delegate typed wire execution and ready transport sequence projection to
their resource owners without growing the connection type.

Exact Swift files pass eight suites/28methods on immutable validation
source2a74fd62 using its official matching published native tree eb966;
job a4c6a87c47577e66e78cd436 is done/0/reset. This is component proof only.
Current carrier Rust at parenta11bfff6 passes43inventory and140shared app
cases. Current native/provider composition awaits its matching artifact
and the production demand-to-watch bridge.

* test: retain exact provider inventory demand sequences

* apps: connect permissioned inventory demand to admitted machine watches

* apps: validate inventory subscriptions through the native app journey

* apps: fence provider callbacks and verify reconnect and lease retirement

* test: reject stale registry admission after actor reconnect

* apps: require the registry observed control transport for inventory admission

* daemon: expose observable side event control admission

* test: name disconnected control reason in stamp lifecycle fixture

* test: recover explicit no-lease inventory admission refusals

* machines: prove absent inventory leases before explicit admission retry

* sdk: model native owner inventory events across generated wire layers

* test(agents): reproduce duplicate and stale selected-owner job admission

* fix(agents): bind job actions to one admitted pending request

* test(core): pin agent update admission to the captured machine

* feat(core): fence agent update calls to their captured machine

---------

Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Use paired server identities for links (#21644)

* test(server): expose missing pairing commands

* test(server): reject missing idempotency values before mutation

* Implement human-approved server enrollment in Rust

* test(server): keep pairing links in the selected API environment

* Keep pairing verification links on the selected API and fix isolated fixtures

* Keep server enrollment coordination in its lane ledger

* test(link): bind paired servers to their enrolled WireGuard identity

* Use the enrolled server identity for an explicitly selected link

* Dereference the protected key when checking a running link identity

* Share link help and exercise enrolled traffic through real cmux processes

* Report the exact encrypted-journey deadline and owned process logs

* test(link): exit a refused encrypted dial while stdin remains open

* Finish link dial after peer EOF with stdin still open

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* docs(web): point Huge Blue inspection comment at the vms README (#21952)

docs/design/huge-blue-app-server-inspection.md only existed in the
unmerged #21335; the design lives in web/services/vms/README.md.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* test(agent cursor): red: the name tag is a gray Liquid Glass capsule (pill gallery P1)

User (Oct 20): 'more designs for tinted pill ... gray and liquid glassy, think
about how Apple would design this'. Replaces the light-glass tag expectations:
neutral gray translucent tint, continuous corners, light specular rim and top
highlight, subtle shadow, dark text, gray dot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* cmux-next agent cursor: the name tag is a gray Liquid Glass capsule (pill gallery P1)

Neutral gray translucent tint (#D6D6D8 at 78%), continuous-corner capsule
22 pt tall, light specular rim, top highlight gradient, soft 1.5 pt shadow,
dark label text and a gray dot; the typing caption uses the same material.
No agent color anywhere in the tag. The overlay panel sits above Chromium's
child windows, so it cannot blur the page; the tint stands in for the blur.
No new animation: the tag still shows and hides through the existing Motion
fades, so Reduce Motion and animation speed apply unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cmux-next tabs): a tab switch at rest walks no row of tabs (red: every switch runs the occlusion and full separator passes)

TabSwitchWorkBudgetTests counts the strip's passes over every cell (TabStripRowPasses: occlusion,
separators, frames) across 2N switches at 2, 10 and 30 tabs and expects none: the switch's cost must
not grow with the tab count. Deterministic, where a timing gate on shared CI hosts is not (the same
bench measured 0.4 ms on one fleet host and 29 ms on another).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* cmux-next tabs: a tab switch updates the two tabs it changes, not the row (tab-switch-rewrite)

syncSelection walked every cell twice per switch: the full separator pass and the occlusion pass
(TabOcclusion samples each cell's motion path). At rest no tab overlaps another and no mask is on, so
occlusion runs only while a strip move is in flight (movesInFlight, cleared by the move's completion),
and the separator rule is applied to the lines next to the old and new selected tab through a slot
index built once per layout (TabSlotIndex), which reveal also reads. A multi-selection change keeps
the full pass. TabStripSwitchSeparatorTests proves the lines equal a full pass after any switch order.

Benches (fleet, aws-m4pro-5, same host A/B against a9439ce7ac3): TabSwitchPathBench apply p50
60 tabs 0.42 -> 0.31 ms, 30 tabs 0.21 -> 0.15 ms; strip separators 38 us -> 2.5 us at 60 tabs,
constant. TabSwitchPathBench now prints the per-stage split (BenchSpans switch.*), and
TabStripSelectionBench each strip step. scripts/cmux-next/tab-switch-matrix.py also reports each
Chromium and WebKit page's own first frame after a switch (content_frame_ms).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Server: Add Server card: the Name/Team grid takes the card's width

The fleet render at 39b0b0b23b5 still drew the Name field at its ideal width:
the Grid sized to its cells, so maxWidth on a cell had nothing to fill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Server: Add Server card: Name and Team as captioned full-width rows

The Grid kept the Name field at its ideal width (the maxWidth frame sat
outside the drawn card) and wrapped the Chief checkbox. Captions over the
controls need no label column, so long labels in any language fit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: actions.md lists the transcript actions where the generator puts them (AgentTranscriptActionCatalog order)

* test(next): pinned groups read as pinned; a group header's right-click is one flat menu (red)

Lawrence 2026-10-20: Move to Top left a workspace under groups that were
pinned (the Pinned band lists above the machine sections), but nothing
showed they were pinned and Pin/Unpin could only pin. And the header's
right-click opened the editor, whose More Group Actions row held the menu.

Red: SidebarDocPinnedGroupTests (a Pinned-band group maps pinned),
WorkspaceGroupFlatMenuTests (common rows top-level, no catch-all folder,
every row in the palette), GroupPaletteDefaultTests (right-click shows the
menu, editor rows without More). Design: plans/cmux-next/sidebar-ordering-DESIGN.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(next): pinned groups read as pinned and unpin; a group header's right-click is one flat menu

Root cause (plans/cmux-next/sidebar-ordering-DESIGN.md): the groups Move to
Top left a workspace under were pinned. Pin Group moves a group into the
Pinned band, above every machine section, but SidebarDocSections mapped
every document group with pinned: false, so no pin glyph showed and
Pin/Unpin always pinned. A Pinned-band group now maps pinned (the header's
pin, Unpin Group in the menu and editor), and Pin/Unpin toggles from what
the sidebar shows.

The header's right-click opens the group's menu at once (no editor, no More
Group Actions row): Color, Set/Remove Icon, Pin/Unpin, Collapse All/Expand
All/Collapse Others, Move to Top, Ungroup, Close Group, Delete Group and
Close Other Groups are top-level; Move and Sort keep their lists. The editor
offers Pin and Move to Top itself. New Workspace in Group, Pin/Unpin Group,
Mark Read/Unread, Clear Notifications and Edit Group Config join the palette.
Pin and Collapse rows read as the change they make. Strings in all 21 locales
(19 need review).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(next): the flat group menu keeps 12 top-level rows

ActionSurfaceParityTests.everyMenuKeepsAShortTopLevel (Lawrence 2026-10-01:
at most 12 top-level rows) failed at 21. The rows Lawrence named stay
top-level (New Workspace in Group, Rename, Color, Pin/Unpin, Collapse/Expand,
Move to Top, Ungroup, Close Group) with Delete Group and Close Other Groups;
Options ▸ takes icon, read state, collapse-all, config and Duplicate, and
Move ▸ takes the group sorts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(next): Copy Group ID joins Options, so the group menu keeps 12 top-level rows

Its one-row Copy folder showed inline as a 13th row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(next): regenerate action exports for the flat group menu

Regenerated on the fleet (key a219-exports-820ba663b9c,
scripts/measure/export-action-surfaces.sh). check-group-menus.py passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(next): TitleCoalescerTests takes mutating results before #expect (the pattern that broke CEFPumpScheduleTests)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
Co-authored-by: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com>
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.

1 participant