Skip to content

cmux NEXT: Retry Now and Restart cmux-tui on the not-connected screens (cx-jniyz.1) - #20765

Merged
teamleaderleo merged 28 commits into
feat-cmux-nextfrom
cx-next-handoff/cx-jniyz.1-restart-cmux-tui
Oct 18, 2026
Merged

teamleaderleo merged 28 commits into
feat-cmux-nextfrom
cx-next-handoff/cx-jniyz.1-restart-cmux-tui

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

cx-jniyz.1, the Swift half of cx-jniyz item (b). When cmux-tui is stuck, the app's launch window and the App Store's not-connected banner showed the failure but offered no way out.

Both now have two buttons:

  • Retry Now fires DaemonService.retryWake.
  • Restart cmux-tui appears only after a plain server ensure has failed: a restore timeout, the ensure call itself timing out, or server.unavailable. The verb can't tell a stuck owner from one still restoring, so the restore bound has to pass first. A later successful ensure hides the button again.
    • It asks first, in the app's window-scoped dialog (cmux.dialog.restartDaemon): "Restart cmux-tui? cmux-tui is not responding. Restarting it stops the stuck process and starts a new one. Terminals running in it may be lost." Buttons: Cancel / Restart.
    • It then runs the app's ensure command with --replace-unresponsive added, and fires retryWake whether that worked or not.
    • The replace prints nothing for up to 20 s, so the call allows 30 s of silence before restore progress applies, with a hard cap of 180 s.
    • A server.replace_* refusal (exit 3) shows "cmux-tui could not be restarted. Try again in a moment." The refusal code goes to Copy Details.

Remote machines get neither button: there is no launcher for them.

Where the code is:

  • How each ensure ended is recorded in DaemonLauncher+Replace.swift.
  • The state and actions are in DaemonRecovery, owned by DaemonService.
  • The buttons are in DaemonRecoveryControls.
  • The App Store banner uses a small AppsRecovery hook. Its accessibility changed from .combine to .contain so the buttons can be pressed.

The 7 new strings are in all 21 languages. The translations are machine-made and need a human review.

Testing

  • New test: DaemonRestartTests. A stand-in cmux-tui script runs on the app's real startup path; no real daemon is started and no process is signalled. It checks that Restart is hidden before a failed ensure and offered after it, and that pressing it runs ensure with --replace-unresponsive and then retries. It also covers a refusal.
  • Red (test commit 6db7e84, fleet run-swift-testing-suites.sh Packages/macOS/CmuxNext, job af884d75c1e513231215d6df): only DaemonRestartTests failed. The app reached the failure screen, but the buttons were missing: (retry → nil) != nil, (shown("daemon.restart") → nil) != nil.
  • Green (758e3ef, jobs 599df8b981aa8790145610b8 and 558a20f6d216e1cde74136d8): DaemonRestartTests passed in both runs (336 suites).
  • Release compile of 758e3ef (fleet, isolated, job f4ef136ef8f5f67e4ce6f78b): BUILD SUCCEEDED on Xcode 26.6. There is no Xcode 27 compile receipt yet; the bead asks for one.
  • Local checks: crash_ratchet.py, check-no-godfiles.sh (no file grew past its limit), check-concurrency.sh, check-system-blue.py and check-l10n.sh (21 languages, 0 errors) all passed.
  • Not yet done:
    • the GUI proof from the bead (fleet app, SIGSTOP its own daemon, press Restart through the debug socket, see a new pid);
    • light and dark captures of both surfaces;
    • the App Store banner path has no test of its own;
    • restart behaviour is not yet agreed with the PTY-holder lead.

Changelog

Added: Retry Now and Restart cmux-tui on the not-connected screens when cmux-tui stops responding

Target

The launch window's connecting and failure screen (DaemonConnectingView), the recovery controls, the App Store banner's recovery actions and their 12 strings live in a new CmuxNextDaemonRecovery target (SW14). CmuxNextApp keeps only the wiring in WindowController and AppsService. CmuxNextApp drops to 113,559 lines: 170 under base (113,729), 133 under the cap (113,692). That also clears the base's +37 over the cap from #20709, which #20883 fixes separately. ci-target-graph.json is updated by hand to match; the cmux-next generated files job checks it.

Proof

Captures (fleet DEV builds of 758e3ef and its base; method and PID evidence in this comment)

Launch window failure screen (owner SIGSTOPped, no saved layout):

base head
light base light head light
dark base dark head dark

App Store not-connected banner:

base head
light base light head light
dark base dark head dark

Restart flow (head): Restart cmux-tui, confirm, "Restarting cmux-tui…", reconnected about 11 s after the press (owner 68203 → 67513).

restart flow

Full quality: pr20765-head-restart-flow.mp4

light dark
dialog light dialog dark

🤖 Generated with Claude Code


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


Summary by cubic

Adds Retry Now and Restart cmux-tui to the not-connected screens so a stuck cmux-tui no longer leaves the launch window and App Store banner at a dead end. Remote machines get neither button, since they have no launcher.

  • Retry Now fires the service's retry wakeup immediately.
  • Restart cmux-tui appears only after a plain server ensure failed the way a stuck owner makes it fail (restore timeout, no answer, or server.unavailable). It asks for confirmation first, then runs the app's ensure command with --replace-unresponsive and retries the connection; a refusal shows an error and leaves Retry Now.
  • A cancelled ensure no longer hides Restart, and a successful ensure clears an earlier Restart failure so it does not resurface on a later disconnect.
  • The launch screen and recovery controls moved into a new CmuxNextDaemonRecovery target; DaemonRestartTests covers the flow against a stand-in cmux-tui script on the app's real startup path.

Also in this branch

  • A strip's tab bar lays out for where its pane rests, so its tabs move rigidly with the column instead of being pinned to the traffic lights while the strip camera scrolls; a moving column keeps the larger traffic-light inset until it rests.
  • A top-item selection repaints only the old and new sidebar item, with no band update or list reload; the perf gate budgets 0 row configures and band updates and names list reloads by caller, and strip-scroll-check.py verifies strip recordings frame by frame.

Written for commit 3eaf574. Summary will update on new commits.

View guided diff Turn on auto-fix

teamleaderleo and others added 2 commits October 14, 2026 15:19
… owner (cx-jniyz.1)

Behavior test on the app's real startup path against a stand-in cmux-tui
script: Restart is offered only after a plain ensure failed with
server.restore_timeout, runs the same ensure with --replace-unresponsive
after a confirm, then connects again; a refusal says so and leaves Retry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The launch window's failure and the App Store's disconnected banner offer
Retry Now (fires the service's retryWake) and, once a plain server ensure
failed as a stuck owner makes it fail (server.restore_timeout,
server.unavailable or no answer in time), Restart cmux-tui. Restart asks
first in the cmux dialog (terminals running in a stuck cmux-tui may be
lost), then runs the app's own ensure argv plus --replace-unresponsive and
fires retryWake. The replace call allows 30 s of stdout silence (10 s probe
+ 5 s TERM + 5 s KILL) before restore progress applies. A refusal
(server.replace_*) shows a plain line and leaves Retry Now.

The gate lives on the launcher (EnsureOutcome), so the startup loop, the
first attempt and every reconnect feed it. Strings in all 21 languages.

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

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Important

Review skipped

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

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

⚙️ Run configuration
  • Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: c3b51373-544c-4638-a9f9-2d3d83be9be1

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.

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

all web/size checks green

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Not verified: macOS jobs did not run: compile and app tests were skipped on 3eaf574a29; ci-status is green without them.

CI passes on 3eaf574a29 (run 39130809160 attempt 1).
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped (the macos job needs both to succeed). Re-run CI once they pass, before merging.

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.

…s a stale failure (cx-jniyz.1)

Review follow-ups: a torn-down connection's cancelled ensure no longer
hides Restart, and an ensure that works clears an earlier Restart's
failure so a later disconnect does not show it.

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

Copy link
Copy Markdown
Collaborator Author

In-app proof: Retry Now and Restart cmux-tui (fleet DEV builds)

These captures are from head 758e3eff1c71a4778c31d0287b8f7a839286b10b (fleet job 40d1c48853b925ee1515cdba) and its merge base with feat-cmux-next, a2ea1b0e5f661ddad466371bb13b3399482f5766 (job 7c804e96c8a03a6d8e102de6). Both are Mac-builder DEV builds, run on capture slots on cmux-mac-mini-cua-4. The current head 50786b8de5b725ed2517055c8e36d36ec5d37afd came after these builds; it only changes logic (a cancelled ensure leaves the Restart gate alone, and a successful ensure clears a stale restart failure), with no UI or string changes. It wasn't rebuilt or captured.

How the owner was made stuck. Each app was launched once so its own cmux-tui --headless --session cmux-app-… owner came up, then quit with quitKeepSessions. That owner was then SIGSTOPped by its exact pid (the only cmux-tui of that bundle with that session that isn't a terminal host), and the app was relaunched against it. Nothing else was signalled. Base stills were taken after 135 s or more, past the restore bound.

Launch window failure screen

The capture deleted this tag's launch-snapshot.json and sidebar-snapshot-v1.json before the relaunch. With them present, the window draws the last layout instead of this screen (see "Found along the way").

base head
light base light head light
dark base dark head dark

App Store not-connected banner

This was relaunched on the stuck owner with the launch snapshot kept: the last layout is drawn, then appStore.show opens the store.

base head
light base light head light
dark base dark head dark

On head, Retry Now shows at once (banner text "still starting its background service"). Restart cmux-tui joins it after the first plain ensure fails, about 135 s after relaunch, when the text changes to "taking longer than 2 minutes". On the launch window it appeared 124 s after relaunch. Before that the window shows "couldn't start its background service" with Retry Now and Copy Details only. Base shows the same texts with no buttons.

Restart flow (head)

restart flow

Full quality: pr20765-head-restart-flow.mp4 (frames from debug.window_snapshot at about 4 fps; the dark frame in the middle is me switching appearance for the dialog still).

light dark
dialog light dialog dark

Restart cmux-tui was pressed by an AX click through cua-driver. That opened cmux.dialog.restartDaemon with the PR's title, body and Cancel / Restart buttons, and debug.dialog {press: "restart"} answered it. The window then showed "Restarting cmux-tui…" with both buttons disabled. About 11 s after the press it switched to the workspace.

PID evidence (launch-window run):

  • Old owner: 68203, in state Ts (stopped) from before the relaunch until the press.
  • About 10.6 s after the press, 68203 no longer existed (ps -p was empty) and 67513 was the only owner listening on cmux-app--085de0c4.
  • App log, com.cmuxterm.app.next:app.daemon: cmux-tui restart: replaced, pid 67513.
  • The window then showed workspace-1 again, so the app had reconnected.

Retry Now (pressed once, on the launch window, owner still stopped): nothing visible changed for 33 s. The buttons stayed enabled, there was no status line, and the owner stayed Ts. That fits firing retryWake while the owner can't answer: the new ensure is still pending. Restart stayed offered.

Found along the way (not from this PR)

  • A stuck owner on a later launch never shows the failure screen. If the daemon's launch snapshot exists, a relaunch draws the last layout (workspace-1 and its New Tab page) and nothing on screen says cmux-tui isn't connected, even 220 s in. Neither button is reachable unless you open the App Store. In practice, most stuck-owner launches will look like this, not like the screen above.
  • A stuck owner while the app is connected goes unnoticed. With the app connected and the App Store open, SIGSTOPping its owner produced no disconnected banner within 200 s.
  • The launch window's failure line is clipped on base and head alike: "…This is taking longer than 2 minutes;" loses "cmux keeps trying." (the banner shows the full text).

Not verified

  • The restart-refusal path (server.replace_*, "cmux-tui could not be restarted"), the Cancel button, and Copy Details content.
  • Restart pressed from the App Store banner. Only the launch window's Restart was pressed; the banner's buttons were seen but not pressed.
  • Whether terminal hosts are adopted across a restart: these runs had no open terminals.
  • Real keyboard and mouse input: the slot runs with CMUX_NEXT_NO_ACTIVATE, so presses went through AX and the debug socket.
  • 50786b8de5b725ed2517055c8e36d36ec5d37afd itself, which differs only in the logic noted above.

Cleanup: each run resumed or replaced the one pid it stopped, quit through quitEndSessions, removed its tag's state and released its slot lease. After release, nothing from these build tags was left running on cua-4.

teamleaderleo and others added 3 commits October 14, 2026 20:57
…rget (cx-jniyz.1)

CmuxNextApp went 156 lines over its size cap with the Restart cmux-tui and
Retry Now controls (SW14: new features go in their own target). The new
CmuxNextDaemonRecovery target holds DaemonConnectingView, the recovery
controls, the App Store banner's recovery actions and their 12 strings;
the app keeps the wiring. CmuxNextApp is now 113559 lines, 170 under the
base 113729, which also clears the base's +37 over the cap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 15, 2026 04:42 — with GitHub Actions Inactive
@blacksmith-sh

This comment has been minimized.

…niyz.1)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 15, 2026 04:50 — with GitHub Actions Inactive
… not a public namespace enum (cx-jniyz.1)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 15, 2026 04:57 — with GitHub Actions Inactive
…handoff/cx-jniyz.1-restart-cmux-tui

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextApp/Windows/WindowController.swift
#	Packages/macOS/CmuxNext/Sources/CmuxNextDaemonRecovery/DaemonConnectingView.swift
@blacksmith-sh

This comment has been minimized.

lawrencecchen and others added 10 commits October 18, 2026 04:44
…ip of tabbed columns

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, never pinned to the traffic lights while the camera scrolls

The window-controls inset was computed from the strip's presented window
frame on every frame of a strip scroll. A column sliding in from under
the sidebar overlapped the sidebar's toolbar band and traffic lights,
so its tabs were pushed right to clear them and stood still while the
column moved (Lawrence 2026-10-18: tabbar bad when moving left/right).

Now obstacles count only over the part of the strip its ancestors show,
and a strip inside a pane on a layout spring measures where that pane
rests (RestingPlacement, set by applyPresentation from the spring target
and the camera target), so its layout changes at most once when motion
starts and the tabs then move rigidly with the column.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…column scrolled in is never blank

A paused Ghostty surface releases its swap chain (renderer setVisible
false), so a column the strip camera scrolled in showed an empty
terminal for about five frames until it resumed and redrew. Terminal
keys whose pane is in the keep-alive band now render; browser pages
there (unretained, hibernation owns them) stay paused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and motion start for a strip recording

motion-record.py's strip scenarios now save their own start layout and
the key's timing next to the frames.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…onsensus where no text is shared; text extent from a resting frame

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g, so a column scrolled in is never blank"

The recording disproves the premise: on the base, a column the camera
scrolls in already shows its prompt on its first visible frame (right-1
frame 10). The "blank" frames in left-1 were frames where the prompt,
at the column's left edge, was still behind the strip's left clip.
Keeping band terminals drawing would only add GPU and energy cost.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nder the traffic lights

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… until it rests

With the sidebar hidden, the column leaving the traffic lights dropped
its inset on the first motion frame (its tabs jumped 75 pt while on
screen). On the way a strip now keeps the larger of the inset it shows
and the one it will rest with; the pane host tells the content when it
comes to rest, so the strip settles on its resting inset then, when the
column is off screen. strip-scroll-check skips pill slivers at the clip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lawrencecchen and others added 8 commits October 18, 2026 05:55
… list_reloads@file:line; the gate prints them per switch) (cx-8w027.17)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…update, no list reload (cx-8w027.17)

A workspace with a tile (SidebarNavigation selects the tile's top item) drew its
selection through the block and band content (infos[].isActive), so a switch to
or from it changed SidebarListBlocks and reloaded the whole list (20 row
configures) and laid out all three bands. The selection is no longer content:
SidebarRegionView.activeItem and SidebarListView.activeTopItem repaint only the
two items. Removes the gate's KNOWN lines for cx-8w027.17.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…val (cx-8w027.17)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ates (cx-8w027.17)

GUI run nx 1018-051837-6e261d at 4f0a297: 24 workspace and 6 theme switches, each 0 list reloads, 0 row configures, 0 band updates, 1 selection patch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@teamleaderleo
teamleaderleo merged commit aba459f into feat-cmux-next Oct 18, 2026
134 of 144 checks passed
@teamleaderleo
teamleaderleo deleted the cx-next-handoff/cx-jniyz.1-restart-cmux-tui branch October 18, 2026 13:44
teamleaderleo added a commit that referenced this pull request Oct 18, 2026
#20765 made the not-connected screen's status label selectable without
focusRingType = .none, so check:text-focus fails every feat-cmux-next PR.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 18, 2026
* cmux-next: keep the Mac awake while agents work (sessions.keepAwake)

New Settings > Agents > Agent Sessions row, "Keep Mac Awake While Agents
Work": Off, While Plugged In (default) or Always. While a local agent runs a
turn (an acpmux session, or a tab's agent hooks or OSC 7501 working record),
idle system sleep stays off. The display can still sleep and closing the lid
still sleeps the Mac.

- schemas/settings/source/sessions.json is the row's source; the Swift
  descriptor, parser constants and choice enum are generated.
- KeepAwakeService holds one ProcessInfo activity for every reason, so the
  Keep Mac Awake action and the setting never end each other's hold. The
  action now goes through it and still holds on battery.
- PowerSourceMonitor follows IOKit's power-source notification on the main
  run loop (no polling); unplugging releases a While Plugged In hold.
- Agents may not set the key (userOnly).
- Strings in all 21 locales of the settings catalog.

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

* cmux NEXT perf-hitch2: an evicted terminal shows its last frame at once when shown again (GhosttyNextKit a19c884fa, cx-8w027.19.1)

A terminal the warm set evicted showed an empty pane until its new session
attached and drew. Ghostty emptied every released drawable
(kIOSurfacePurgeableEmpty in IOSurface.deinit), and a hidden surface releases
its swap chain, so even the layer's presented frame was blank (ghostty-next
PR 42: a plain release; the last owner frees the pages).

- A paused surface marks its presented frame volatile and makes it
  non-volatile before it shows again; a purged one leaves the layer empty
  until Ghostty's next frame, as before.
- Eviction keeps the presented IOSurface (no copy, volatile), at most the warm
  set terminal capacity, oldest first; a frame dropped by the bound leaves a
  480 px hover picture; memory pressure drops them with no renders.
- The new session shows it over its first surface (TerminalFrameHold
  begin(lastFrame:)) until its first content is parsed and drawn, at most 3 s;
  a purged frame or a hidden mount shows nothing. Hover cards picture it on
  demand.
- debug.surfaces: last_frames outcomes, per pane last_frame_holds, frame_held.
  Proof: scripts/cmux-next/evicted-terminal-frame-live.py.

Pins GhosttyNextKit release xcframework-a19c884fa8d132378ae838dcba95654b177b6723-apple-v6
(checksum 94ff3178... from SHA256SUMS, matching the downloaded zip) and moves the
ghostty-next gitlink, package-notices.json and vt-link-graph.json to it (only
pkg/macos/iosurface/iosurface.zig changed since e6a27a1fb).

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

* cmux-next: regenerate settings exports and defaults for sessions.keepAwake

Exports from the fleet export step at 4beacc0 (run-swift-testing-suites.sh,
export-settings); defaults snapshot from defaults_guard.py snapshot.

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

* perf(cmux-next): a terminal mounted hidden after an eviction shows its last frame at its reveal (cx-8w027.19.1)

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

* perf(cmux-next): hover pictures reclaim a paused surface's volatile frame for the read; a frame in flight at the pause is not reclaimed; no last frame at capacity 0 (cx-8w027.19.1, review)

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

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

Exports from the fleet export step at 011a67c.

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

* restore-trace.py: trace a relaunch restore per pane with the snapshot sources that moved (cx-8w027.23)

* restore-trace.py: every pane in short, 50 ms reads (cx-8w027.23)

* perf(cmux-next): last-frame logic lives in CmuxNextTerminal (TerminalLastFrames evict/show/report); the frame shows at the reveal; CmuxNextApp does not grow (cx-8w027.19.1)

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

* cmux NEXT perf-restore: no pane re-run on connect, room badge from the strip's window, one topology per turn for every pane that changed (cx-8w027.23)

- PaneController.Snapshot.connected is gone: apply never read it, and the
  read re-ran every pane's snapshot when the daemon connected (16 per
  16-pane restore). A reconnect with a new daemon still re-maps through
  generation and surfaces.
- BrowserProfileService.roomDefault takes the room of the window the
  strip or sidebar avatar draws in (WindowState.profileID, observed), so a
  room's profile badge follows that window; the lookup of WindowManager
  state (not observed) stays only for one-time choices (a new tab's profile).
- A pane whose part of the focus topology changed marks the workspace
  (setNeedsTopology); FocusCoordinator builds and sends one topology per
  owner for the whole main-actor turn, at its end or before any state read
  or event (I4), instead of one per pane.

* cmux NEXT: no focus ring on the daemon recovery status label (#21679)

#20765 made the not-connected screen's status label selectable without
focusRingType = .none, so check:text-focus fails every feat-cmux-next PR.

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

* FocusCoordinator: state is a pure read; key routing and events reduce the pending topology; the turn-end reduction runs detached; what the settled observer queues runs (review of cx-8w027.23 slice 1)

* release-notes(next): fit browser highlights to the What's New limits (#21716)

* release-notes(next): fit browser highlights to the What's New limits

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

* release-notes(next): live links, dev-build wording for remote tabs

Replaces cmux.com/whats-new (404) with real try-it actions, says the
remote browser tab entries are development-build features (their action
is DEV/Nightly only), and drops 'instant' from the tab-switch entry.

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

---------

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

* release-notes(next): fit app highlights to the What's New limits (#21717)

* release-notes(next): fit app highlights to the What's New limits

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

* release-notes(next): live links and exact profile-menu wording

Replaces two 404 links (cmux.com/whats-new, docs/agent-integrations) with
real actions or docs pages, and says what the profile button opens.

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

---------

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

* feat(next): workspace name source and revision, like tab names (hq#1957) (#21648)

* feat(next): workspace name source and revision, like tab names (hq#1957)

Workspaces now record whether their name came from a person or from
auto-naming, the way tab names do (NameSource { User, Auto } and a
name_revision), and the daemon decides whether an automatic rename lands.

- RegistryWorkspace and the mux Workspace carry name_source and
  name_revision, stored in two additive workspaces columns (legacy rows
  read user/0). Both commit paths re-check the stored row's name, source
  and revision inside the transaction.
- rename-workspace and workspace.rename accept source, expected_generation,
  expected_name_revision and release. An automatic rename never replaces a
  person's name (operation.failed, reason_code name_pinned), never empties
  one (name_empty), and loses to any rename after the revision it read
  (revision.conflict). source auto with release hands a pinned name back.
- Workspace projections (raw tree, list-workspaces, resource extra) expose
  name_source and name_revision; the daemon advertises
  workspace-name-source-v1.
- CLI: cmux workspace rename --source auto --expected-generation G
  --expected-name-revision N, and --release-to-auto.
- Spec, regenerated SDK bindings, cmux-app.d.ts and resource descriptor
  hashes follow; optchat-chief's RenameWorkspaceRequest literal gains the
  new fields.
- The app's DaemonCapabilities moves workspace-name-source-v1 from
  unservedByBundledDaemon to optional, since the bundled daemon now serves
  it.

Carved from #20710 onto current feat-cmux-next.

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

* fix(next): project workspace name provenance like the journal states it (hq#1957)

feat-cmux-next tests that landed after #20710's base failed on this slice:

- batch_close and resource_effects: the journal decorates every stored
  workspace with extra.name_source/name_revision, but the projected
  workspace values had no extra, so the public fold never matched and every
  commit restated every workspace. Every workspace value builder (deltas,
  full and scoped projection, normalized registry deltas, resource
  snapshots) now carries the same extra.
- key_close_cannot_rebind_a_live_durable_workspace_identity: creating a
  workspace at a key bound to another live workspace hit the name fence
  (revision.conflict) before the identity check. The name fence now
  applies only to the same workspace (numeric id), and the resource store
  checks identity before the name.

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

* refactor(next): workspace_resource_upsert lives with workspace renames (hq#1957)

Its name provenance parameter pushed mux.rs past the 1000-line god-file
budget (check-no-godfiles.sh); the helper moves to mux/workspace_rename.rs
unchanged.

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

---------

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

* fix(cmux-next): restore #21369 and #21384, undone by the Chief Dock squash (#21690)

* test(cmux-next): older history is asked for once per page

Fails on feat-cmux-next: a streaming commit near the top asks for the same
older page again, because 50a0c60 (#21194) restored the effect #21369
had keyed on the first row.

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

* perf(cmux-next): avoid duplicate older-history loads (#21369)

(cherry picked from commit 8f6f9cb)

* perf(cmux-next): gate find highlight repaints by transcript changes (#21384)

* perf(cmux-next): gate find highlight repaints by transcript changes

* fix(cmux-next): preserve find repaint triggers after fold reveals

* style(acpmux): format find highlight effect

* test(next): carry apps empty-state selector through PR 21384

* test(next): carry apps empty-state selector through find highlight PR

(cherry picked from commit afec4de)

---------

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

* acpmux: no-ask start modes by inherit or an approved preset (#21666)

* feat(acpmux): start a no-ask mode from a session in it or an approved preset

A `session/new` that asks for a mode that doesn't ask, without the
person, now has two ways through:

- Inherit: a caller that is itself an acpmux session in that exact mode on
  the same harness family. Each harness gets ACPMUX_SESSION_TOKEN, an HMAC
  of its session id under a per-run in-memory secret, and
  ACPMUX_SESSION_TOKEN_SOCKET. The token is read only from a unix socket
  connection's first `initialize`. The caller's mode is read at request
  time.
- Approved preset: `_acpmux/presets {name, approveMode: true}` from the
  person records the sha256 of the preset's contents. The approval counts
  only while the resolve and the spawn both used those exact contents, for
  the preset's own mode, with no request overrides other than a folder.

A start mode never applies to an adopt without the person, and a proven
caller session that asks never gets the full-access default for its
chats. The refusal names where to approve (cmux NEXT on this host, or the
preset to approve there once), with {reason, host, mode, preset} in
error.data. Web and peer clients never get the host name.

person_auto_start.rs runs in the hermetic test environment, and its
person-key pipe is close-on-exec so parallel tests' daemons never hold
each other's key pipe open.

Carved from #20653.

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

* test(acpmux): an approved preset with a route, and env that names the caller token

Red on 116ab01:
- an approved preset plus a request `route` passes the start gate and
  reaches the handler ("no route"), where it should need the person;
- a harness profile env that sets ACPMUX_SESSION_TOKEN reaches the agent
  in place of acpmux's caller token.

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

* fix(acpmux): a route counts as an override, env never sets the caller token

Review fixes for the start gate:
- A request `route` on an approved preset now needs the person, like its
  own model, effort, policy or env: a route swaps the provider base URL
  and keys.
- A profile's, preset's or request's env no longer replaces
  ACPMUX_SESSION_TOKEN or ACPMUX_SESSION_TOKEN_SOCKET. That env is
  layered after `caller_env`, so the spawn drops both keys from it.
- The CLI's daemon readiness pipe is close-on-exec from creation
  (`std::io::pipe`, pipe2(O_CLOEXEC) where the OS has it) instead of a
  `pipe` then `fcntl`, which raced with a fork on another thread.

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

---------

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

* cmux-next: the static status look everywhere; indicators name their state (cx-sya4n) (#21664)

* test(cmux-next): the static look reaches tabs, the attention ring, the agent pane and every label (cx-sya4n)

Red: tabs keep their icon set's marks and colored dots, the attention
ring blinks, the agent pane's theme has no statusIndicators look and
indicators have no tooltip or VoiceOver label (compile failure).

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

* cmux-next: the static status look everywhere, and every indicator names its state (cx-sya4n)

Tabs draw the static look's still marks (the badge too, never a dot
told apart by color alone); the pane attention ring is the steady ring
under Static; the agent pane's theme sends statusIndicators (static
under Static or Reduce Motion). Every StatusIndicatorView's tooltip and
VoiceOver label is the state's short name (StatusIndicatorState
.shortLabel, nine strings in every catalog locale), and a tab's
needs-input VoiceOver text names the blocked kind. Carved from #20511.

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

---------

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

* fix(cmux-next): restore the URL chip hover card styles lost in the Chief Dock squash (#21699)

* test(cmux-next): every URL chip hover card class has a rule

Fails on feat-cmux-next: 50a0c60 (#21194) dropped the .cv-link-preview
rules #21199 added, while LinkChips.tsx still renders those classes.

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

* fix(cmux-next): restore the URL chip hover card styles (#21199)

50a0c60 (#21194, a stale-base squash) removed the nine .cv-link-preview
rules while LinkChips.tsx still renders them, so the card drew unstyled.
Restored byte for byte from 5106cc9.

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

---------

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

* acpmux: a saved model or effort pick reaches open New Tab pages (#21677)

* test(acpmux): a saved model or effort pick is announced to watchers

A New Tab prewarmed before a pick draws the old start effort and the chat
then starts on the pick. Fails: note_pick saves the new start default but
sends no _acpmux/harnesses_changed.

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

* fix(acpmux): announce a saved model or effort pick to watchers

note_pick makes the pick the harness's start in _acpmux/harnesses, but
told nobody, so a New Tab prewarmed before the pick kept drawing the old
start effort until the chat started on the pick. It now sends
_acpmux/harnesses_changed, as a saved mode pick already announces
start_mode_changed; pages re-read the catalog on it.

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

---------

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

* cmux-next: keep awake lives in its own target (CmuxNextKeepAwake)

Review of #21730: CmuxNextApp only shrinks (check-app-size.sh), so
KeepAwakePolicy, PowerSourceMonitor and KeepAwakeService move to a new
CmuxNextKeepAwake target with their tests. The App keeps a one-line start
and the Keep Mac Awake binding; its source is 10 lines under the base.

The power-source callback now hops to the main actor with a Task instead of
MainActor.assumeIsolated (a banned crash class).

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

* cmux-next: route test knows the keep-awake tests; one policy write per change

Review of #21730: a CmuxNextSidebar change now also selects
CmuxNextKeepAwakeTests (CmuxNextKeepAwake depends on CmuxNextBridge), so
test_a_shared_module_selects_its_dependents lists it. KeepAwakeService sets
the setting and the agents-working fact in one policy write, so the hold
cannot drop between the two.

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: safe-push <safe-push@cmux.local>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants