Skip to content

cmux-next sidebar: more menu on group headers and workspace rows (cx-a9h6) - #19019

Merged
teamleaderleo merged 16 commits into
feat-cmux-nextfrom
feat-cmux-next-row-more-menu
Oct 10, 2026
Merged

teamleaderleo merged 16 commits into
feat-cmux-nextfrom
feat-cmux-next-row-more-menu

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Group headers and workspace rows get a more (⋯) button that opens the same menu as a right-click, ChatGPT project style (cx-a9h6).

  • Workspace rows: the button shows on hover and on the selected row, left of the close button. It selects the row the way a right-click does, then opens the row's context menu under the button. That menu includes pin, rename, color and icon, Reveal in Finder, and close.
  • Group headers: the ⋯ button and a right-click both open the group's full menu: New Workspace, Rename, Pin, Color and Icon, Mark read, Collapse, Move, Edit Config, Copy ID, Ungroup, Close Workspaces, and Delete. A click on the group's name or chip still opens the editor (name, color, emoji). The accessibility Edit action opens the editor too.
  • Both buttons use the icon pack's action.more.

Not in this PR: the group menu has no "Reveal in Finder" and no "Section" entry (the closest is Move to Space). Adding them means new catalog actions, which wait for the action-catalog freeze and regenerated exports, so they're a follow-up.

This PR is stacked on #18997 (cx-25az) and retargets to feat-cmux-next when that lands.

Visible change (native): a ⋯ button appears on the hovered or selected workspace row and on a hovered group header; clicking it opens the right-click menu.

Testing

  • No unit tests (cmux-next rule). The button runs the same contextMenuProvider menu that a right-click shows, so it adds no new menu logic. Real verification: in a tagged build, hover a workspace row and a group header, click ⋯, and compare with a right-click. Not run yet.
  • Fleet run-swift-testing-suites.sh and the hosted Swift canary on the exact head.

Localization: the new key sidebar.a11y.workspaceMenu ("Workspace options") is translated in all 21 locales of the sidebar catalog.

Changelog

Added: a more (⋯) button on sidebar workspace rows and group headers that opens the same menu as a right-click

🤖 Generated with Claude Code


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


Summary by cubic

Adds more (⋯) buttons to sidebar group headers and workspace rows that open the same menu as a right-click, ChatGPT project style.

  • Workspace rows show the button on hover and on the selected row; clicking it selects the row first, then opens the row's context menu under the button (the button is wired to its menu).
  • Group headers show the button on hover; the group's full menu opens from the button or a right-click, while clicking the chip still opens the editor (accessibility Edit opens it too).
  • The group's right-click now opens the full menu instead of the editor. The group menu lacks "Reveal in Finder" and "Section" entries, which need new catalog actions and are a follow-up.

Written for commit 87f68b5. Summary will update on new commits.

View guided diff Turn on auto-fix


Note

Low Risk
UI-only sidebar interaction changes that reuse existing context menus; no new menu logic or security-sensitive paths.

Overview
Adds ChatGPT-style ⋯ controls on cmux-next sidebar workspace rows and retargets group header ⋯ / right-click to the full context menu instead of opening the group editor.

Workspace rows get a new moreButton that appears on hover and when selected (left of close/badge). Tapping it selects the row like a right-click, then pops the same contextMenuProvider menu via new SidebarRowMenu. Selection changes trigger relayout so the button stays visible on the selected row.

Group headers split actions: ⋯ and right-click call groupEditing.showMenu (full group menu); chip click and VoiceOver Edit use new onEdit → groupEditing.open. showMenu is now callable from context menu handling.

Adds localized a11y string sidebar.a11y.workspaceMenu ("Workspace options") across the sidebar catalog.

Reviewed by Cursor Bugbot for commit 87f68b5. Bugbot is set up for automated code reviews on this repo. Configure here.

Showcase

row more button before after

Open the live gallery

teamleaderleo and others added 14 commits October 10, 2026 02:48
…s, header +, spam-click toggles (cx-q5jw)

- Member rows start right of their group's line (SidebarStyle.groupGutter),
  so hover and selection fills never cover it; tab rows drop their own rail.
  The line runs from under the header to the last member.
- Header bar and line take the theme's ANSI color itself (no pastel), with
  black or white ink by contrast; none is the sidebar's tonal step.
- New groups get colors in a stable theme order (GroupColor.automaticOrder).
- Header + (hover, with the more button) runs workspaceGroup.newWorkspace,
  the editor's New Workspace in Group.
- Fast repeated clicks on the chip toggle the editor open, closed, open:
  clicks are matched by event number, not a double-click time window.
- Member titles start closer to the line (less left space).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd emoji (cx-25az)

- GroupTint: a palette token or a custom #RRGGBB, the two color shapes the
  daemon stores for a group. SidebarGroup keeps the token and a custom hex.
- The group editor's dot row adds two icon-only controls (Lawrence: never
  full width): a custom color dot that opens the system color panel (wheel,
  sliders, hex field) and shows the color once chosen, and an emoji button
  that runs workspaceGroup.setIcon (the shared icon picker, emoji included).
- workspaceGroup.setColor and the bridge take a GroupTint, so the CLI and
  palette accept #RRGGBB as well as the tokens.
- Two new strings in all 21 sidebar catalog locales.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- No + on an empty group: a click on it raced the editor closing on a new
  group, which then went before the workspace arrived. The editor's New
  Workspace in Group stays.
- The + is hidden (not only transparent) while it does not show, so a
  click there before any hover still collapses the group.
- Header buttons draw in the header's ink (SidebarIconButton.tintOverride);
  their own hover tint overrode white ink on dark accents.
- The + never moves left of the bar's padding in a narrow sidebar.
- The tab-drop insertion line inside a group starts right of the group's
  line, as the row does.

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

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/GroupHeaderRowView.swift
…5az)

- The emoji button (workspaceGroup.setIcon) and the custom color dot keep a
  new empty group: both pickers edit it after the editor closes, and the
  close used to delete it first.
- The shared color panel is released when it closes: its target, action,
  alpha and continuous settings go back, so a later Show Colors elsewhere
  never recolors the group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…x-next-group-color-picker

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/GroupEditor/SidebarGroupEditor.swift
#	Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/GroupHeaderRowView.swift
… the same as right-click (cx-a9h6)

- Workspace rows get a more button on hover and on the selected row; it
  shows the row's right-click menu (Pin, Rename, Color and icon, Reveal in
  Finder, Move to group, Close...) under the button.
- A group header's more button and a right-click show the group's full
  menu (Pin, Rename, Color, Icon, Close workspaces, Ungroup, Delete); a click
  on the chip still opens the name and color editor.
- The group's more glyph is the horizontal ellipsis, as on rows.
- New string sidebar.a11y.workspaceMenu in all 21 sidebar locales.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d-file type limit) (cx-25az)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e instead of a full fill (cx-25az)

Leo 2026-10-10 screenshot: no full pastel header fill. The group's color is
a dot on its line (the line runs from the dot to the last member); the name
is in the sidebar's text color and lines up with the members' titles; hover
and drop fills are a row's, right of the line. The faint vertical more glyph
becomes the clear three-dot (ellipsis) symbol.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…x-next-group-color-picker

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/GroupHeaderRowView.swift
…row-more-menu

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/GroupHeaderRowView.swift
#	Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/WorkspaceRowView.swift
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Important

Review skipped

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

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

⚙️ Run configuration
  • Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: db922cca-b2a2-4575-8ec5-d46421835055

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@blacksmith-sh

This comment has been minimized.

@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

CI fast guards failed on 87f68b5331 (https://github.com/manaflow-ai/cmux/actions/runs/38047041734). It does not block the merge; a red guard merged into main breaks it for every open PR.
The log named no failed step; see the run.

Agents: python3 scripts/ci/guard_attribution.py fix applies the mechanical fixes locally. This comment is updated in place on each push.

@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Unclear: GhosttyKit release check failed: no known signature; failed step: Verify published GhosttyKit release and checksum.

CI failed on 87f68b5331 (run 38047041734 attempt 1): 1 unknown.
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped.

Job Verdict Why
GhosttyKit release check unknown no known signature; failed step: Verify published GhosttyKit release and checksum

Not re-run automatically: GhosttyKit release check is not a machine failure.

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

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Base automatically changed from feat-cmux-next-group-color-picker to feat-cmux-next October 10, 2026 11:01
teamleaderleo and others added 2 commits October 10, 2026 04:01
…x-next-row-more-menu

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/GroupHeaderRowView.swift
Review found the button shown but never connected: onPress was nil, so a
click did nothing.

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

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 87f68b5. Configure here.

groupEditing.open(id)
// A right-click on a group shows its full menu, the same as its
// more button (cx-a9h6); a click on the chip edits name and color.
groupEditing.showMenu(id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Group right-click menu dismisses immediately

High Severity

A group header right-click now calls showMenu, which runs NSMenu.popUp from inside menu(for:) and then returns nil. AppKit invokes menu(for:) during right-mouse-down, so tracking starts while the button is still down. The menu is anchored under the chip, away from the cursor, and the following mouse-up dismisses it. If AppKit flips the menu upward near the screen edge, that same release can activate an item such as Close or Delete.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 87f68b5. Configure here.

@teamleaderleo
teamleaderleo merged commit 31db8ce into feat-cmux-next Oct 10, 2026
67 of 69 checks passed
@teamleaderleo
teamleaderleo deleted the feat-cmux-next-row-more-menu branch October 10, 2026 11:08
teamleaderleo added a commit that referenced this pull request Oct 14, 2026
…ault (#20442)

* test(cmux-next): groups draw Lawrence's palette in every theme; the more button opens the group editor (red)

Tab group chips and underlines, sidebar group headers and members' lines
draw the fixed group palette (#15516) under a dark, a light and a
wallpaper theme; new groups take its order; the header's more button
opens the cx-rcby editor with its rows in order. Red on base: #18991 and
#19179 draw the theme's ANSI colors, #19019 points the more button at
the right-click menu.

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

* fix(cmux-next): tab and sidebar groups use Lawrence's group palette and his group editor again

Lawrence 2026-10-13: familiar group UI first.

- Colors: every group color is the fixed palette from #15516
  (GroupColor.rgb(_:dark:), light and dark variants picked by the theme
  scope), not the theme's ANSI colors (#18991 sidebar, #19179 tab strip,
  marks and palette balls). The sidebar header bar and the tab chip take
  the palette's name background (`fill`), lines, dots, swatches and marks
  the color itself (`swatch`); the header keeps its 4.5:1 ink step.
- New groups take the palette's own order without grey and blue (red,
  yellow, green, pink, purple, cyan, orange), as GroupColor.automatic did
  before #18991; agent presence keeps its own order.
- The group header's more button opens the cx-rcby group editor again
  (#19019 had pointed it at the right-click menu). The full menu stays on
  right-click, on the editor's More Group Actions row and on VoiceOver's
  More Group Actions action.
- The editor's color dots are named by color again (the sidebar catalog's
  21-locale entries from before d8bf299); "Theme color %d" is gone.
- No Labs variant: the only Labs setting (labs.previewFeatures) would need
  live wiring and new help text, so the theme-derived group palette is dropped.

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

* refactor(cmux-next): drop the unused group headerInk (review)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
lawrencecchen added a commit that referenced this pull request Oct 14, 2026
…pens the group editor

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

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

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 14, 2026
…20492)

* 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>

* 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)

* 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 a8608cf

- 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: sidebar rows lead with a logo or kind icon, badge its corner, and draw no count

Leo and Lawrence 2026-10-14: no count numbers on NEXT rows; one corner badge on the leading
icon (T3 Code style), the most urgent state first, and the dim age on the right.

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

* feat: NEXT sidebar rows badge their logo's corner instead of drawing a count

Leo and Lawrence 2026-10-14 ("maybe we can do unreads like this", T3 Code's thread list):
- a row leads with its harness logo or its kind icon; a user's icon still wins;
- one badge on that icon's bottom-right corner, on a disc of the row's backdrop so it cuts
  a thin ring: the most urgent of failed, needs approval, question, working (the grey
  spinner, a still ring under Reduce Motion), done or unread (the blue dot);
- no count number; the dim age of the last change sits on the right, and the count reads
  in the hover card, the tooltip and the accessibility label.

Debug Settings sidebar.rowBadge switches to Leo's stack (up to three overlapping glyphs,
one per pending item) or back to the count and trailing glyph. SidebarModel.rowBadgeStyle
pins a style per sidebar.

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

* fix: a 10 pt corner badge on the pixel grid; stack items as whole chips

Coordinator 2026-10-14 on the #20492 captures: the corner badge's i, shield and bubble were
unreadable at 1x, and at 40% overlap the stack's half-hidden dot read as a D.
- Every badge glyph is the static glyphs' 10 pt box in a whole-point cell, so their 1x
  pixel art lands on the grid; a 1.5 pt cutout ring hugs it (6 pt for the unread dot).
- Each stack item is a full chip (a tonal disc ringed by the backdrop); a chip tucks under
  the one before only as far as its padding, so every glyph stays whole.

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

* fix: pass the badge look when a row draws no badge

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

* fix: the row's time never ends in an ellipsis

A time today ("1:26 AM") measured a few points short of its text in the compact row's
trailing slot and drew "1:26…".

Co-Authored-By: Claude Opus 5.5 <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>

* 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>

* Home: the home workspace with hq-ec's page in a left chat dock; Home links open on its right side (cx-ebm.59.5, cx-ebm.59.6)

S5 (cx-ebm.59.5): Home is the daemon home workspace. On every connect the app
ensures ONE `home-channels` page tab in it (keyed new-conversation-tab on the
workspace) and moves it into a left dock column with the agent chat role
through TabMoves.toNewDockColumn (the Cmd-I dock, cx-ihyx2; width
TabMoves.leftDockWidth), never a second dock. A home from before migrates on
its first connect: its Chief conversation tabs close (the page shows the Chief
conversation) and the page moves into the dock; when nothing else stays on the
right, the move respawns a terminal there in the same daemon commit (the
daemon undocks a screen with no scrolling column). home.show reveals the home
workspace in the active window (or brings forward the window that has it). The
classic top page stays behind HomeDock.classic (false here; the setting lands
separately). New code is in CmuxNextHomeEngine/HomeDock.swift; CmuxNextApp net
lines unchanged.

S6 (cx-ebm.59.6): the home-channels page tab's onOpenExternal (a person's
click on an http(s) link) calls workspace.open_url {machine, session: current,
url} on the app's own daemon connection (WorkspaceOpenURLClient). No routing
in Swift: the daemon picks the pane and the tab.

* 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)

* Home dock: review fixes; classic Home reads DesignSettings.classicHome (cx-ebm.59.5)

- HomeService: one @ObservationIgnored declaration per property (the macro
  takes one binding; the fleet compile caught it).
- HomeDock: no second dock move while one is in flight or the tree does not
  show its dock yet (launch runs ensure twice); the guard also needs
  tab-column-respawn-v1.
- home.show refuses with homeNeedsFocus for a run that may not change the
  view, as before.
- HomeDock.classic reads DesignSettings.classicHome (off). The setting that
  sets it (labs.classicHome) touches the frozen settings paths and lands
  separately.

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

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

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

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

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

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

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

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

CmuxNextApp line count unchanged (113450).

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

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

CmuxNextApp line count unchanged (113450).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* test: the row badge stack shows five by default, Debug Settings picks 3/5/7, and a preview set shows 1-7 items

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

* feat(next): the row badge stack shows five, 3/5/7 in Debug Settings, and a preview set with 1-7 items

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

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

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

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

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

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

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

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

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

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

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

* chore(next): PR autofix at 7be0d24

- regenerated schemas/defaults/cmux-next.json

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

---------

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

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

* webviews: test that every popup draws one hairline edge

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

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

* webviews: popups draw one hairline, not two

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

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

* chore(next): PR autofix at 1cc6616

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore(next): PR autofix at 54dd59d

- regenerated schemas/defaults/cmux-next.json

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

---------

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

* fix(next): the preview's working row shows its spinner; the cap test registers the sidebar tunables

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

* cmux-next: name the owner of ControlWorkspaceInfoCache's publish hop (clears the concurrency red) (#20485)

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

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

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

* fix: keep PaneAnimationsSetting internal to CmuxNextSettings

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

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

* chore(next): PR autofix at c2bdb98

- regenerated schemas/defaults/cmux-next.json

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

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

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

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

* chore(next): PR autofix at 7cf07e3

- regenerated schemas/defaults/cmux-next.json

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore: regenerate the settings exports on the merged base

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

* chore: refresh the defaults snapshot on the merged base

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

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

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

---------

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

* fix: badge time from row content, stack-only overflow, themed tooltip

Review fixes: the compact row's dim time comes from the layout's now and
skips rows whose second line shows the last activity or minimal mode; the
tooltip names what the stack left out only for the stack; preview rows
have no close button.

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: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
Co-authored-by: safe-push <safe-push@cmux.local>
teamleaderleo added a commit that referenced this pull request Oct 14, 2026
… a running app (hq#1870) (#20471)

* 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)

* test(control): a deleted control socket file comes back while the app runs (red)

Two readiness soaks on one capture mini lost /tmp/cmux-debug-<tag>.sock at about
11:00Z (an external /tmp sweep); the app kept listening on the unlinked inode and no
client could connect until a relaunch. These tests delete the file under a running
server and expect a ping within 5 s, with the same mode, and expect a path another
server took meanwhile to stay that server's.

hq#1870, no bead

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

* fix(control): put the control socket back when its file is deleted under a running app

ControlSocketPathWatch watches the socket's directory with kqueue (no timer) and, once
a burst of changes settles, asks the server for a new listener when nothing is at the
path. The new socket is bound like the first (private staging dir, its mode, then
RENAME_EXCL), so a path another server or a file took meanwhile is never replaced, and
stop still unlinks only the server's own inode. Open connections keep working. The
socket-file helpers move to ControlSocketServer+File.swift (move-only) to keep the
server under the file budget.

hq#1870, no bead

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

* test: a stop during a socket restore closes the new socket

The restore cancelled a listener it never resumed when the server stopped
mid-bind. The other-server test now swaps the path in one rename.

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

* fix: restore the control socket on its accept queue

The path check runs inline on the accept queue instead of a timer task, so a
restore never races an accept pausing on descriptor exhaustion. The new
listener is resumed before it is published, and resumed then cancelled when
the server stopped mid-restore. The watch stops after three failed restores
in a row instead of re-triggering itself. Adds the wake source to the
idle-wakeups plan.

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

* fix: set SO_NOSIGPIPE on the stale-socket probe

The probe moved to its own file, where the crash-safety check looks for it.

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

* test: take the restore path with a plain listener

A second ControlSocketServer renamed into place restored its own path and
dropped the listener the test connected to.

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

* test: failing socket restores back off and still come back

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

* fix: back off failing socket restores instead of giving up

Three failed restores in a row stopped the watch for good, so a delete
during descriptor exhaustion lost the socket until a relaunch. The watch now
ignores events and retries with a capped backoff until a restore works.

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

* chore(next): PR autofix at 8dd3953

- regenerated schemas/defaults/cmux-next.json

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Home: the home workspace with hq-ec's page in a left chat dock; Home links open on its right side (cx-ebm.59.5, cx-ebm.59.6)

S5 (cx-ebm.59.5): Home is the daemon home workspace. On every connect the app
ensures ONE `home-channels` page tab in it (keyed new-conversation-tab on the
workspace) and moves it into a left dock column with the agent chat role
through TabMoves.toNewDockColumn (the Cmd-I dock, cx-ihyx2; width
TabMoves.leftDockWidth), never a second dock. A home from before migrates on
its first connect: its Chief conversation tabs close (the page shows the Chief
conversation) and the page moves into the dock; when nothing else stays on the
right, the move respawns a terminal there in the same daemon commit (the
daemon undocks a screen with no scrolling column). home.show reveals the home
workspace in the active window (or brings forward the window that has it). The
classic top page stays behind HomeDock.classic (false here; the setting lands
separately). New code is in CmuxNextHomeEngine/HomeDock.swift; CmuxNextApp net
lines unchanged.

S6 (cx-ebm.59.6): the home-channels page tab's onOpenExternal (a person's
click on an http(s) link) calls workspace.open_url {machine, session: current,
url} on the app's own daemon connection (WorkspaceOpenURLClient). No routing
in Swift: the daemon picks the pane and the tab.

* 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)

* Home dock: review fixes; classic Home reads DesignSettings.classicHome (cx-ebm.59.5)

- HomeService: one @ObservationIgnored declaration per property (the macro
  takes one binding; the fleet compile caught it).
- HomeDock: no second dock move while one is in flight or the tree does not
  show its dock yet (launch runs ensure twice); the guard also needs
  tab-column-respawn-v1.
- home.show refuses with homeNeedsFocus for a run that may not change the
  view, as before.
- HomeDock.classic reads DesignSettings.classicHome (off). The setting that
  sets it (labs.classicHome) touches the frozen settings paths and lands
  separately.

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

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

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

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

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

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

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

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

CmuxNextApp line count unchanged (113450).

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

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

CmuxNextApp line count unchanged (113450).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore(next): PR autofix at 7be0d24

- regenerated schemas/defaults/cmux-next.json

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

---------

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

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

* webviews: test that every popup draws one hairline edge

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

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

* webviews: popups draw one hairline, not two

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

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

* chore(next): PR autofix at 1cc6616

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore(next): PR autofix at 54dd59d

- regenerated schemas/defaults/cmux-next.json

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

---------

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

* cmux-next: name the owner of ControlWorkspaceInfoCache's publish hop (clears the concurrency red) (#20485)

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

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

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

* fix: keep PaneAnimationsSetting internal to CmuxNextSettings

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

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

* chore(next): PR autofix at c2bdb98

- regenerated schemas/defaults/cmux-next.json

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

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

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

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

* chore(next): PR autofix at 7cf07e3

- regenerated schemas/defaults/cmux-next.json

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore: regenerate the settings exports on the merged base

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

* chore: refresh the defaults snapshot on the merged base

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

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

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

---------

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

* test(cmux-next): a failed LineClient connect closes its descriptor once

The init closed the socket and then threw with every stored property set, so deinit closed the same number again. Under parallel suites that number already belonged to another test's listener: listen failed with EBADF in passwordModeRequiresLogin, and a restored socket stopped answering in aDeletedSocketFileComesBackWithItsMode (macOS 27 run at 140aa1f). The restore tests' connect-until-up loops made it frequent. Set the stored descriptor only after the connect works.

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

---------

Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
Co-authored-by: safe-push <safe-push@cmux.local>
teamleaderleo added a commit that referenced this pull request Oct 14, 2026
…s picked (cx-uq9xi) (#20506)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore(next): PR autofix at 73b393e337d9

- regenerated schemas/defaults/cmux-next.json

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

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

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

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

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

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

* Channels view: origin auth comments (review)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* CmuxNext: static status glyphs for the Static status mode

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

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

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

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

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

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

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

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

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

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

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

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

* feat: Codex-like status icon set

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

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

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

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

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

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

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

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

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

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

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

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

Red: the default is still symbols.

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

* feat: Codex-like status icons are the default

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

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

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

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

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

* chore(next): PR autofix at a8608cf74601

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

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

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

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

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

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

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

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

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

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

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

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

---------

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

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

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

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

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

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

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

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

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

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

* CmuxNext: no gray ring trails a split

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

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

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

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

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

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

* chore(next): PR autofix at 44de1da3d855

- regenerated schemas/defaults/cmux-next.json

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

* chore(next): PR autofix at c7d76dbf2fb3

- regenerated schemas/defaults/cmux-next.json

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

---------

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

* cmux-next: tabs take the max width while the strip has room

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

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

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

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

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

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

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

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

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

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

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

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

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

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

CmuxNextApp line count unchanged (113450).

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

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

CmuxNextApp line count unchanged (113450).

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

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

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

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

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

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

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

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

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

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore(next): PR autofix at c26ac6910070

- regenerated schemas/defaults/cmux-next.json

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

* chore(next): PR autofix at b55536721932

- regenerated schemas/defaults/cmux-next.json

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

---------

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

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

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

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

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

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

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

* optchat-chief: loop guard review fixes

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore(next): PR autofix at 918e7d3d8115

- regenerated schemas/defaults/cmux-next.json

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

---------

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

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

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

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

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

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

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

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

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

* chore(next): PR autofix at 3dd82a78f6aa

- regenerated schemas/defaults/cmux-next.json

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

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

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore(next): PR autofix at 7be0d24457e0

- regenerated schemas/defaults/cmux-next.json

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

---------

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

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

* webviews: test that every popup draws one hairline edge

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

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

* webviews: popups draw one hairline, not two

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

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

* chore(next): PR autofix at 1cc661608ff2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore(next): PR autofix at 54dd59d45f3d

- regenerated schemas/defaults/cmux-next.json

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

---------

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

* cmux-next: name the owner of ControlWorkspaceInfoCache's publish hop (clears the concurrency red) (#20485)

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

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

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

* fix: keep PaneAnimationsSetting internal to CmuxNextSettings

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

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

* chore(next): PR autofix at c2bdb982abed

- regenerated schemas/defaults/cmux-next.json

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

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

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

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

* chore(next): PR autofix at 7cf07e3731b0

- regenerated schemas/defaults/cmux-next.json

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore: regenerate the settings exports on the merged base

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

* chore: refresh the defaults snapshot on the merged base

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

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

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

---------

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

---------

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

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

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

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

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

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

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

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

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

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

* chore(next): PR autofix at 5099ccd

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

CmuxNextApp line count unchanged (113450).

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

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

CmuxNextApp line count unchanged (113450).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore(next): PR autofix at 7be0d24

- regenerated schemas/defaults/cmux-next.json

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

---------

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

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

* webviews: test that every popup draws one hairline edge

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

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

* webviews: popups draw one hairline, not two

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

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

* chore(next): PR autofix at 1cc6616

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore(next): PR autofix at 54dd59d

- regenerated schemas/defaults/cmux-next.json

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

---------

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

* cmux-next: name the owner of ControlWorkspaceInfoCache's publish hop (clears the concurrency red) (#20485)

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

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

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

* fix: keep PaneAnimationsSetting internal to CmuxNextSettings

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

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

* chore(next): PR autofix at c2bdb98

- regenerated schemas/defaults/cmux-next.json

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

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

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

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

* chore(next): PR autofix at 7cf07e3

- regenerated schemas/defaults/cmux-next.json

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore: regenerate the settings exports on the merged base

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

* chore: refresh the defaults snapshot on the merged base

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

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

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

---------

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
Co-authored-by: safe-push <safe-push@cmux.local>
teamleaderleo added a commit that referenced this pull request Oct 14, 2026
…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…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant