Skip to content

docs: gh-merge-green is optional on feat-cmux-next (no bead) - #20567

Merged
teamleaderleo merged 1 commit into
feat-cmux-nextfrom
merge-green-optional-docs
Oct 14, 2026
Merged

teamleaderleo merged 1 commit into
feat-cmux-nextfrom
merge-green-optional-docs

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Lawrence and Leo: gh-merge-green is optional on feat-cmux-next, and nothing should tell agents to use it. A plain gh pr merge --squash --match-head-commit <sha> under the branch's rules is fine. The helper's fast gate is in #20566 (main, where the helper lives).

  • docs/ci/cmux-next-batch.md: by hand, the owner lands a validated PR with a plain merge or the optional helper. Only the queue's own --land uses gh-merge-green.
  • scripts/ci/next_batch.py: the green receipt now says "land it yourself on this receipt (gh pr merge --squash --match-head-commit; gh-merge-green is optional)".

Verification: python3 tests/test_next_batch.py passes (it asserts "land it yourself" in the receipt).

Changelog

none

🤖 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

Clarifies that gh-merge-green is optional on feat-cmux-next and that nothing should tell agents to use it. A plain gh pr merge --squash --match-head-commit <sha> is fine under the branch's rules.

  • Updates docs/ci/cmux-next-batch.md so only the queue's own --land uses gh-merge-green; owners land validated PRs with a plain merge.
  • Changes the green receipt in scripts/ci/next_batch.py to say "land it yourself on this receipt" and note that gh-merge-green is optional.

Written for commit 1b5f222. Summary will update on new commits.

View guided diff Turn on auto-fix

A plain gh pr merge on the exact head is fine under the branch's rules; the
batch queue's own --land still uses the helper.

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: f9ebf311-23d9-449d-aa74-2e856f822645

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

Copy link
Copy Markdown
Collaborator Author

Bead cx-trtcc created for this PR by the cmux coordinator.

@teamleaderleo
teamleaderleo merged commit c5b55f8 into feat-cmux-next Oct 14, 2026
78 checks passed
@teamleaderleo
teamleaderleo deleted the merge-green-optional-docs branch October 14, 2026 10:59
teamleaderleo added a commit that referenced this pull request Oct 14, 2026
…s picked (cx-uq9xi) (#20506)

* free tier: the local relay owns the device lease; the cmux NEXT app is its App Attest provider; freeModel.enabled (cx-ds09i)

Relay (cmux-coderouter free_tier.rs): attest_provider on the admin socket,
peer-checked (same user; a Team-signed build: the cmux app that contains the
relay, cmux-link app_caller), production origin only in release apps (staging
and the dev Worker only in DEV builds), one provider and one request at a time.
The relay builds the client data hash from the server challenge, keeps the free
token in memory only (never in status or logs), drops it when the provider
ends, when the setting turns off or an account signs in (also for a lease in
flight), and starts a lease only at the first signed-out data-plane request
(20 s, in its own task). App Attest refuses a second attestation of a key in a
later launch, so a new month takes a fresh key. The account bearer wins.

App: Packages/Shared/CmuxAppAttest (DeviceAttesting, AppleDeviceAttest,
KeychainAttestKeyStore); FreeTierAttestProvider (CmuxNextAgentPane) answers only
purpose cmux.free-tier.v1 over a 32-byte hash on an AcpmuxCheckedLine
connection, one keychain item per bundle id; AppServices+FreeTier follows
freeModel.enabled (default on, MDM lock, agents refused: privacy) and the
sign-in (a restoring session counts as signed in).

Behavior tests: acpmux/tests/router_free_tier.rs (real relay, admin socket,
stand-in app and API; 7 tests, Linux rbx and hosted macOS 38362901804). Red
check of the in-flight race at a3b1066ba9c7 recorded on cx-ds09i.
scripts/cmux-next/free-tier-attest-e2e.sh: T1/T2 on a real Mac.
Security review hq-84: APPROVE at 8b1c3f861a1e.

* settings: tip catalog with the four freeModel strings (21 locales); exports reset to the tip before regeneration (cx-ds09i)

* test(cmux-tui): Chief registry behavior tests at the socket and the CLI (red) (cx-ebm.60.1, cx-ebm.60.2)

The person makes, edits and deletes Chiefs with cmux chief new|list|edit|delete;
a Chief on its own bound connection changes its own picture and only the
fields its self_edit lists; another agent is refused. Red: the daemon has no
chief.list/get/create/update/delete/avatar.* operations yet.

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

* feat(cmux-tui): the daemon's Chief registry and cmux chief new|list|edit|delete (cx-ebm.60.1, cx-ebm.60.2, cx-ebm.60.3)

Multiple Chiefs per Chief home (Lawrence 2026-10-14: "create new chiefs too
with specific roles, names, ... profile pics (chiefs need to be able to do it
themselves too), personality, system prompt, tools").

Daemon: chief_registry.rs owns chiefs.sqlite3 in the session state
directory. A record has id (= the conversation participant id; agent_mux is
the default Chief, written on first open; new ones agent_<26 base32>),
display_name, role, personality, instructions (system prompt additions),
avatar (PNG/JPEG/GIF/WebP <= 1 MiB, bytes by SHA-256, collected when no
record names them), tools (closed catalog), harnesses, engine, self_edit,
is_default, rev. Deleted ids stay as tombstones. Idempotency ledger per
caller.

cmux.protocol/2: chief.list, chief.get, chief.avatar.get (read);
chief.create, chief.update, chief.delete, chief.avatar.set (mutation).
Callers: the owner (Unix, no link peer, user_local) and the Chiefs (a
connection bound with conversation-bind to a live record's id). A Chief
reads every record and changes only its own self_edit fields (default:
avatar); tools, harnesses, engine, self_edit, create and delete are the
owner's; anything else is chief_self_edit_denied before any write. Pages,
WebSocket, relayed and remote connections and non-Chief agents get
origin.forbidden. Not MCP tools.

CLI: cmux chief list | new --name N [profile] | edit CHIEF [profile]
[--clear-avatar] | delete CHIEF, CHIEF an id or a unique name, --json.

schemas/chief-profile/tool.json: the Chief's self-edit tool (profile) for
the brain (wired in cx-ebm.60.5 after the per-build Chief home lands).

Catalog: 271 operations (84 read, 168 mutation).

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

* app: AcpmuxEnvironment.resolve for the free-tier provider (the tip removed AgentTabPaneSource.paneEnvironment) (cx-ds09i)

* settings: regenerated settings schema and MDM exports for freeModel.enabled on the tip (cx-ds09i; export-settings cmux-ci step 02f78faaf8d0)

* webviews: settings strings for freeModel on the tip (cx-ds09i)

* fix(cmux-tui): Chief registry ops are chief.profile.*; review fixes (cx-ebm.60.1, cx-ebm.60.2)

Review (correctness subagent, 2026-10-14):
- The cloud catalog already has chief.list/create/update with another
  shape; the app platform merges both catalogs by name with the local op
  winning, so equal names hid the cloud ops. The daemon ops are now
  chief.profile.list/get/create/update/delete/avatar.set/avatar.get; the
  generated app-host types keep the cloud chief.* ops.
- idempotency.conflict names the committed operation; the ledger keeps the
  newest 256 keys per caller.
- harness, model and effort are single-line.
- CLI: "needs a newer daemon" only when the first chief.profile.list call is
  refused as unknown; a value error prints the daemon's reason. The picture
  is checked for 1 MiB before any write; when the picture write fails after
  the create or edit committed, the CLI says what committed. edit with no
  change, and --clear-avatar outside edit, are usage errors.
- The spec states the trust limit (a Chief turn with a shell is the same OS
  user; the self_edit rule guards the model); hardening is cx-ebm.60.9.
- Generated: app-host d.ts/ops/scopes and the AppPlatform scopes copy.

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

* style(cmux-tui): rustfmt the Chief registry files (cx-ebm.60.1)

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

* style(cmux-tui): mod order for rustfmt (cx-ebm.60.1)

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

* cmux-next: the typeahead GUI gate in the repo (cx-wb5.78): typeahead-gate.sh runs split, newterm, split with a slow host, new-tab and cold new-tab proofs on the GUI host and fails on a lost key; typeahead-compare.py counts keys sent by terminal id; creation-hold-proof.py is superseded (named in the gate header)

* app: the free-tier composition moves to CmuxNextAgentPane (FreeTierAttestStarter); CmuxNextApp keeps a two-line hook (cx-ds09i; CmuxNextApp size cap)

* app: one-line free-tier hook; FreeTierAttestStarter owns its task for the app's life (cx-ds09i; CmuxNextApp size cap)

* +1 cx-ds09i launch hook, +1 tip drift from another lane; chief go

* free tier: keep the tip's ghostty pointer (cb1ef7091cde, #20024; a stale submodule checkout had moved it back); signed-in fallback true when the services are gone (cx-ds09i move-check)

* free tier: ci-target-graph.json and the workspace file are the tip's plus only the CmuxAppAttest lines (cx-ds09i final check: a stale regeneration had dropped CmuxNextBrowserHistory, CmuxNextCLIInstall, CmuxNextNowPlaying and two CmuxNextCompat test edges)

* settings: regenerated settings schema and MDM exports on the merged tip ae657738afc2 (cx-ds09i; export-settings cmux-ci step 788077f4a088)

* webviews: settings strings for freeModel on the merged tip (cx-ds09i)

* chore(cmux-app-host): regenerate the app catalog after the tip merge (cx-ebm.60.1)

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

* acpmux: a Windows client whose daemon lost the start lock waits for the winner's socket; fixes the Windows start code of landings 2/3 (2f063f889c, 1fdb2a766b) (cx-ncc.54)

two_starts_at_once_share_one_daemon failed on the hosted Windows runner (cmux-tui full run 38352677155): the losing start's client connected once before the winning daemon had bound its socket ("read the owner: The system cannot find the file specified"). It passed on run 38352027335 (same start code), so it is a race in the Windows start that landings 2 and 3 put on feat-cmux-next, not in the agent job change. Now, when the started daemon exits without a readiness line, the client waits (within the same start budget) while another daemon holds daemon.lock and the socket file is missing. The probe takes daemon.lock for an instant when it is free (Windows cannot test a LockFileEx lock without taking it); the risk is written in a code comment. The change is in daemon/windows.rs; daemon.rs does not grow.

* acpmux: owner-only files and folders on Windows (cx-ncc.54)

What is mode 0600 / 0700 and a uid + mode check on Unix is an access list
on Windows (src/owner_only.rs):

- Written files are created (CreateFileW, CREATE_NEW, no reparse-point
  follow) with a protected DACL that grants only our token user, our user
  as owner: write_atomic (config.json and every file written through it:
  handoffs, the registry cache, harness profiles), the LocalApp and peer
  token files, private agent files, preset system prompts. daemon.log is
  opened that way and narrowed if a wider one exists.
- Folders made here inherit the same list (0700 on create):
  harness_admin, handoffs, the registry cache, `cmux harness add` and
  `harness registry`, the doctor's temporary folder. ensure_private_dir
  (the run folder of the token files) makes or checks one and refuses a
  wider folder, as on Unix. narrow_to_owner replaces a wider list on
  config.json.
- Profile reads (user and folder profiles): a file another user can change
  is refused, as `mode & 022` or a foreign uid is on Unix. "Another user"
  is the owner, or a SID granted write, delete or ACL rights, other than
  our user, SYSTEM, Administrators or OWNER RIGHTS (they can change any
  file, as root can); deny and inherit-only entries grant nothing; no DACL
  grants everyone. The diagnostic names the SID and gives the icacls fix.

Not in this landing: the Computer Use endpoint check (cua_v2, with its
socket), router routes (the router landing) and the remote-floor file
identity checks.

Behavior tests (tests/windows_daemon.rs; green on the Windows Server 2022
VM): after `acpmux daemon start`, config.json, daemon.log, run\,
run\localapp.token and run\peer.token are owner-only (the SDK's
owner-only check); `acpmux harness add` writes an owner-only profile that
`harness list` loads, and after `icacls /grant Everyone:(M)` the list
refuses it with "another user (S-1-1-0) can change this file" and the fix.

* acpmux: Windows owner-only review fixes (cx-ncc.54)

- daemon.log is opened shared for read, write and delete, as std opens
  are: a running daemon holds it as stdout and stderr, and a second start
  (two clients at once) must still open it; with read-only sharing it
  failed with a sharing violation.
- daemon.log must be the file itself, not a link: the access list checks
  and the narrowing go by path.
- write_atomic removes a temporary file a crashed process left under a
  reused pid, then creates it again (CREATE_NEW refused it).

Behavior test: two acpmux daemon start at once on a new home both succeed
and name the same daemon (green on the Windows VM).

* acpmux: the Windows narrow_to_owner body moves to owner_only.rs, so daemon.rs does not grow (cx-ncc.54)

daemon.rs is over the Rust god-file budget on feat-cmux-next (not in the
baseline), and the CI god-file check fails any growth. The Windows
narrow_to_owner (an existing file or folder that is not owner-only gets
the owner-only access list; a failure is logged) is now
owner_only::narrow, imported under the old name. No behavior change.

Base reds: exact base 7b86415e2d82 (cmux-tui full run 38529816383). The gate excludes a red job only when that run has the same job red with the same error lines; expected from the earlier base 12b1e29b82d9 (run 38357900702): "valgrind-leak-check (startup)" and "valgrind-leak-check" ("cmux_tui-f9329b8abfb86495 does not contain exact test config::tests::ghostty_config_files::load_uses_file_ghostty_defaults_without_invoking_external_resolver"; valgrind 0 errors). This stack changes only cmux-tui/crates/acpmux. The first commit of this landing is the Windows start-race fix for landings 2/3 (2f063f889c, 1fdb2a766b).

Linux tests (chief decision 2026-10-14, cx-f4thx): this landing changes no
Linux-compiled line (every hunk is cfg(windows) code or under it). Its gate
accepts a red "test (linux)" only when the only failing test is
router_lifetime::a_kept_agent_keeps_its_router_and_the_next_daemon_adopts_it,
a feat-cmux-next flake ("agent response channel dropped" on the first
prompt; Freestyle loops: 4/120 on aebcd4572fc6, 2/40 on this stack; its
cause, a double host adoption, is fixed by cx-f4thx 7b86415e2d82). darwin,
windows, the Linux build and clippy must be green.

* acpmux: red test, a direct agent's moved-out helper outlives the model probe and a kill (cx-n3uid)

Behavior test at the daemon socket (real acpmux binary, agent hosts
off): the fake harness starts a setsid helper on session/new.
_acpmux/models {refresh:true} waits for the model probes; every probe
helper must be gone within 10 s. A direct session agent stopped with
_acpmux/kill must take its helper too. ChildAgent::kill/terminate end
the harness with killpg only, so both helpers run on with ppid 1.

* acpmux: ending a direct agent ends what its harness moved out of its group (cx-n3uid)

The model probe (probe_one, at each daemon start, config reload and
models --refresh) and a direct session agent (agent hosts off) end the
harness through ChildAgent::kill/terminate: killpg only. A helper the
harness moved into its own session (setsid, Node detached:true) missed
the group signal and ran on with ppid 1.

As the pool does for a SIGKILLed host (cx-7gs9r, terminate_unadoptable):
take the processes under the harness by pid and start time
(agent_host::tree::descendants) while the harness is not reaped, signal
the group, then SIGKILL the ones still running (kill_still_running).
Both ps reads run off the runtime. Unix only.

harness_helpers.rs: red before (both helpers ran on after 10 s), green
after (rbx).

* acpmux: review follow-ups, name what the direct-agent tree end does not reach; the helper test cleans up on failure (cx-n3uid)

* acpmux: red test: with nothing picked a new chat starts on the harness's own config (cx-uq9xi)

Codex's config.toml model and model_reasoning_effort, and Claude Code's
settings.json model (an alias resolved to the catalog id), come before the
catalog default; the person's pick still wins; a config that does not parse,
or names a model the catalog does not list, falls back to the catalog.

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

* acpmux: a new chat with nothing picked starts on the harness's own config (cx-uq9xi)

Leo 2026-10-14: the composer's starting model and effort are the last pick,
else what the harness's own config chooses, else the catalog default.
start_defaults now reads Codex's config.toml (`model`,
`model_reasoning_effort`, the active `[profiles.<profile>]` first) and Claude
Code's settings.json `model` (harness_config.rs), between the saved pick and
the catalog. A config model counts only when the catalog lists it (an alias
names its id) and a config effort only when it fits the running model; a file
that is missing or does not parse chooses nothing. `_acpmux/harnesses` reports
the new source as "harness". The start_defaults test homes give each fake its
own config folder, so the machine's ~/.codex and ~/.claude never leak in.

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

* CmuxNext: chrome fonts are nil-checked; a nil font no longer aborts keycap drawing (no bead)

nxtip3-v1 (772b6ea30a5b) aborted at 01:42 in PaletteKeycapsView.capWidth:
NSString.size(withAttributes:) -> CoreText TAttributes::ApplyFont ->
-[NSDictionary initWithDictionary:copyItems:] "attempt to insert nil object
from objects[0]".

Cause: Typography.shortcut (NSFont.monospacedSystemFont(ofSize:weight:),
imported non-optional) returned nil on the main thread. Swift kept the null
NSFont in [.font: ...]; CoreText found no font, copied the attributes to
apply its default, and the copy hit the nil. Reproduced on cmux-lawrence-2
(macOS 27.0.1): a nil font in the attributes gives the same exception and
frames; a nil foreground color does not abort. Ruled out: the size (NaN, 0,
negative and infinite sizes all return a font there), the weight, and a
call off the main thread (the faulting thread is the main queue). The
laptop log has no fontd or CoreText error and no jetsam before the crash;
why AppKit returned nil is not visible.

Fix: CheckedFont tests each AppKit font for null (an optional bit cast in a
non-inlined function, which -O does not fold) and falls back to the system
font, then CoreText Helvetica, then the last font that resolved, with a
fault log. Every Typography font (body, caption, header, search, shortcut,
title, subtitle) goes through it, so the palette keycaps, the which-key
overlay and every other Typography caller measure and draw with a non-null
font; ShortcutHintChipView and the braille spinner font too.

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

* feat(cmux-next): one shared native popup row; page menus and the Extensions menu draw their rows with it (cx-jpi3o, REUSE.md slice 2)

PopupRowView (CmuxNextDesign/Overlay) owns what every native popup row shares:
the leading column, title and subtitle, a trailing view, the gray highlight in
the popup row radius, press fill, gray focus ring, hover tracking, release-inside
click, Return/Enter/Space and the accessibility press. WebPopupRows stacks
PopupRowViews (the page keeps the one highlight); ExtensionMenuRowView is a
PopupRowView with the system menu font and colors.

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

* free tier part 2: settings sources and exports equal the tip (landed in part 1, 7bd91dc3129c) (cx-ds09i)

* chore(next): PR autofix at 73b393e337d9

- regenerated schemas/defaults/cmux-next.json

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

* chore(app-platform): sync the AppPlatform scopes copy after the tip merge (cx-ebm.60.1)

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

* Channels view: connect with no credential when the page's origin already authorized the person

A plain-browser Channels page whose daemon endpoint is its own origin first asks
`GET /auth/session`. When that server answers {"authorized": true} (a relay that
identified the person, for example by a private-network identity, and attaches
the daemon credential upstream itself), the page connects with the new
`{ origin: true }` auth: no preamble, no form, and the browser never holds the
credential. Any other answer (a 404 from a daemon that serves the page itself
included) shows the credential form as before; a refused origin connection falls
back to the form too.

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

* Channels view: origin auth comments (review)

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

* acpmux: behavior test, a new chat starts in full access; the person's switch back is saved; every chat gets the cmux MCP server (red) (cx-wx93i)

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

* acpmux: new chats start in the harness's full-access mode; the person can switch back and the pick is saved; every agent chat gets the cmux MCP server (cx-wx93i)

Lawrence 2026-10-14: "give every agent chat the cmux MCP tools by default,
but by default all chats should be in skip permission mode, that user can
switch back".

- hub/start_mode.rs: once the harness is up, acpmux sets the start mode with
  session/set_mode (soft, like the start model): the person's saved pick
  (config.json defaults.<profile>.mode), else acpmux config.json
  defaultPermissionMode, else the cmux setting agents.defaultPermissionMode
  (fullAccess | standard | ask), else full access: Claude Code
  bypassPermissions, the Codex bridge agent-full-access (approval never +
  danger-full-access), or a harness's yolo/full-access mode. Not for Web
  (remote-origin) chats, presets, folder profiles, adopted chats, or a
  profile that pins its mode in argv.
- A mode pick from the person's app pane (session/set_mode or the mode
  config option) is saved; Plan is a toggle and is not saved; a pick equal
  to what the setting gives removes the saved mode. Only the person may
  clear a saved mode (_acpmux/defaults clear), as for a stored policy.
- claude_stdio: offers bypassPermissions ("Full access") and launches Claude
  Code with --allow-dangerously-skip-permissions when it accepts it (not
  root, or IS_SANDBOX=1); remote chains strip the flag.
- agent_tools: the cmux MCP server (cmux mcp serve) is on for every agent
  chat unless the settings file says mcp.enabled false; the settings file is
  now the app's cmux-next.json (user_settings.rs), not classic cmux.json.
- cmux mcp serve: on when CMUX_AGENT_SESSION is set and mcp.enabled is not
  set; any other client still needs mcp.enabled true.

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

* review: popup row title end yields in a narrow row; 12 pt before a shortcut; a disabled row's check mark is quiet (cx-jpi3o)

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

* ci-guards: keep the step name "Run agent-chat tests" (cx-eczvt)

Main sync merge 3bf71fdcda1e brought back main's "Run agent-chat unit
tests"; feat-cmux-next renamed it after the unit-test deletion (cx-034r,
cx-ys07v). test_ci_guard_workflow_structure and test_ci_change_areas now
name the same step.

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

* feat(cmux-next): Page Info on the shared native popup host; its rows on the shared popup row (cx-x9o52, REUSE.md slice 3)

PageInfoPanel is a NativePopupPanel: placement through PopupPlacement, the
host's grow/shrink motion and dismiss rules (click outside with the omnibar
button kept open, Escape, key loss, app inactive, window move/resize/close).
The hand placement, PopupFade and the mouse-down reopen guard go. The card no
longer draws its own shadow band (PopupHostView draws the one popup shadow).
PageInfoRowView is a PopupRowView. NativePopupPanel joins the child-window
allow list (the bookmark bubble tripped it in DEBUG). PageInfoMotionTests (a
unit test of PopupFade on PageInfoPanel) is deleted under the no-unit-test rule;
the live check covers the bubble.

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

* fix(cmux-next): a new bookmark's name follows the page title that arrives after the star (bookmark bubble)

The star saves the bookmark at once, often before the page has a title, so the
bubble showed an empty name. While the bubble shows, the page title (observed)
fills the name until the user edits it; close saves it as before.

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

* test: bookmark bubble live check covers the late page title, Page Info on the shared host (captures) and a real click on the star (--real-click) (cx-x9o52)

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

* review: Page Info rows keep their inset before the accessory (cx-x9o52)

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

* cmux-tui: the documented customize template installs as is in a real daemon (cx-swovq.1)

Behavior test through the daemon socket: the docs/cmux-next/sidebar-customization
folder, copied unchanged into the customize folder, is listed installed,
enabled and sandboxed with its workspace:read grant.

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

* CmuxNext: Codex-like status icons by default, plus static status glyphs (#20410)

* test(cmux-next): static status glyphs are apart by shape, crisp, still and theme-colored

Red: there is no static status set and no ui.status-glyphs contact sheet yet.

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

* CmuxNext: static status glyphs for the Static status mode

Leo 2026-10-14: six custom static glyphs for op-next-motion's Static status mode
(Reduce Motion): a hollow ring (idle), a half-filled ring (working), an empty
speech bubble (waiting on you), a shield (needs permission or a sign-in), a check
disc (done) and a ! diamond (error). Told apart by shape alone at 10 pt, tinted by
their theme role, never animated.

One source, schemas/status-glyphs/static-status.json: a 20-unit master (10 pt at
2x) and 10 px pixel art for 1x. gen-status-glyphs.py writes the Swift
StaticStatusGlyph (CGPaths) and the web statusGlyphs.generated.ts (SVG paths);
--check runs in bun run check. StatusMark.vector draws a glyph, StatusIconSet.still
(Static) maps every state to them, and StaticStatusGlyph(state)?.plan is the hook
for the Static setting. The gallery sheet ui.status-glyphs shows each live, in a
row, and its 1x and 2x pixels, plain and on a wallpaper.

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

* test(cmux-next): status glyphs at 2x probe their straight edges

A share-of-soft-pixels cap cannot tell a curve from blur (a ring half a pixel off
reads the same), and the idle ring's 63% tripped it. At 2x the test now probes
pixels on each glyph's straight edges, which are whole only when the drawing sits
on the pixel grid; half a pixel off, every glyph leaves some half covered.

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

* CmuxNext: the Static set keeps a known-progress ring

Review: StatusIconSet.still drew the working glyph for an agent that reports
progress, dropping how far along it is. That ring never moves, so the Static
set keeps it.

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

* test: Codex-like status set draws grey work, blue done, red error

Red: StatusIconSet has no codex case, there is no spinner or unread
tint, and the web sheet has no codex glyphs (shieldLine, info, dot).

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

* feat: Codex-like status icon set

A Debug set for the status indicators that follows Codex's sidebar:
Lawrence's shared spinner re-tinted to the foreground at 66% (a still
ring under Reduce Motion or Static), Codex's blue (#3982F5) done dot,
muted line bubble and shield for waiting, and a red line i for error.
The schema gains a codex set that the generator emits for the web
copy, and the gallery sheet gains codex variants.

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

* test: Codex-like still loader stays a stopped arc

Red: with motion off, the Codex-like set turned every arc into the
still work ring, so a busy page or command loader read as agent work.

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

* fix: Codex-like still ring only for agent work

Only working without progress becomes the still ring when nothing
animates; a busy loader keeps its stopped arc. The glyph generator now
checks that every set picks a glyph for every state from its own set or
the static set.

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

* fix: Codex blue as a theme token; document the idle/info 2x floor

Codex's blue moves to --cmux-status-unread in webviews/src/theme, so
the generated glyph table holds no blue literal (check-system-blue).
At 2x the still work ring and the error ring differ only by the i,
16 whole pixels, and they differ in color too; the test says so.

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

* test: the Codex-like status icons are the default set

Red: the default is still symbols.

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

* feat: Codex-like status icons are the default

Leo 2026-10-14, a team decision: the grey spinner (a still ring under
Reduce Motion), Codex's blue done dot, muted line glyphs and a red
line i for errors, for everyone. Symbols, Static and the other sets
stay as Debug alternates.

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

* fix: Codex blue as a theme constant, not a page stylesheet color

A literal color in a page stylesheet trips the theme-colors ratchet, so
the blue moves to STATUS_UNREAD in webviews/src/theme/statusColors.ts
and the generator writes it as the fallback of --cmux-status-unread.

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

* chore(next): PR autofix at a8608cf74601

- vp fmt webviews/src/agent-session/acpmux/modelPickerLayout.ts

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

* test: the status glyph and count badge center on the row's middle, one line or two

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

* fix: the status glyph centers on the row's middle beside the badge; a 6 pt done dot

The glyph sat on the title's capitals, so on a two-line row it rode above the count badge
(Leo 2026-10-14: "why is it superscript?"). It now centers on the line the badge and the
other trailing marks share. The Codex-like done dot grows from 4 pt to 6 pt ("too tiny").

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

* test: a status glyph at 3x or 5x draws the master, not the 1x pixel art blown up

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

* fix: only a 10 px status glyph takes the 1x pixel art; 3x and 5x draw the master

Review on #20410: the odd-multiple-of-10 rule sent 30 and 50 px glyphs to the 10-unit art,
blown up in coarse blocks.

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>

* cmux-next: pin CEF cef-154.0.28-cmux.25 (cmux.24 + WebAuthn hybrid fix with a PIN key attached; fork API 23) (cx-d0d.97)

* cmux-next: remote browser host linked against CEF cmux.25 (step beeef9df6b98) (cx-d0d.97)

* passkeys-roaming-e2e: --typing smoke with trusted key events (cx-d0d.97)

* passkeys-matrix-e2e: a timed-out addVirtualAuthenticator reply counts as sent (the socket deadline is now 6 s) (cx-d0d.97)

* chrome-promos-live: ignore Chromium's status bubble (<= 30 pt at the page window's bottom-left), logged; any other new window still fails (cx-d0d.97)

* passkeys matrix + promo check review fixes: unconfirmed authenticator rows that pass without one are NOTE; promo count uses only new windows (cx-d0d.97)

* CmuxNext: no gray ring trails a split (#20500)

* CmuxNext: test that a split's focus ring lands on the new pane in the apply that mounts it (red)

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

* CmuxNext: no gray ring trails a split

The split's daemon tree mounted the new pane and the layout view drew
the focus ring around the old pane at its new frame; the focus
coordinator landed focus on the new pane in the same apply, but the view
mirrored it only on its observation's next turn. Sync the view again
after the topology so the frame rings the pane focus landed on.

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

* CmuxNext: one line for the split focus sync (app size cap)

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

* CmuxNext: split focus ring test compiles (closure, not a key path, in #expect)

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

* chore(next): PR autofix at 44de1da3d855

- regenerated schemas/defaults/cmux-next.json

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

* chore(next): PR autofix at c7d76dbf2fb3

- regenerated schemas/defaults/cmux-next.json

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

---------

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: tabs take the max width while the strip has room

Lawrence 2026-10-14 asked for max width: "default widths for new tabs
still have the same max width and try to grow as much as possible (ofc it
can shrink tho)". This reverses the fit-to-title rule from 7f9fc1ed53f2
(merged into feat-cmux-next with 3ef63bc02144; that titlebar commit carries
Lawrence's name as author but was hq-1d's worker change).

Every unpinned tab takes the max width (Metrics.tabMaxWidth) while the
strip has room; a crowded strip shrinks all tabs evenly down to the
minimum, then scrolls. The tabs.minFitTabWidth tunable,
TabLayoutItem.naturalWidth, TabStripMetrics.naturalTabWidth and the title
measurement in TabStripView+Layout go with it. That measurement ran
NSString.size for every tab title on every layoutItems call, on the main
thread, also during strip springs; it no longer runs. Separators, the
traffic-light center line, the + position and the reserved close-button
room are unchanged.

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

* cmux-next: debug.pane_chrome reports each tab's slot

tab_slots ([x, width] per tab in strip content points) and tab_available,
so a proof can read the tab width rule without pixels.

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

* free tier: a Keychain error never mints a new App Attest key; no key-id read where App Attest cannot work (cx-5bxp7 P3-A, P3-B)

AttestKeyStore load/save/delete throw on a store error (load: nil only for
errSecItemNotFound). The provider answers failed when the key id cannot be read
or saved, so it never generates or attests a new key then (a new key spends the
device's monthly grant), and it saves a new key id before attesting it. The
register line skips the Keychain read when App Attest is not supported (no
prompt in an ad-hoc DEV build).

* acpmux start mode: review fixes (cx-wx93i)

- Settings file read as JSONC like the app (comments, trailing commas); a
  file acpmux cannot read or parse, or an unknown value, gives the harness's
  own mode, never full access. Classic cmux.json only when cmux-next.json
  does not exist. The setting is resolved before the config write lock.
- A remote chain never starts Claude in bypassPermissions (a transfer keeps
  the mode): the spawn mode and args fall back to default.
- A handoff target starts in full access only from a full-access source and
  never with narrower.
- A profile with its own skip flag keeps its mode on respawn; config reload
  picks up defaultPermissionMode; acpmux reads mcp.enabled only nested, as
  the CLI does.
- Tests: the fake Claude refuses bypassPermissions without the launch flag;
  the settings-file test is its own binary (it sets CMUX_NEXT_CONFIG_FILE)
  and covers JSONC, no key, an unparseable file and an unknown value.

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

* cmux-next: Ctrl and Option holds show their shortcut hints too; hint chips are Liquid Glass in the theme's tint (cx-w9f3s)

Lawrence 2026-10-14: "we need cmd hold tab to work for hold ctrl as well,
maybe option if there are relevant shortcuts, we also need it to be liquid
glassy".

- A hold arms on any set of Cmd, Ctrl and Option (with or without Shift),
  not only bare Cmd or Ctrl. The overlay shows each target's binding from
  the resolved table (keybindings.json and cmux.json overrides included)
  whose modifiers contain the held set; a hold with no matching binding
  shows no chip. Same 300 ms delay, key-down suppression and placement.
- ShortcutHintChipView: a full Liquid Glass capsule (GlassPanelView ->
  NSGlassEffectView) tinted with the theme's glass tint, text in the
  theme's primary text color. macOS 14/15: the same panel's blur, tint and
  hairline. Reduce Transparency: the theme's opaque popup surface, a
  device-pixel hairline and the soft shadow.
- debug.shortcut_hints takes opt, shift and +-joined sets (ctrl+opt).

CmuxNextApp line count unchanged (113450).

* cmux-next: hold hints review fixes: only Cmd/Ctrl chords get a chip, a click suppresses, stronger chip tint (cx-w9f3s)

Review of d77c9ec3f078 (P2s): a chip only for bindings KeyRouter runs
(KeyRouter.isChord: a pure-Option binding types a character); a mouse-down
while a modifier is held hides and suppresses the hints like a key-down
(Option-click / Option-drag in a terminal); the chip's glass tint is the
theme tint at 1.5x alpha so 10 pt text keeps its contrast over bright
content. P3s: no double Borders.color, no continuous curve against a
circular shadow path, debug release only alone. plans/cmux-next/keybindings.md
describes the Cmd/Ctrl/Option holds and the glass chip.

CmuxNextApp line count unchanged (113450).

* review: Page Info keeps its side on a new page; any NativePopupPanel passes the child-window policy; the bookmark name follows only the bookmarked URL's title; live check presses the star once (cx-x9o52)

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

* free tier e2e runner: never kill; the app quits through its control socket (action.run quitEndSessions, peer-checked), the daemon by acpmux daemon shutdown (cx-ds09i T1/T2)

* New Tab: never block launch on a slow WebKit setup (#20484)

* test: launch prewarm does not wait for a stalled WebKit

NewTabSparePool.makeSpare becomes a seam (no behavior change) so the test puts a WebKit that
blocks for 2 s in its place.

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

* fix: the New Tab spare never makes a web view before WebKit's setup answers

Launch no longer makes the spare. prewarm() asks the sandbox broker off the main thread
(WebKitSetupGate, 2 s timeout); the first spare follows the first window's first frame once it
answered, and a spare that held the main thread over 1 s marks WebKit slow for the run. A wedged
sandboxd (cua-2: WKWebView load blocked >10 s in sandbox_extension_issue) gets no spare.

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

---------

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

* backend: behavior test for a Chief brain on Durable Objects answering in Cloud conversations (red)

The owner turns the chief's brain on over HTTP; a person writes in the chief's DM and in a
channel over POST /v1/ops; the chief answers as itself (typing, then the message, on a member's
socket), in the channel message's thread, with a run reference whose run and steps only members
read. Fails before the brain exists (the routes answer 404).

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

* backend: ChiefBrainDO, a Chief that answers in Cloud conversations on pi-durable with OptChat memory

One ChiefBrainDO per chief (agent id): the optchat-lab impl/pi-durable turn loop ported into the
API Worker (agents SDK PiHarness, pi-durable, the OptChat Rust core as wasm over DO SQLite,
background compactor, one fresh pi session per turn).

- MuxDO forwards each committed mux.wake (DM, mention, reply, wake_policy all) to the brain where
  CHIEF_BRAIN_ENABLED is 1 (staging, development; not production).
- The brain pulls the conversation's new messages as the chief, runs one turn per conversation
  batch, and posts one message.send as the chief (in a channel, in the thread of the message it
  answers) with a work part naming the run; NO_REPLY posts nothing; then mux.ack. A refused post
  (loop guard agent_rate) retries under a new key; no answer retries under the same key.
- Realtime: ConversationDO.agentFrame sends the chief's typing (same gate as a socket) and
  conversation-run-changed frames (TRAJECTORIES Run/Step JSON) to member sockets, never stored.
- HTTP: GET/PUT /v1/chiefs/<agent>/brain (owner: enabled, model, role, personality,
  instructions), GET /v1/chiefs/<agent>/runs[?conversation=] and /runs/<run> (members only).
- Model: fake by default; Workers AI through the Worker's AI binding where CHIEF_BRAIN_MODEL names
  one (staging: @cf/moonshotai/kimi-k2.7-code). The team subrouter is tailnet-only.
- DO migration v15 (ChiefBrainDO). firstName moves to its own file (ConversationDO size limit).

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

* backend: Chief brain review fixes: one memory per conversation, live-chief and placement checks, bounded turns

Review (subagent, REVISE) findings fixed:
- P0: one OptChat memory for every conversation could carry one conversation's messages into
  another's reply and run steps. Now one brain per conversation (ChiefBrainDO name
  <agent>/<conversation>); the chief's settings object (name <agent>) holds the owner's settings.
- P1: every turn and post asks the settings object: enabled, the chief record live (archived chiefs
  stop at once) and not placed on a paired server, under a daily turn limit
  (CHIEF_BRAIN_DAILY_TURNS, default 300). MuxDO forwards only for brain cloud.
- P1: the reply answers the newest woken message (rows carry woke), not the newest message.
- P1: the compactor wait is bounded (60 s, then the view as it is); a turn stops after 10 min.
- P2: post retries reschedule through the job outcome; no concurrent post of one run; a drain that
  ends re-checks the queue; the test fires the brain's alarm.
- P3: run reads need ?conversation= and check membership before touching a brain; NO_REPLY match
  ignores punctuation and case; replies are clipped under the text limit.

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

* backend: Chief brain: a transient owner-record error retries later instead of skipping; a failing drain backs off

Re-review (APPROVE) follow-ups 1 and 2: owner.unreachable leaves the woken messages queued and
schedules a drain job; a drain that threw schedules its next try instead of looping at once. Reply
helpers move to brain-text.ts (size limit).

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

* fix(cmux-next): macOS 27 base reds: CLI parity person-only refusal, cancelled load keeps the new address (#20373)

* test(cmux-next): a New Tab load that cancels a loading page shows no preview of it (red)

Replays WebKit's callbacks in the order the macOS 27 lane hit
(NewTabHoverPreviewTests:50 on #20342): the New Tab address arrives,
then the cancellation of the load it replaced puts the old address back.

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

* fix(cmux-next): a cancelled load keeps the address of the load that replaced it

When a new load replaced one still provisional, WebKit reports the new
address first (the URL KVO, or the old load's late start reading
webView.url) and then the old load's cancellation. The state machine
reverted the address to the last committed one on that cancellation,
so until the new load started the tab showed the old address: the
hover card pictured the old page over the New Tab page
(NewTabHoverPreviewTests:50 on the macOS 27 lane) and the omnibar
flickered back. A cancellation whose failing URL is not the address
shown now keeps it, and never restores the old error page.

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

* test(cmux-next): CLI parity expects the person-only refusal

browserProfile.delete is person-only since 596dd7a (cx-dwg.10): it keeps
its CLI verb but the control socket refuses every run.
CLISurfaceParityTests:48 still expected the run to reach its handler.
A person-only action now has to answer exactly the person-only refusal
(code unavailable, the personOnly reason) and run nothing; every other
CLI action still has to run its own handler.

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

* fix(cmux-next): keep a replaced load's address by its start, not its failing URL

Review: comparing the cancellation's failing URL with the address shown
broke CEF, where the start carries the URL cmux asked for and the
failing URL is Chromium's (after a redirect): a redirected load aborted
as a download kept its address over the committed page. The address now
stays only when it moved off the cancelled load's own start/redirect
address, which in CEF it never does before commit.

WebKit can also report an old load's start after a newer one was asked
for, reading the newer address; the tab now tells the state machine
which navigation it asked for last (`requested`), and the start of an
older one is ignored, so its cancellation changes nothing.

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

* fix(cmux-next): drop the requested-navigation rule; test the real order

Re-review: the late-start order cannot revert in practice (the New Tab
URL KVO arrives first while nothing is provisional and moves the
committed address too), so the test half for it could not fail, and
ignoring an older start could leave a real load untracked when the
newer request is refused by policy. Removed both. The test now commits
a page first, so the cancellation would put that page back on the base.
A cancellation WebKit already moved back to the committed page is not a
replacement, so a cancelled retry still shows its error page.

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

* chore(next): PR autofix at c26ac6910070

- regenerated schemas/defaults/cmux-next.json

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

* chore(next): PR autofix at b55536721932

- regenerated schemas/defaults/cmux-next.json

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

---------

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

* optchat-chief: Chiefs react to other Chiefs, with a per-thread loop guard

In local side conversations another Chief's message now wakes a Chief
(never its own; agents that are not Chiefs still never do). The loop guard
is computed from the conversation itself, so every host and a page agree
with no shared state:

- cmux_chief::rules::chief_streak: consecutive Chief messages at a thread's
  tail (or the top level) since a person wrote; at CHIEF_TURN_BUDGET (6) no
  Chief answers a Chief there until a person posts.
- cmux_chief::rules::owner_agent_streak mirrors the owner's own guard
  (MAX_AGENT_TURNS agent messages per conversation): a Chief stays quiet
  instead of running a turn whose reply the owner would refuse. That guard
  is lower than 6 today, so it is the one that stops Chiefs first.
- Both are taken at the newest message read, and a pause is logged
  ("Chiefs paused ...").

Side messages now carry the author's name in their label on the local
source, the Channels prompt says acks need nothing (NO_REPLY), and only a
person answers a pending approval. WakeRule replaces wakes_as' loose
arguments; wakes() is unchanged.

Test: channels_daemon.rs two_chiefs_wake_each_other_stop_at_the_budget_and
_a_person_resumes_them, two real brains on a real headless daemon. Red with
Chief wakes off (the Chiefs never reach the budget), green with them (twice).

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

* optchat-chief: loop guard review fixes

- the thread streak follows the thread a reply goes into (a top-level
  message's own thread), and retracted Chief messages still count;
- the owner-streak mirror stops only at a known person's text, skips
  authors who left, and resets at an answered question, as the owner does;
- side labels say whether the author is a person, a Chief or an agent.

Not done (P2 test gap): a behavior case where the 6-message thread budget,
not the owner's 4-per-conversation guard, stops the Chiefs. Any person's
text that resets the owner's streak also wakes the Chiefs in its own
thread, so with the owner guard as it is the thread budget cannot bind
first.

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

* cmux-next groups: a right-click on a group header or tab group chip opens the group editor

Lawrence 2026-10-14: "im talking about the rename + color things ... looks
like the image in chrome, we had it at some point". The editor bubble (name
field, color dots, group rows; cx-rcby, 3ca7be38252e) is still there, but
#19019 (31db8ce85faf) pointed the header's more button and its right-click
at the plain menu. #20442 put the more button back on the editor; this puts
the right-click back too, and does the same for a tab group chip, whose
right-click showed the app's menu (the editor opened only on a press and
hold). The full menu stays one row away (More Group Actions… in the sidebar
editor) and on Option-right-click of a tab group chip.

scripts/cmux-next/group-editor-live.py proves it with real right-clicks
(cua-driver): the editor opens with its name field, color dots and rows,
a typed name with Return renames the group, Escape closes it.

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

* cmux-tui: socket test for a workspace move during New Workspace losing tab identity (cx-95uaf)

The app's New Workspace: create-workspace, then the first terminal with a
reserved terminal id, while the app places the listed workspace with
move-workspace. A debug-only gate (CMUX_TUI_TEST_CREATE_BIND_GATE, scoped to
one workspace key) holds that create between its surface insert and its pane
binding until the move is done. Fails on this commit: the create reports
'tab slot N has no durable identity' and later closes fail the same way.

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

* cmux-tui: a created tab keeps its durable identity until it is placed (cx-95uaf)

State::rebuild_resource_indexes rebuilt tab identity only from the tabs the
workspace/screen walk reached. A created terminal is inserted (its identity
registered) before it is bound to a pane, and a move-workspace (or any
other index rebuild) in that window dropped the identity. The binding then
placed a slot with no durable identity, the create failed ('tab slot N has
no durable identity'), and every later full projection (close-workspace
too) failed the same way. On the app this was the second New Workspace:
the sidebar places the listed workspace while its first terminal is still
being created.

- Placing a live surface in a pane registers the identity it was created
  with when the topology has none (State::register_placed_tab_identity) at
  every terminal placement site: hosted bind, local create, effect tabs and
  panes.
- The rebuild carries the identity of a live surface that is in no pane.
- A placed slot whose record is missing takes its live surface's identity
  (State::tab_identity_of) in the full rebuild and in the scoped reindex,
  so one lost record cannot wedge later mutations. A slot with neither
  still fails loudly.

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

* Channels view in the app: the page may play video from the daemon's loopback listener (PageCSP media-src)

The Channels page plays a video attachment from the daemon's own
GET /attachments/<token> (ws-attachments-v1). The app's page scheme sent no
media-src, so the web view blocked it. PageCSP gains media sources; the
home-channels descriptor allows http://127.0.0.1:* (the daemon listener).

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

* group-editor-live: find the group by its member row, the tab chip by the strip, the rename by the reopened editor

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

* group-editor-live: real window-server right-click first, the in-app event path as fallback; strip rects are arrays

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

* cmux-next tab groups: the group editor's More row shows the group's full menu under the chip (review P2)

A right-click on a tab group chip now opens the editor, whose rows are New
Tab, Ungroup, Close, Move to New Window and Save. The rest of the group's
menu (icon, moves to a split, column or workspace, collapse, configured
rows) stays one row away: More (the strip's existing paneToolbar.more
string, 21 locales) pops the same menu under the chip, as the sidebar
editor's More Group Actions row does. Option-right-click still shows it
directly.

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

* cmux NEXT: Ghostty unfocused-split-opacity and unfocused-split-fill dim unfocused panes (cx-6so.35) (#20533)

* test(cmux-next): Ghostty unfocused-split keys dim unfocused panes (red, cx-6so.35)

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

* cmux-next: honor Ghostty unfocused-split-opacity and unfocused-split-fill (cx-6so.35)

The Ghostty config's unfocused-split-opacity dims every unfocused pane
by 1 - value while a screen has more than one pane, over the
unfocused-split-fill color (the theme background when unset), as
Ghostty.app draws over an unfocused split. cmux loads
unfocused-split-opacity = 1 before the user's files, so nothing dims
unless the config sets it. Both keys leave GhosttyKeySupport.unsupported,
so Settings' diagnostics stop listing them.

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

* chore(next): PR autofix at 918e7d3d8115

- regenerated schemas/defaults/cmux-next.json

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

---------

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

* cmux-tui: behavior test, a large legacy registry gets no full VACUUM while its owner serves (cx-4bd8m)

Red commit: drives the owner binary on a registry in the shape a build
without journal maintenance leaves (auto_vacuum off, no marker). Adds a
debug-only CMUX_TUI_TEST_JOURNAL_MAINTENANCE_INTERVAL_MS override.

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

* ci(nightly): signed app downloadable before the notarization wait (#20551)

* test(nightly): require a signed app artifact before the notarization wait

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

* ci(nightly): upload the signed app before the notarization wait

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

* chore(next): PR autofix at 3dd82a78f6aa

- regenerated schemas/defaults/cmux-next.json

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

* ci(nightly): signed-app upload never blocks notarization; only the import pause skips it

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>

* docs: gh-merge-green is optional on feat-cmux-next (#20567)

A plain gh pr merge on the exact head is fine under the branch's rules; the
batch queue's own --land still uses the helper.

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

* cmux-tui: no full VACUUM of a large legacy registry while its owner serves (cx-4bd8m)

Austin's cmux NEXT registry grew to 10.7 GB in 8.5 h under a build without
journal maintenance (auto_vacuum off, 4.47M journal rows). The maintenance
worker's first step converts such a registry with PRAGMA auto_vacuum=
INCREMENTAL; VACUUM; while it holds the registry: on that file every client
of the owner would read "not connected" for minutes after the next update.

Convert only while live data is at most 64 MiB. Above it the conversion is
deferred (one diagnostic per owner); sealing still runs and deletes archived
rows, and SQLite reuses their pages, so the file stops growing once sealing
keeps up. It does not shrink: sealed segments stay in the registry, so giving
the free pages back needs a compaction that does not hold the registry
(follow-up bead). Debug-only overrides for the interval and the bound keep
the behavior test small. Red/green: legacy_registry_maintenance.

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

* cmux-next: a local merge commit reuses the fetched same-tree acpmux (#20534)

* test: a local merge commit reuses the fetched same-tree acpmux (red)

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

* fix: a local merge commit reuses the fetched same-tree acpmux

reload.sh on a developer Mac stopped with "no cached acpmux" for any commit
no workflow published (a local integration of feat-cmux-next and PRs), even
though the tree fetch had just put that tree's checksum-verified acpmux in
place. build-acpmux.sh now seeds its commit cache from it when it has every
requested architecture.

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

* chore(next): PR autofix at 7be0d24457e0

- regenerated schemas/defaults/cmux-next.json

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

---------

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

* webviews: popups draw one hairline, not two (#20461)

* webviews: test that every popup draws one hairline edge

Red: --acpmux-popup-elevation stacks a composer-edge hairline on the one
already in --ui-popup-shadow.

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

* webviews: popups draw one hairline, not two

--acpmux-popup-elevation stacked a 0.5px composer-edge hairline on the
hairline already in --ui-popup-shadow. It now is the shared popup shadow,
so every menu and popover has one edge. Increase Contrast colors that one
edge in the popup's ink, which the composer-edge hairline did before.

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

* chore(next): PR autofix at 1cc661608ff2

- vp fmt webviews/src/agent-session/acpmux/modelPickerLayout.ts

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

* webviews: test that popups draw no border over the hairline and clear it under borders none

Red: the pages' .ui-popup and the chat find bar draw a border over the
--ui-popup-shadow hairline, and appearance.borders none leaves the
hairline drawn.

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

* webviews: one rim on page popups and the find bar; borders none clears it

The pages' .ui-popup drew a 1px border over the --ui-popup-shadow
hairline; it now draws no border. The chat find bar keeps its border
(its focus cue) and casts --ui-popup-drop, the shadow without the
hairline. appearance.borders none sets --ui-popup-edge transparent.

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

* webviews: format modelPickerLayout.ts (base fmt red)

Keeps cmux-autofix's vp fmt of the base file, which the base merge
replaced with the unformatted copy; react-apps-check fails on it.

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

* webviews: the Channels palette draws one rim; test follows the summary card

Red after the base merge: the new Channels palette (.lg-palette, ledger.css)
drew a 1px border over the --ui-popup-shadow hairline, and the summary
popover became .acpmux-summary-card. The palette now draws no border.

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

* chore(next): PR autofix at 54dd59d45f3d

- regenerated schemas/defaults/cmux-next.json

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

---------

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: name the owner of ControlWorkspaceInfoCache's publish hop (clears the concurrency red) (#20485)

* fix: clear two feat-cmux-next check reds

tree-publish-v2's stub uploader takes --conflict-exit-code, which the tree
publisher now passes (af8b68ebc8f9e). PaneAnimationsSetting carries the
namespace-type justification its ratcheted sibling settings have.

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

* fix: keep PaneAnimationsSetting internal to CmuxNextSettings

Nothing outside the module uses it, so it needs no namespace-type allow.

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

* chore(next): PR autofix at c2bdb982abed

- regenerated schemas/defaults/cmux-next.json

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

* fix: name the owner of ControlWorkspaceInfoCache's publish hop

check-concurrency flags the unowned Task d0a62bef9b28c added; it is a
one-shot main-actor hop that holds the cache weakly.

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

* chore(next): PR autofix at 7cf07e3731b0

- regenerated schemas/defaults/cmux-next.json

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

---------

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

* CmuxNext: one-line sidebar rows by default; notification text in the hover card (#20483)

* test: a notified compact row stays one line; its text reads in the hover card

Leo 2026-10-14: "why is it even 2 lines in the first place?"

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

* fix: compact sidebar rows are one line by default; the notification text moves to the hover card

sidebar.workspaceRow.notification is now off by default (Leo 2026-10-14). #19862
turned it on for classic parity; the setting stays as an opt-in.

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

* chore: regenerate the settings schema hash for the notification default

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

* chore: regenerate the settings schema hash on the merged base

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

* chore: take base's settings exports after the base merge (regenerated next)

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

* chore: regenerate the settings exports on the merged base

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

* chore: refresh the defaults snapshot on the merged base

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

* docs: the hover card template lists the notification fact; comments say where the text shows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.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: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
Co-authored-by: safe-push <safe-push@cmux.local>
teamleaderleo added a commit that referenced this pull request Oct 14, 2026
…nce, failed with why, lost) (#20362)

* test(acpmux): every session summary carries its lane (cx-v9k2f)

Working since its turn started, input, idle since its last turn ended,
failed with the error text (a restart's outcome_unknown included), lost;
peer sessions too. Red: summaries have no lane yet.

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

* feat(acpmux): every session summary carries its lane (cx-v9k2f)

lane = {state, since, error}, decided once next to indicator from the
summary's own fields, for local and peer sessions (re-decided when a peer
goes unreachable): working since its turn started, input, idle since its
last turn ended, failed with that turn's error text, lost. A restart's
outcome_unknown now records lastTurn, so that session reads failed with why,
not idle. The restart test moves to its own file (hub_integration.rs was at
its size cap).

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

* fix(acpmux): review follow-ups for the session lane (cx-v9k2f)

Waiting reads inputSince (when the session began waiting), so a draft save,
a later record or a second ask no longer moves it. A session lost mid-turn
is lost since it went, without the previous turn's error. outcome_unknown
records lastTurn before its record goes out, as every turn end does, and
lastTurn keeps detail. An idle session with turns but no lastTurn falls back
to updatedAt. The schema documents lane and inputSince; the restart test
module sits in order.

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

* chore(next): PR autofix at 5099ccd

- cargo clippy --fix cmux-tui/crates/acpmux/src/hub/turns.rs
- cargo clippy --fix cmux-tui/crates/acpmux/src/hub/views.rs

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

* acpmux: lastTurn keys live beside the lane (turns.rs does not grow) (cx-v9k2f)

Adding `detail` wrapped the key list and grew hub/turns.rs to 1064 lines,
which the god-file check refuses (1062 on base, over the 1000 limit). The
list becomes views::LAST_TURN_KEYS, so turns.rs stays at 1062.

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

* cmux-next: the typeahead GUI gate in the repo (cx-wb5.78): typeahead-gate.sh runs split, newterm, split with a slow host, new-tab and cold new-tab proofs on the GUI host and fails on a lost key; typeahead-compare.py counts keys sent by terminal id; creation-hold-proof.py is superseded (named in the gate header)

* acpmux: behavior test, a new chat starts in full access; the person's switch back is saved; every chat gets the cmux MCP server (red) (cx-wx93i)

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

* acpmux: new chats start in the harness's full-access mode; the person can switch back and the pick is saved; every agent chat gets the cmux MCP server (cx-wx93i)

Lawrence 2026-10-14: "give every agent chat the cmux MCP tools by default,
but by default all chats should be in skip permission mode, that user can
switch back".

- hub/start_mode.rs: once the harness is up, acpmux sets the start mode with
  session/set_mode (soft, like the start model): the person's saved pick
  (config.json defaults.<profile>.mode), else acpmux config.json
  defaultPermissionMode, else the cmux setting agents.defaultPermissionMode
  (fullAccess | standard | ask), else full access: Claude Code
  bypassPermissions, the Codex bridge agent-full-access (approval never +
  danger-full-access), or a harness's yolo/full-access mode. Not for Web
  (remote-origin) chats, presets, folder profiles, adopted chats, or a
  profile that pins its mode in argv.
- A mode pick from the person's app pane (session/set_mode or the mode
  config option) is saved; Plan is a toggle and is not saved; a pick equal
  to what the setting gives removes the saved mode. Only the person may
  clear a saved mode (_acpmux/defaults clear), as for a stored policy.
- claude_stdio: offers bypassPermissions ("Full access") and launches Claude
  Code with --allow-dangerously-skip-permissions when it accepts it (not
  root, or IS_SANDBOX=1); remote chains strip the flag.
- agent_tools: the cmux MCP server (cmux mcp serve) is on for every agent
  chat unless the settings file says mcp.enabled false; the settings file is
  now the app's cmux-next.json (user_settings.rs), not classic cmux.json.
- cmux mcp serve: on when CMUX_AGENT_SESSION is set and mcp.enabled is not
  set; any other client still needs mcp.enabled true.

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

* feat(cmux-next): Page Info on the shared native popup host; its rows on the shared popup row (cx-x9o52, REUSE.md slice 3)

PageInfoPanel is a NativePopupPanel: placement through PopupPlacement, the
host's grow/shrink motion and dismiss rules (click outside with the omnibar
button kept open, Escape, key loss, app inactive, window move/resize/close).
The hand placement, PopupFade and the mouse-down reopen guard go. The card no
longer draws its own shadow band (PopupHostView draws the one popup shadow).
PageInfoRowView is a PopupRowView. NativePopupPanel joins the child-window
allow list (the bookmark bubble tripped it in DEBUG). PageInfoMotionTests (a
unit test of PopupFade on PageInfoPanel) is deleted under the no-unit-test rule;
the live check covers the bubble.

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

* fix(cmux-next): a new bookmark's name follows the page title that arrives after the star (bookmark bubble)

The star saves the bookmark at once, often before the page has a title, so the
bubble showed an empty name. While the bubble shows, the page title (observed)
fills the name until the user edits it; close saves it as before.

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

* test: bookmark bubble live check covers the late page title, Page Info on the shared host (captures) and a real click on the star (--real-click) (cx-x9o52)

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

* review: Page Info rows keep their inset before the accessory (cx-x9o52)

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

* cmux-next: pin CEF cef-154.0.28-cmux.25 (cmux.24 + WebAuthn hybrid fix with a PIN key attached; fork API 23) (cx-d0d.97)

* cmux-next: remote browser host linked against CEF cmux.25 (step beeef9df6b98) (cx-d0d.97)

* passkeys-roaming-e2e: --typing smoke with trusted key events (cx-d0d.97)

* passkeys-matrix-e2e: a timed-out addVirtualAuthenticator reply counts as sent (the socket deadline is now 6 s) (cx-d0d.97)

* chrome-promos-live: ignore Chromium's status bubble (<= 30 pt at the page window's bottom-left), logged; any other new window still fails (cx-d0d.97)

* passkeys matrix + promo check review fixes: unconfirmed authenticator rows that pass without one are NOTE; promo count uses only new windows (cx-d0d.97)

* free tier: a Keychain error never mints a new App Attest key; no key-id read where App Attest cannot work (cx-5bxp7 P3-A, P3-B)

AttestKeyStore load/save/delete throw on a store error (load: nil only for
errSecItemNotFound). The provider answers failed when the key id cannot be read
or saved, so it never generates or attests a new key then (a new key spends the
device's monthly grant), and it saves a new key id before attesting it. The
register line skips the Keychain read when App Attest is not supported (no
prompt in an ad-hoc DEV build).

* acpmux start mode: review fixes (cx-wx93i)

- Settings file read as JSONC like the app (comments, trailing commas); a
  file acpmux cannot read or parse, or an unknown value, gives the harness's
  own mode, never full access. Classic cmux.json only when cmux-next.json
  does not exist. The setting is resolved before the config write lock.
- A remote chain never starts Claude in bypassPermissions (a transfer keeps
  the mode): the spawn mode and args fall back to default.
- A handoff target starts in full access only from a full-access source and
  never with narrower.
- A profile with its own skip flag keeps its mode on respawn; config reload
  picks up defaultPermissionMode; acpmux reads mcp.enabled only nested, as
  the CLI does.
- Tests: the fake Claude refuses bypassPermissions without the launch flag;
  the settings-file test is its own binary (it sets CMUX_NEXT_CONFIG_FILE)
  and covers JSONC, no key, an unparseable file and an unknown value.

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

* cmux-next: Ctrl and Option holds show their shortcut hints too; hint chips are Liquid Glass in the theme's tint (cx-w9f3s)

Lawrence 2026-10-14: "we need cmd hold tab to work for hold ctrl as well,
maybe option if there are relevant shortcuts, we also need it to be liquid
glassy".

- A hold arms on any set of Cmd, Ctrl and Option (with or without Shift),
  not only bare Cmd or Ctrl. The overlay shows each target's binding from
  the resolved table (keybindings.json and cmux.json overrides included)
  whose modifiers contain the held set; a hold with no matching binding
  shows no chip. Same 300 ms delay, key-down suppression and placement.
- ShortcutHintChipView: a full Liquid Glass capsule (GlassPanelView ->
  NSGlassEffectView) tinted with the theme's glass tint, text in the
  theme's primary text color. macOS 14/15: the same panel's blur, tint and
  hairline. Reduce Transparency: the theme's opaque popup surface, a
  device-pixel hairline and the soft shadow.
- debug.shortcut_hints takes opt, shift and +-joined sets (ctrl+opt).

CmuxNextApp line count unchanged (113450).

* cmux-next: hold hints review fixes: only Cmd/Ctrl chords get a chip, a click suppresses, stronger chip tint (cx-w9f3s)

Review of d77c9ec (P2s): a chip only for bindings KeyRouter runs
(KeyRouter.isChord: a pure-Option binding types a character); a mouse-down
while a modifier is held hides and suppresses the hints like a key-down
(Option-click / Option-drag in a terminal); the chip's glass tint is the
theme tint at 1.5x alpha so 10 pt text keeps its contrast over bright
content. P3s: no double Borders.color, no continuous curve against a
circular shadow path, debug release only alone. plans/cmux-next/keybindings.md
describes the Cmd/Ctrl/Option holds and the glass chip.

CmuxNextApp line count unchanged (113450).

* review: Page Info keeps its side on a new page; any NativePopupPanel passes the child-window policy; the bookmark name follows only the bookmarked URL's title; live check presses the star once (cx-x9o52)

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

* free tier e2e runner: never kill; the app quits through its control socket (action.run quitEndSessions, peer-checked), the daemon by acpmux daemon shutdown (cx-ds09i T1/T2)

* cmux-next groups: a right-click on a group header or tab group chip opens the group editor

Lawrence 2026-10-14: "im talking about the rename + color things ... looks
like the image in chrome, we had it at some point". The editor bubble (name
field, color dots, group rows; cx-rcby, 3ca7be3) is still there, but
#19019 (31db8ce) pointed the header's more button and its right-click
at the plain menu. #20442 put the more button back on the editor; this puts
the right-click back too, and does the same for a tab group chip, whose
right-click showed the app's menu (the editor opened only on a press and
hold). The full menu stays one row away (More Group Actions… in the sidebar
editor) and on Option-right-click of a tab group chip.

scripts/cmux-next/group-editor-live.py proves it with real right-clicks
(cua-driver): the editor opens with its name field, color dots and rows,
a typed name with Return renames the group, Escape closes it.

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

* Channels view in the app: the page may play video from the daemon's loopback listener (PageCSP media-src)

The Channels page plays a video attachment from the daemon's own
GET /attachments/<token> (ws-attachments-v1). The app's page scheme sent no
media-src, so the web view blocked it. PageCSP gains media sources; the
home-channels descriptor allows http://127.0.0.1:* (the daemon listener).

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

* group-editor-live: find the group by its member row, the tab chip by the strip, the rename by the reopened editor

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

* group-editor-live: real window-server right-click first, the in-app event path as fallback; strip rects are arrays

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

* cmux-next tab groups: the group editor's More row shows the group's full menu under the chip (review P2)

A right-click on a tab group chip now opens the editor, whose rows are New
Tab, Ungroup, Close, Move to New Window and Save. The rest of the group's
menu (icon, moves to a split, column or workspace, collapse, configured
rows) stays one row away: More (the strip's existing paneToolbar.more
string, 21 locales) pops the same menu under the chip, as the sidebar
editor's More Group Actions row does. Option-right-click still shows it
directly.

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

* cmux-tui: behavior test, a large legacy registry gets no full VACUUM while its owner serves (cx-4bd8m)

Red commit: drives the owner binary on a registry in the shape a build
without journal maintenance leaves (auto_vacuum off, no marker). Adds a
debug-only CMUX_TUI_TEST_JOURNAL_MAINTENANCE_INTERVAL_MS override.

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

* docs: gh-merge-green is optional on feat-cmux-next (#20567)

A plain gh pr merge on the exact head is fine under the branch's rules; the
batch queue's own --land still uses the helper.

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

* cmux-tui: no full VACUUM of a large legacy registry while its owner serves (cx-4bd8m)

Austin's cmux NEXT registry grew to 10.7 GB in 8.5 h under a build without
journal maintenance (auto_vacuum off, 4.47M journal rows). The maintenance
worker's first step converts such a registry with PRAGMA auto_vacuum=
INCREMENTAL; VACUUM; while it holds the registry: on that file every client
of the owner would read "not connected" for minutes after the next update.

Convert only while live data is at most 64 MiB. Above it the conversion is
deferred (one diagnostic per owner); sealing still runs and deletes archived
rows, and SQLite reuses their pages, so the file stops growing once sealing
keeps up. It does not shrink: sealed segments stay in the registry, so giving
the free pages back needs a compaction that does not hold the registry
(follow-up bead). Debug-only overrides for the interval and the bound keep
the behavior test small. Red/green: legacy_registry_maintenance.

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

* cmux-next: a local merge commit reuses the fetched same-tree acpmux (#20534)

* test: a local merge commit reuses the fetched same-tree acpmux (red)

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

* fix: a local merge commit reuses the fetched same-tree acpmux

reload.sh on a developer Mac stopped with "no cached acpmux" for any commit
no workflow published (a local integration of feat-cmux-next and PRs), even
though the tree fetch had just put that tree's checksum-verified acpmux in
place. build-acpmux.sh now seeds its commit cache from it when it has every
requested architecture.

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

* chore(next): PR autofix at 7be0d24

- regenerated schemas/defaults/cmux-next.json

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

---------

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

* webviews: popups draw one hairline, not two (#20461)

* webviews: test that every popup draws one hairline edge

Red: --acpmux-popup-elevation stacks a composer-edge hairline on the one
already in --ui-popup-shadow.

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

* webviews: popups draw one hairline, not two

--acpmux-popup-elevation stacked a 0.5px composer-edge hairline on the
hairline already in --ui-popup-shadow. It now is the shared popup shadow,
so every menu and popover has one edge. Increase Contrast colors that one
edge in the popup's ink, which the composer-edge hairline did before.

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

* chore(next): PR autofix at 1cc6616

- vp fmt webviews/src/agent-session/acpmux/modelPickerLayout.ts

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

* webviews: test that popups draw no border over the hairline and clear it under borders none

Red: the pages' .ui-popup and the chat find bar draw a border over the
--ui-popup-shadow hairline, and appearance.borders none leaves the
hairline drawn.

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

* webviews: one rim on page popups and the find bar; borders none clears it

The pages' .ui-popup drew a 1px border over the --ui-popup-shadow
hairline; it now draws no border. The chat find bar keeps its border
(its focus cue) and casts --ui-popup-drop, the shadow without the
hairline. appearance.borders none sets --ui-popup-edge transparent.

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

* webviews: format modelPickerLayout.ts (base fmt red)

Keeps cmux-autofix's vp fmt of the base file, which the base merge
replaced with the unformatted copy; react-apps-check fails on it.

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

* webviews: the Channels palette draws one rim; test follows the summary card

Red after the base merge: the new Channels palette (.lg-palette, ledger.css)
drew a 1px border over the --ui-popup-shadow hairline, and the summary
popover became .acpmux-summary-card. The palette now draws no border.

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

* chore(next): PR autofix at 54dd59d

- regenerated schemas/defaults/cmux-next.json

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

---------

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: name the owner of ControlWorkspaceInfoCache's publish hop (clears the concurrency red) (#20485)

* fix: clear two feat-cmux-next check reds

tree-publish-v2's stub uploader takes --conflict-exit-code, which the tree
publisher now passes (af8b68e). PaneAnimationsSetting carries the
namespace-type justification its ratcheted sibling settings have.

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

* fix: keep PaneAnimationsSetting internal to CmuxNextSettings

Nothing outside the module uses it, so it needs no namespace-type allow.

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

* chore(next): PR autofix at c2bdb98

- regenerated schemas/defaults/cmux-next.json

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

* fix: name the owner of ControlWorkspaceInfoCache's publish hop

check-concurrency flags the unowned Task d0a62be added; it is a
one-shot main-actor hop that holds the cache weakly.

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

* chore(next): PR autofix at 7cf07e3

- regenerated schemas/defaults/cmux-next.json

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

---------

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

* CmuxNext: one-line sidebar rows by default; notification text in the hover card (#20483)

* test: a notified compact row stays one line; its text reads in the hover card

Leo 2026-10-14: "why is it even 2 lines in the first place?"

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

* fix: compact sidebar rows are one line by default; the notification text moves to the hover card

sidebar.workspaceRow.notification is now off by default (Leo 2026-10-14). #19862
turned it on for classic parity; the setting stays as an opt-in.

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

* chore: regenerate the settings schema hash for the notification default

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

* chore: regenerate the settings schema hash on the merged base

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

* chore: take base's settings exports after the base merge (regenerated next)

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

* chore: regenerate the settings exports on the merged base

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

* chore: refresh the defaults snapshot on the merged base

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

* docs: the hover card template lists the notification fact; comments say where the text shows

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>
Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
Co-authored-by: safe-push <safe-push@cmux.local>
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