Repository navigation
CmuxNext: Codex-like status icons by default, plus static status glyphs - #20410
Conversation
…l 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>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
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>
|
all web/size checks green |
|
Not verified: macOS jobs did not run: compile and app tests were skipped on CI passes on Written by |
|
|
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
This comment has been minimized.
This comment has been minimized.
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>
…icons/static-status
…icons/static-status
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>
…icons/static-status # Conflicts: # Packages/macOS/CmuxNext/Sources/CmuxNextDesign/StatusIndicator/StatusIconSet.swift
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>
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>
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
|
@lawrencecchen Codex-like status set (team ask): neutral grey spinner on your cx-qtgj2 spinner (still ring under Reduce Motion/Static), blue #3982F5 done dot, muted line glyphs, red line i for error. Proposed as default, not flipped; your call. In-app comparison of the alternates is in #20437. |
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>
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>
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>
Red: the default is still symbols. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
- vp fmt webviews/src/agent-session/acpmux/modelPickerLayout.ts Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38521104749
Gallery227 states changed: agent-pane.transcript/long-code, agent-pane.artifact-panel/versions, agent-pane.artifact-panel/closed, agent-pane.artifact-panel/html, agent-pane.edited-files/fourteen-files, ... · 34 new states · 50 broken states · 104 play checks failed Diff page and renders (the run's 221 more on the diff page. Live preview (follows this branch; start it with
Play checks failed (0 px anchor movement, 0 layout shift, no frame over 33 ms unless the entry gives a reason):
648 nondeterministic states differed from a second render of the same head (a clock or fixture leak) and are not counted as changes. |
…e line or two Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… 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>
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
|
Found 1 test failure on Blacksmith runners: Failure
|
teamleaderleo
left a comment
There was a problem hiding this comment.
usesHinting(pixels:) treats every integer width congruent to 10 modulo 20 as the 1x pixel-art grid (30, 50, … as well as 10). On a 3x display a 10pt glyph is 30 device pixels, so this selects the 10-unit path and scales each 1x block to 3px, producing visibly coarse/jagged glyphs instead of the 20-unit vector/master path. The predicate needs to distinguish actual 1x output from higher scale factors (or use the vector path above 1x).
…rt blown up Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… 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>
|
cmux-next tour of The tour job failed before the tour ran (build or setup); the run log has the output. The tour produced no media. Tours are data in |
…icons/static-status # Conflicts: # webviews/src/agent-session/acpmux/modelPickerLayout.ts
|
Review finding (usesHinting at 3x/5x) fixed before merge: only exactly 10 device pixels takes the 1x pixel art; 20, 30 and 50 px draw the master. Red/green receipts are in the body (Crisp at 3x and 5x). |
|
The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/38546442207 at 78687ac failed: cmux-next Release compile (Xcode 26). |
…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>
…dle reaper spares background shells (#20585) * sidebar rows: row-level status updates and lag fixes on the current model A status tick (agent status, OSC 7501, title, unread, progress, favicon) no longer re-runs the window's structural sidebar pass. SidebarRowFeed observes each listed row's mapping on its own; the structural pass reads only order, groups, pins, membership and machines. A changed row is written in place (SidebarModel.patchWorkspaces) and its one realized view is configured (SidebarRowIndex, SidebarListView.patchRows). Also: SidebarView compares a sections revision, not the rows; bands update only when their inputs change; SidebarRegionView.update skips an unchanged layout; scroll realizes only rows in range and rebinds parked views; one shared tunables observation instead of one per configure; O(1) DaemonStore.workspace(id:); O(n) PersonalSidebar.mixedSections and personalPlacements; debug.sidebar_perf counters; membership filters moved out of the App target. * sidebar rows: no unowned capture in the feed's row source (transient closure) * sidebar rows: non-Sendable row mapping type; sidebar-rows-perf-live.py * sidebar rows: the asset-icon test helper passes an escaping asset closure (SidebarMapping keeps it for the row feed) * sidebar rows: a row patch skips rows this window does not list (another window's workspace, a top tile) instead of rebuilding * sidebar rows: review fixes - a patched group member configures its collapsed header (aggregate activity, unread) - the feed observes every row again only when the pass's by-value inputs change (muted set, New Tab pages, unread on sidebar); a row reads its tab selection itself, so a tab switch re-maps rows instead of rebuilding the sidebar - bands lay out again on a height change too (app sections take a height share) - every listed copy of a workspace is patched (launch: saved row beside live row) - an unchanged sections set is no structural change - a height-change reload keeps the provisional-rows rule; teardown resets the feed * sidebar-rows-perf-live.py: window_frame is an object * sidebar-rows-perf-live.py: scroll after the ticks end, wheel and trackpad, prove the list moved * daemon state: a terminal-only session delta lays the mirror over those terminals' tabs only An agent status tick (OSC 7501, OSC 9;4, busy) arrives as a session.events delta of terminal records. Each one re-laid the whole mirror over every workspace, screen and tab (O(all records) per tick, with a screen-group rebuild per workspace). Now an unchanged mirror lays nothing, and a terminal-only delta updates just those terminals' tabs. * daemon state: a tab whose record ids change gets the mirror's records for its new ids (review: the terminal-only delta no longer re-lays every tab) * control snapshot: model changes publish at most every 100 ms Each publish maps every workspace of every machine. An agent status tick changes the model hundreds of times a second, so the publisher rebuilt the whole topology on nearly every frame (hq-2d profile: 18% of main thread per tick at 100 workspaces). A read that waits on a write barrier still publishes at once, and so does a work-queue frame (CLI read-your-writes) and a key-window change. * scripts: hq-2d's sidebar-stress-bench.py (unchanged copy of 9ca18a53ba6a1) for the rows lane's before/after runs * sidebar-rows-perf-live.py: scroll over a point whose hit view is the list * test(acpmux): lifted harness limits and a reaper that spares background shells (red) Three behavior tests at the hub boundary, failing on the base: - an idle chat whose shell, or whose subagent's shell, still runs in the background keeps its harness, also after a later turn (the 5-minute reaper killed the shell with it); - a chat's Claude Code starts with its subagent limits and Bash timeout ceiling lifted, and a value the profile sets wins; - a chat's codex app-server starts with -c overrides for its agent thread limits, and a key the profile's argv or the project's .codex/config.toml sets keeps its value. The fakes learn a running background shell (also a subagent's), a limits probe, a memory hog, and (codex) global options before app-server, as codex takes them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(acpmux): lift harness limits for the chats acpmux starts; budget, not counts Leo 2026-10-14, no caps on subagents. Every session spawn (direct, hosted, pooled) goes through harness_command, so a new chat has these without a restart. A value the user set wins (profile env or login env for Claude; for Codex the same -c key in the profile argv, or the key in codex's config.toml (the profile's CODEX_HOME first), its active profile or the project's .codex/config.toml). - Claude Code: CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS 1000 (was 20), _MAX_SUBAGENTS_PER_SESSION 100000, _MAX_SUBAGENT_SPAWN_DEPTH 16 (was 3), _MAX_TOOL_USE_CONCURRENCY 64 (was 10), BASH_MAX_TIMEOUT_MS at Claude's own ceiling (~24.8 days; was 2 h), which is also how long a background command may run in an unattended session. BASH_DEFAULT_TIMEOUT_MS stays: raising it would hold every foreground command that long. - Codex (app-server bridge): -c agents.max_concurrent_threads_per_session=1000 (was 6) and -c agents.max_depth=16 (was 1). Codex's 64 unified-exec processes per session is a compile-time constant and stays. - Idle reaper: Claude's translator now sends a running background_task_update when a shell goes to the background; the hub keeps those per chat across turns (and rebuilds them from the log since the harness last exited or started, when it adopts a running host), and never reaps a chat with one running, a subagent's shell included. Web ignores the running state. - Safety valve (hub/budget.rs, unix): every 10 s one ps read sums each harness's process tree. Over the memory budget (default half of RAM, ACPMUX_SESSION_MEMORY_MB) the largest process the agent started is killed and the chat logs budget_exceeded. The agent never is: neither the harness nor what started with it (10 s; a launcher's agent, MCP servers); nothing is killed when those alone are over, nor a process holding less than the overage and a tenth of the budget. The harness pid must still be alive and the child of its host (or this daemon), so a reused pid is never acted on. Over the CPU budget (default 3/4 of cores, ACPMUX_SESSION_CPU_CORES) for 30 s the tree is reniced to 10 (every thread on Linux). 0 turns a check off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(acpmux): keep hub/mod.rs within its line limit after the base merge Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * control snapshot: the visible-workspace filter allocates nothing per workspace RoomMembership.contains no longer builds the room set, MachineRegistry finds a local workspace without building the daemon list, and WindowProfiles.visible skips the per-workspace check when no room membership is loaded. The filter was about 2/3 of each control snapshot publish at 1000 workspaces (hq-2d bench, rows head 2deca51c). * sidebar rows: one observation per row; an inputs change or a forgotten row removes the old one (no registration growth) * sidebar rows: review fixes (row watch cancel type is file-private; band labels re-measure on a font size change; a filtered window ignores rows it does not list) * sidebar-rows-perf-live.py: probe rows from the top of the list (a middle realized row can sit under the footer band) * chore(next): PR autofix at c42773cc4ce1 - cargo fmt in Native/OptChat/optchat-chief Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38570043969 * debug.sidebar_wheel: x, y from the content view's top left, as debug.sidebar_rows reports rows (a title bar shifted every point up) * debug.sidebar_rows: one id table per report (per-row model.workspace scans tripped the main-thread watchdog at 1,000 rows) * Channels view: attachments meet the page rules (cx-hnmbt.16 follow-up) - Lightbox arrow keys through ui/KeyboardScope (no raw key handler); the composer's hidden file input has no tab index (ui-rules base red). - Gallery: #media in the mock provider (images with previews, a video with its poster, a PDF; attachment ops served in memory) and three variants that play the grid, the lightbox (arrow keys) and the composer tray (paste); the entry covers AttachmentViews.tsx (gallery-coverage base red). - vp fmt of the page files (fmt base red). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fmt HomeChannelsPage after the merge Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test: placement-shift-v1 live checks (red): only that token stops the restatement; a sidebar-doc-v1-only reader keeps it; the TUI echoes both (cx-qno.26) * cmux-tui: placement-shift-v1 is the opt-out of the placement restatement; sidebar-doc-v1 alone keeps it; the TUI echoes both (cx-qno.26) hq-2d decision: one token per meaning. A connection that echoes placement-shift-v1 (advertised in identify, negotiable in set-client-info and client.metadata.update) gets no workspace_placement restatement on session.events; sidebar-doc-v1 no longer changes placement delivery. Journal subjects stay 'a workspace is a subject when its own value changes': no in-repo subscriber filters journal records by a workspace subject to follow position shifts. * cmux-tui: rustfmt (cx-qno.26) * review: stale sidebar-doc-v1 wording in a test comment, the TUI live script and the ledger (cx-qno.26) * fmt DaemonGate.tsx and daemonHome.ts (base red after the owner-socket merge) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * acpmux: Claude's "Allow for this session" keeps a session rule (#20591) * test(acpmux): Claude's Allow for this session must stop the second ask (red) The fake Claude Code keeps rules from an allow's updatedPermissions with destination session, as Claude Code does, and asks again for a call no rule covers. acpmux answers allow_always with a plain allow today, so the turn asks twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(acpmux): Claude's Allow for this session keeps a session rule Claude Code keeps a permission only when the allow carries it back as updatedPermissions. acpmux answered allow_always with a plain allow, so Claude asked again and the option worked like Allow once. The ask's permission_suggestions become session-only rules: allow addRules entries with destination session, never the settings file a suggestion names (the option promises this session, no more). The option is offered only when there is such a rule, as #20407 proposed; without one it would be Allow once under another name. Folds #20407. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(next): PR autofix at cf1b9463d13f - cargo clippy --fix cmux-tui/crates/acpmux/src/claude_stdio/mod.rs Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38556207429 --------- 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: a chat from a prewarmed New Tab page can open tabs again (cx-ckt2z) (#20616) * test: a chat from the prewarmed New Tab page opens tabs (cx-ckt2z) A tab that adopts the spare New Tab page refuses tab.open and tab.typeAhead once the page becomes a chat; a standalone chat must still refuse both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: a chat from the prewarmed New Tab page opens tabs (cx-ckt2z) The spare New Tab page is made with allowsTabConversion false, so once a local tab adopted it and its page became a chat, AgentPaneModel refused tab.open and tab.typeAhead. Both adopt paths (open and view(for:)) now go through AgentTabStore.adoptSpare, which marks the spare's model as a local tab's. Standalone and remote panes keep it false. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux NEXT: only this package may turn on a pane's tab opening (cx-ckt2z review) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * cmux NEXT CLI: progress while waiting for the app, and a timeout that says what to run next (#20627) * test: a silent app wait shows progress and a typed timeout Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cli): show progress while the app runs a waiting request, and name who did not answer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: read the JSON error beside the run's key report Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: the next command keeps the caller's route and short reads stay quiet Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cli): route the next command like the call, notes only for long waits Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix: Recently Closed is high signal: no New Tab pages, no empty windows, one row per thing (#20523) * test: Recently Closed lists only things worth reopening Leo 2026-10-14: Recently Closed lists Chromium New Tab pages and one row per close of the same page; removing a page's row lets an older close of it take its place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: the New Tab page's Recently Closed shows a page closed again once Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: Recently Closed lists only things worth reopening Blank and New Tab pages (about:blank, chrome://newtab) never show, and a page closed again shows once at its newest close, on the palette and History page (daemon history.entries.list) and the New Tab page (Swift HistoryService). Removing that row removes every close of the page. Terminals, chats, screens, workspaces and windows keep one row per close. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(next): PR autofix at 669d1f1e8073 - cargo fmt in Native/OptChat/optchat-chief Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38564174343 * fix: sort Recently Closed newest first by key (clippy) 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: fleet builds ship Ghostty themes, so debug.appearance light turns the app light (cx-31li) (#20597) * test(cmux-next): fleet-built apps ship no Ghostty themes (cx-31li red) The Resources phase copies Resources/terminfo-overlay/67/ghostty flat as Contents/Resources/ghostty. The fleet's Linux builder runs that copy after "Bundle Ghostty resources", so the file replaces the ghostty/ directory and the app ships without themes: every theme resolves to Ghostty's default (#282C34) and debug.appearance light leaves the app dark. The test lays down both phases in either order and expects ghostty/themes in the bundle. Red here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cmux-next): fleet builds ship Ghostty themes, so debug.appearance light turns the app light (cx-31li) Resources/terminfo-overlay/67/ghostty was a member of the synchronized Resources group, so the Resources phase copied it flat to Contents/Resources/ghostty. Xcode runs that phase before "Bundle Ghostty resources", which replaced the file with the ghostty/ directory; the fleet's Linux builder runs it after, so the file replaced the directory and the app had no themes. Every theme (the default Apple System Colors pair, a cmux.json theme) then resolved to Ghostty's default #282C34, and neither debug.appearance nor a config theme changed anything. The overlay file is now a membership exception, like its xterm-ghostty sibling; the script phase alone bundles it. debug.appearance also returns the applied terminal background and the bundled themes folder (null when the bundle has none), so capture tooling can tell a missing-themes build from a theme that did not switch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * review: debug.appearance reports only the app's own themes; the layout test fails on a crashed copy and on any phase directory turned flat Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * acpmux: a new chat starts on the harness's own config when nothing was 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 d…
#20410 landed with its own set mechanism (glyphs carry a `set`, `sets` maps each state to a glyph and tint, as the Codex-like set does). The Dots and Pictograms sets now ride on it instead of the PR's Style enum: their glyphs are dots*/pictograms* cases with `sets.dots` and `sets.pictograms` entries, StatusIconSet.stillDots/stillPictograms pick them through staticGlyph(for:), and the Debug status icon switch (status.iconSet) keeps its codex default. The gallery gets dots and pictograms variants and the web test holds them to the Static set's rules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…21659) * test(cmux-next): the static look stills every indicator on the Static set (cx-sya4n) Red: there is no static look (StatusIndicatorConfig.isStatic, StatusIndicatorPlan.make(isStatic:)); under Reduce Motion an indicator keeps its icon set's mark (compile failure). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux-next: a static status look, on under Reduce Motion (cx-sya4n) StatusIndicatorSettings.animation (animated or static) and Reduce Motion make StatusIndicatorConfig.isStatic; StatusIndicatorPlan.make(isStatic:) then draws the Static set's glyphs (StaticStatusGlyph, #20410) with no animation, whatever the icon set. The Debug toggle status.static.shapes draws #20511's StatusMark shapes instead. StatusIndicatorView takes the look; tabs and the Settings row follow in later slices. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…21662) * test(cmux-next): the static look stills every indicator on the Static set (cx-sya4n) Red: there is no static look (StatusIndicatorConfig.isStatic, StatusIndicatorPlan.make(isStatic:)); under Reduce Motion an indicator keeps its icon set's mark (compile failure). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux-next: a static status look, on under Reduce Motion (cx-sya4n) StatusIndicatorSettings.animation (animated or static) and Reduce Motion make StatusIndicatorConfig.isStatic; StatusIndicatorPlan.make(isStatic:) then draws the Static set's glyphs (StaticStatusGlyph, #20410) with no animation, whatever the icon set. The Debug toggle status.static.shapes draws #20511's StatusMark shapes instead. StatusIndicatorView takes the look; tabs and the Settings row follow in later slices. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(cmux-next): appearance.statusIndicator.animation parses and has a Settings row (cx-sya4n) Red: cmux.json's animation key is ignored and Settings has no Status Indicators row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cmux-next: Settings > Appearance > Loading Indicator > Status Indicators (Animated / Static) (cx-sya4n) appearance.statusIndicator.animation reads into StatusIndicatorSettings.animation, with a choice row (agent-settable like its neighbours) and its four strings in every catalog locale. The settings exports regenerate on a Mac (PR autofix). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * l10n(cmux-next): the Status Indicators help in all 21 catalog locales (cx-sya4n) check-l10n wants every catalog locale; the help had the nine required ones only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(cmux-next): settings exports for appearance.statusIndicator.animation settings-schema.json (the row, its page slot and the schema hash), the mdm JSON, plist and table, the source stamp, the defaults snapshot and the Settings page strings. Written on Linux with the exporter's rules ported to Python (DeterministicJSON, defaults_guard's stamp); SettingsSchemaExportTests and ManagedPreferencesManifestTests check them against the Swift exporter in CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…h (hq#1957) (#20437) * 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> * CmuxNext: Dots and Pictograms static status glyph sets (hq#1957 status glyph thunderdome) The static glyph schema now holds several sets with the same six states and tints; #20410's set (still) stays first and default. gen-status-glyphs.py writes StaticStatusGlyph.Style and path(in:style:) for Swift and STATUS_GLYPH_SETS for web. StatusIconSet gains stillDots and stillPictograms (Debug menu and Debug Settings), localized. <StatusGlyph set=...> picks a set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * CmuxNext: gallery and tests cover every static glyph set (hq#1957 status glyph thunderdome) The ui.status-glyphs sheet shows the three sets side by side for each state (10 pt, 1x pixel art, 2x drawing) and in sidebar rows. The browser test checks each set for order, stillness, theme tint, crisp 1x/2x edges and pairwise shape distance; StaticStatusGlyphStyleTests checks the same in CoreGraphics and that the status icon switch selects each style. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * CmuxNext: format the status glyph gallery's set rows Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(next): PR autofix at 0e18c18 - regenerated schemas/defaults/cmux-next.json Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38700092982 --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
…d spokes (#31409) * test: the agent working spinner is the settled turning arc, never stepping spokes Fails on base, where #29903 made the working mark the macOS activity indicator's segmented spokes stepping round. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639 * cmux NEXT: restore the settled turning-arc working spinner (undo #29903's spokes) The agent working-state mark (sidebar, tabs and the transcript's Spinner) is the thin arc turning smoothly again, as from 6388291 and #20410 until #29903. #29903's shared-clock phase fixes stay; the spokes stay only as the explicit 'macOS Spinner' loading style. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639 --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>






Leo, for op-next-motion's Static status mode (Reduce Motion, Austin's headaches): "design new symbols if needed; we do not have to reuse SF Symbols." This adds six custom static status glyphs. Each is told apart by shape alone at 10 pt, crisp at its exact pixel size, tinted by its theme role, and never animated.
They follow the custom marks already in
StatusMarkArt: rings, discs and badges with the figure cut out.One source, Swift and web
schemas/status-glyphs/static-status.jsonholds each glyph twice:master: vectors on a 20-unit grid (10 pt at 2x, one unit per pixel). Straight edges and stroke widths sit on whole pixels; only curves antialias.x1art: the 10 px (1x) drawing as pixel art, so every 1x edge falls on a pixel.scripts/cmux-next/gen-status-glyphs.pywrites:StaticStatusGlyph.generated.swift: CGPaths, filled even-odd;src/ui/statusGlyphs.generated.ts: SVG path data.--checkruns inbun run check(check:status-glyphs).StaticStatusGlyph.usesHintingandusesPixelArt.Tokens for op-next-motion
StaticStatusGlyph(state)maps aStatusIndicatorStateto its glyph. It returns nil for busy and paused, which keep their loading style..planis the indicator plan:animation: niland the glyph's tint.StatusMark.vector(_:)draws a glyph anywhere a mark is drawn.StatusIconSet.still("Static") maps every state to these glyphs. It sits in the Debug menu and Debug Settings with the other sets. The Static setting can select it or callStaticStatusGlyph(state)?.plan.<StatusGlyph name size label>insrc/ui/StatusGlyph.tsx.Contact sheet
Gallery
ui.status-glyphs, variantssheetandwallpaper. It shows:Switch the gallery's appearance to see it in light and dark.
Test
webviews/test/status-glyphs.test.ts(browser lane) loads the sheet in Chromium in light, dark and on the wallpaper, at 1x and 2x. It rasterizes each live glyph at the window's pixel size with the browser's SVG renderer and checks:Results: the red commit runs first in CI (react-apps-check); the green run follows on the fix commit.
Codex-like set, now the default
Team feedback via Leo: the colored status glyphs read as iffy, and the team likes Codex's sidebar.
StatusIconSet.codex("Codex-like") follows it. It is now the default for everyone (Leo 2026-10-14, a team decision; it wassymbols). Symbols, Static, Dots and the other sets stay as Debug alternates (Debug menu > Status Icons, or Debug Settings > Status Indicators > Status icons).No motion at all: turn on Reduce Motion (System Settings > Accessibility > Display > Reduce motion). Then the grey spinner becomes a still ring, and nothing else in the set moves.
In the app
A real cmux NEXT DEV build of this branch on a capture mini. The sidebar rows are driven by OSC 7501 from each workspace's terminal: error (deploy), permission (api), done (infra), question (docs-site) and working (cmux-next). Top row: before (
symbols, the old default); bottom row: after (Codex-like). Five frames, 133 ms apart. I watched both 4 s recordings: only the working spinner moves, in both.Full quality: status-sidebar-before-symbols.mp4
Full quality: status-sidebar-after-codex.mp4
#3982F5(57,130,245)#FF6764)captures/codex/app-20261003) and the team's reference still.spinnerandunreadare newStatusIndicatorPlan.Tints. The schema gains acodexset, which the generator emits for the web copy (STATUS_GLYPH_SETS).codexandcodex-wallpaper.Not in this PR: the pin and archive hover row actions, regular-weight titles and softer greys from the same feedback. They are sidebar-row changes, which wait for Lawrence's sidebar rework.
Tests:
CodexStatusIconSetTests(Swift) checks each state's plan and the still ring when nothing animates, plus the sampled colors.status-glyphs.test.tschecks the codex sheet in light, dark and on the wallpaper at 1x and 2x: no animation, tint colors, line glyphs, the blue dot, and the glyphs differ pairwise.run-swift-testing-suites.sh, CmuxNext):codexIsTheDefaultSetForEveryonefails (the default wassymbols); green e7792eb: passes (all ofCodexStatusIconSetTests). The same unrelatedNewTabStyleSwitchTestsrace failed there too; Base reds: Cloud helper string in 21 languages and the control cache Task owner (no bead) #20436 fixes it on the base.StatusIconSethas nocodex,Tinthas nospinner); green 7bad855:CodexStatusIconSetTestspasses.aStillLoaderStaysAStoppedArcNotTheWorkRingfails (a busy loader became the work ring with motion off); green 3e27a51: passes.NewTabStyleSwitchTests(a New Tab template save read backterminal). It passed on 7bad855, and this PR does not touch New Tab or settings.Centered glyph, bigger done dot (Leo 2026-10-14)
Leo on the first capture: "wait that's not centered? why is it superscript?", and the blue done dot was "too tiny".
statusGlyphFrame(trailing:centerY:)).dot: r 6, a 6x6 pixel-art disc at 1x).Same scene on both builds, clean profiles, workspace-1 selected. Before: this PR's earlier head 7bad855. After: ad47b4c. I watched both 4 s recordings: only the cmux-next spinner moves.
plan image 20410/status-center-before-after.png
plan image 20410/status-center-zoom.png
plan gif 20410/status-center-before4.gif (from status-center-before4.mp4)
plan video 20410/status-center-before4.mp4
plan gif 20410/status-center-after4.gif (from status-center-after4.mp4)
plan video 20410/status-center-after4.mp4
uploaded 20410/status-center-before-after.png
uploaded 20410/status-center-zoom.png
uploaded 20410/status-center-before4.gif
uploaded 20410/status-center-before4.mp4
uploaded 20410/status-center-after4.gif
uploaded 20410/status-center-after4.mp4
Full quality: status-center-before4.mp4
Full quality: status-center-after4.mp4
Receipts (fleet
run-swift-testing-suites.sh, CmuxNext):SidebarStatusGlyphCenterTestsfails (the glyph sits 7.5 pt off the row's middle on a two-line row).SidebarStatusGlyphCenterTests,SidebarRowGutterTestsandCodexStatusIconSetTestspass; no suite failed.check-release-compile.sh) on ad47b4c: BUILD SUCCEEDED.Crisp at 3x and 5x (review finding)
Review found
usesHintinggave 30 px and 50 px output (a 10 pt glyph at 3x or 5x) the 1x 10-grid pixel art, blown up and coarse. Now only exactly 10 device pixels takes the pixel art; every larger size draws the vector master. Swift (gen-status-glyphs.py, regeneratedStaticStatusGlyph.generated.swift) and web (StatusGlyph.tsxusesPixelArt, regeneratedstatusGlyphs.generated.ts) agree.StaticStatusGlyphScaleTests.onlyTenDevicePixelsTakeThePixelArtfails (curves 0 > 0at 30 and 50 px);webviews/test/status-glyph-pixel-art.test.tsfails.NewTabStyleSwitchTests, also fails on the red run and is a base test fixed by cmux NEXT: NewTabStyleSwitchTests awaits the style save instead of counting yields #20423.Changelog
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.Summary by cubic
Adds two status glyph sets for CmuxNext status indicators: six static glyphs for the Static status mode (Reduce Motion) and a Codex-like set modeled on Codex's sidebar. The Codex-like set is now the default for everyone; the other sets, including Static and Symbols, stay as Debug alternates.
Both sets are generated from one schema (
schemas/status-glyphs/static-status.json), which emits the Swift CGPaths and web SVG paths. Glyphs draw pixel art only at exactly 10 device pixels; any larger size uses the vector master, so 3x and 5x no longer blow up the pixel art.The Codex-like set follows Codex's sidebar: a grey spinner (a still ring when nothing animates), a blue done dot, muted line bubble and shield for waiting, and a red line
ifor error. The status glyph now centers on the row's middle beside the count badge, on one-line and two-line rows alike.Written for commit 4e53172. Summary will update on new commits.