Skip to content

cmux NEXT: a pane's top-right is one hover "…" in place of its buttons - #20119

Merged
teamleaderleo merged 5 commits into
feat-cmux-nextfrom
op-next-toolbar/pane-hover-more
Oct 13, 2026
Merged

teamleaderleo merged 5 commits into
feat-cmux-nextfrom
op-next-toolbar/pane-hover-more

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Leo, 10-12: the pane's top-right buttons go away. Hovering the corner shows one "…", whose menu opens a browser, terminal, agent chat and so on.

  • The row of buttons (Split Right, Split Down, Open Diff Viewer, Open File, …) is gone. A pane's tab bar now ends in a single "…".
  • "…" fades in while the pointer is over the pane's top-right corner: the button and 96 pt to its left over the tab bar. It uses the shared HoverReveal that the strip's "+" uses, and stays up while its menu is open.
  • A zoomed pane keeps "…" shown and accent-lit, which replaces the old lit zoom button.
  • The menu holds the strip "+"'s rows for this pane (new terminal, browser, agent chat, …), a separator, then Split Right, Split Down, the tab's own actions and Toggle Pane Zoom. It opens on press (press-drag-release, from cmux NEXT: the pane toolbar's "…" menu opens on press (polish: press-drag-release) #20111).
  • A chat's pane still has no "…", because its header has its own tools.
  • The Hide/Show button menus go away along with the buttons. tabs.hiddenButtons no longer applies: hiding a button used to move it into "…", and every action is in "…" now. A follow-up PR removes the setting and its generated docs.

Visible change: panes show no buttons at the top right until you hover the corner, then one "…".

Verification

Tests: PaneToolbarTests. All runs are fleet run-swift-testing-suites.sh --arg Packages/macOS/CmuxNext (Xcode 26.6).

  • Red, 6812e49: these are the tests as first committed, with two seams (an init that takes newTabMenu, and reveal) that keep the old behavior. All 5 tests failed on assertions. For example: bar.controls → ["More", "Split Right", "Split Down", "Open Diff Viewer", "Open File"], bar.more → ["Toggle Pane Zoom"], and reveal.isRevealed → false for a zoomed pane.
  • Green, 759ec11: all suites passed.
  • After the review fixes: the tests changed (the hidden-setting test was removed and hidden: dropped from the calls), so the red is re-recorded on a scratch commit (the final tests on the old code) and green on the head. Both receipts are in a comment.
  • Capture: in the comment below (quiet, hover, menu open).

Pane corner hover …

Changelog

Changed: A pane's top-right shows one "…" on hover, instead of a row of buttons. Its menu opens a new terminal, browser or agent chat in the pane, splits it, and runs the tab's actions.

🤖 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

Replaces the pane's top-right row of buttons with a single "…" that fades in as the pointer crosses the pane's top-right corner.

  • The "…" appears while the pointer is over the corner — itself and 96 pt to its left over the tab bar — and while its menu is open; it uses the same HoverReveal as the strip's "+".
  • Its menu, opened on press, holds the strip "+"'s rows for the pane (new terminal, browser, agent chat), then Split Right, Split Down, the tab's own actions, and Toggle Pane Zoom.
  • A zoomed pane keeps "…" shown and accent-lit, replacing the old lit zoom button.
  • Chat panes still have no "…", because their header has its own tools.
  • The Hide/Show button menus are gone with the buttons, and tabs.hiddenButtons no longer applies; a follow-up removes the setting and its generated docs.
  • The corner reveals "…" on any enter/exit it owns, including synthetic ones like debug.pointer_hover that carry no tracking area.

Written for commit 8ef8b91. Summary will update on new commits.

View guided diff Turn on auto-fix

teamleaderleo and others added 4 commits October 12, 2026 19:47
… no split buttons (Leo 10-12)

Red: today the corner shows Split Right, Split Down and the tab's actions as
buttons, "…" holds only the overflow, and a zoomed pane lights a zoom button.
The seams (PaneToolbar init with newTabMenu, the view's HoverReveal) keep the
old behavior.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dropping Split Right/Down was meant for the composer's ellipsis, not the pane.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…buttons (Leo 10-12)

The row of buttons (Split Right, Split Down, the tab's actions) goes away.
One "…" fades in while the pointer is over the pane's top-right corner (the
shared HoverReveal, like the strip's plus) and while its menu is open; a
zoomed pane keeps it shown and lit. Its menu is the strip plus's (a terminal,
a browser, an agent chat in this pane), then Split Right, Split Down, the
tab's own actions and Toggle Pane Zoom. Hide/Show button menus go with the
buttons; an action in tabs.hiddenButtons is left out of the menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The hover reaches 96 pt left of "…" over the tab bar (the pane's corner),
  not only "…"'s own cell; the corner's tracking area lives on the pane.
- The tint follows theme changes (set in layout and appearance hooks).
- tabs.hiddenButtons no longer filters "…": it hid buttons from the old
  row, which put them under "…", so everything stays reachable.
- "…" builds the plus's rows without its Chromium warmup.

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

coderabbitai Bot commented Oct 13, 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: 9497f653-9c9a-44b8-9167-a850605fd85e

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 13, 2026 •

Copy link
Copy Markdown
Contributor

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

CI passes on 8ef8b91148 (run 38340624874 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.

A synthetic enter (debug.pointer_hover) carries no tracking area, so the
area check dropped it; the view owns no other area.

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

cursor Bot commented Oct 13, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Capture

Fleet DEV build of 8ef8b91 on a capture slot (showcase profile), right pane only.

Pane corner hover …

  • Quiet: no buttons at the pane's top right.
  • Hover: the pointer over the corner, 33 pt left of "…" on the tab bar (not on the button), fades "…" in. The strip's own "+" reveals too.
  • Press "…": New Terminal Tab, New Browser Tab, New Tab with Browser Profile…, New Agent Chat, New Tab Page | Split Right, Split Down, Open Diff Viewer, Open File…, Search Agent Chats, Toggle Pane Zoom.

The capture found a bug, fixed in 8ef8b91: a synthetic enter carries no tracking area, so the corner ignored it.

Receipts

All on fleet (Xcode 26.6).

  • Red (6812e49): tests as first committed. All 5 failed on assertions.
  • Re-recorded red: the final tests on the old code (scratch commit). All 4 PaneToolbarTests failed with 13 issues.
  • Green: 759ec11, then e9dcc0a, where BranchStateResourcesTests timed out once (a daemon test this PR doesn't touch; it passed on the other runs), then the head 8ef8b91 with all 243 suites passing.
  • Release compile (head): check-release-compile.sh, BUILD SUCCEEDED.
  • Review subagent: findings fixed in e9dcc0a: the corner reach, a theme-hook tint, hidden actions staying reachable, and no Chromium warmup.

@teamleaderleo
teamleaderleo merged commit dc2579c into feat-cmux-next Oct 13, 2026
87 checks passed
@teamleaderleo
teamleaderleo deleted the op-next-toolbar/pane-hover-more branch October 13, 2026 03:21
teamleaderleo added a commit that referenced this pull request Oct 13, 2026
…0115)

* test(optchat-chief): link previews on Chief replies (red, behavior)

The brain runs end to end against the fake conversation owner: a reply
line that is only a URL goes out as a link_preview part whose picture
is uploaded once as an attachment record; a failed fetch sends the URL
only; and the SSRF guard keeps every fetch off loopback, private and
link-local hosts, by literal, by name (localhost), by redirect and by
og:image, so none of those URLs reaches the transport.

Re-applied from the orphaned feat-cmux-next-chief-link-preview (bead
cx-ebm.47). The orphan's unit tests of the guard, metadata, picture and
line-split functions are not carried over (no-unit-tests rule,
2026-10-10); the guard is proven through the brain instead.

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

* feat(optchat-chief): link previews on Chief replies

A reply line that is only an http(s) URL becomes a link_preview part in
its place. The host fetches the page's title, site and image through
an SSRF guard (src/link_preview/guard.rs): http(s) on the default port,
no credentials, every resolved address public (no loopback, private,
CGNAT, link-local, ULA; mapped IPv6 as IPv4), the transport connects
only to the checked addresses (ureq resolver, so a rebinding answer is
never used), at most 5 redirects each re-checked and never https to
http, no proxy and no cookies, 512 KB of HTML (head only), 5 MB of
image, 8 s per preview. The picture becomes a JPEG of at most 512000
bytes uploaded as an attachment record (conversation-attachment-upload);
a preview that fails or is late leaves the URL only.
OPTCHAT_CHIEF_LINK_PREVIEWS=0 turns it off.

Cargo.lock (optchat-chief's own workspace): image and url, resolved on
a Testbox with cargo metadata. cmux-tui-rust-check.sh gets the
optchat-chief mode beside rb-testhost (short TMPDIR for its socket
tests).

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

* CmuxHomeCore, MessagesLab: the sender attaches link previews

A draft with URL-only lines waits for each card's fetch (the one
MessagesLab's send started; the local message already flies with the
grey card), then sends HomeStore parts in TextParts order: .text blocks
and .linkPreview(url, title, site, image). The picture (LinkPreviews'
file) becomes a JPEG of at most 1024 px and 512 KB uploaded as its own
image record with the message (HomeStore.prepareLinkPreviewImage,
send(conversation:parts:uploads:key:)); the owner checks its hash like
an attachment's. A failed fetch sends the URL only. LinkPreview.sendable
applies the owner's URL and label rules before the send.

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

* optchat-chief: rustfmt the link preview test and brain modules

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

* optchat-chief: op_entry starts without a preview hold

The tip's op_entry (added after the orphan branch) builds an
OutboxEntry by hand; it gets previews_until: None like reply_entry. The
turn-reply doc comment goes back above reply_entry.

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

* test(optchat-chief): link preview review, red behavior tests

Through the brain and the fake owner: a URL only the agent wrote is
never fetched (the reply goes as its text); an owner without
link-preview-v1 gets the plain text; a reply whose card the owner
refuses goes again as its original text under a new key; a picture
wider than 4096 px is not decoded; IPv4-translated (::ffff:0:0/96),
local-use NAT64 (64:ff9b:1::/48) and Teredo (2001::/32) addresses are
never requested. Picture uploads move to an uploader the brain hands
to the preview thread (FakeUploader in the harness).

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

* fix(optchat-chief): link preview review: provenance, capability, fallback, caps

- Only a URL a person wrote verbatim in one of the conversation's last
  200 messages (text or own link card) becomes a card; a URL only the
  agent wrote stays text and is never fetched (security policy from
  the review; tool-result URLs are not previewed yet, the stricter
  subset).
- Cards go only to an owner that advertises link-preview-v1; an owner
  that refuses them gets the reply's original text, unchanged, under
  the key '<key>:text' (OutboxEntry::plain_text). A reply is never
  dropped.
- Picture uploads run on the preview thread through an ImageUploader
  with its own connection bound as the chief's agent, never on the
  brain thread.
- The picture decoder takes at most 4096 px a side and 64 MB, scales
  from the one decoded image without copying it per try, and stops at
  the deadline (before the decode and before each encode try).
- The guard refuses ::ffff:0:0/96, 64:ff9b:1::/48 and 2001::/32, and a
  name lookup takes 3 s at most.

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

* CmuxHomeCore, MessagesLab, CmuxNext: link sends log first and check link-preview-v1

A draft with URL-only lines is logged at the press as its plain text
(HomeStore.send(...attaching:)): the row keeps its place in the send
queue and a quit before the previews arrive still sends the text. The
previews then replace the logged op (same key, same position) and the
pictures upload with it. HomeLinkSend attaches cards only when the
owner takes them (HomeSource.acceptsLinkPreviews: the local daemon
advertises link-preview-v1; the cloud and older daemons get plain
text). DaemonCapabilities lists link-preview-v1 as optional
(plans/cmux-next/daemon-capabilities.json updated by hand in the
encoder's format).

No unit tests (no-unit-tests rule); the real check is a tagged app
against an older daemon and a quit during a preview fetch.

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

* optchat-chief: keep the reply key for the preview thread's fallback

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

* test(optchat-chief): only an exact URL token a person wrote gets a card (red)

A shortened query or a shortened host of a person's URL gets no card
and no fetch; the exact URL inside a longer sentence (with trailing
punctuation or brackets) gets one; a URL in a person's link preview
title does not count.

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

* fix(optchat-chief): previews need an exact URL token a person wrote; the read moves off the brain thread

- A card's URL must equal a URL token of a person's text parts (split on
  whitespace, leading brackets and trailing punctuation stripped, the
  same ends as split.rs sole_url) or the URL of a person's own link
  card; a prefix of a person's URL (a shorter query or host) and a URL
  in a card's title do not count.
- The conversation read, the split, the fetches and the uploads run on
  the preview thread's own connection (PreviewPort: snapshot and
  upload_image); the reply waits in the outbox as its text and gets its
  cards when the thread answers, or goes as its text when the hold ends.
  The text fallback after a refusal applies only to an op with cards.
- At most two picture decodes at once (a slot waits only until the
  deadline); a DNS lookup is clamped to the request's deadline.

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

* cmux-next: Update Ready card: See all changes, stacked untruncated buttons; Updated card button on one line; DEV changelog reads cmux-next nightly notes (cx-7rone)

- Ready card: up to 3 lines, Restart to Update full width, a quiet full-width
  See all changes under it (opens What's New for the staged span through the
  new openUpdateChanges intent); the side-by-side Release Notes button
  truncated both labels below a 260 pt sidebar.
- Updated card: its button carries its own styles (the surface document does
  not load styles.css) and keeps one line; Share moves under it when narrow.
- Changelog page: a DEV build with no SUFeedURL listed the stable fallback's
  notes (none: 'No release notes yet'); it now lists the cmux-next nightly
  notes (files-next.cmux.com/nightly-next/notes), built from the same branch.
- Update harness: --notes also signs notes/index.json (the page lists only the
  index); --changelog-shots captures the cards at the 160 pt sidebar and the
  What's New page each button opens; debug.update_surfaces gains
  sidebar-width, open-changes and open-whats-new.

* cmux-next: cx-7rone review fixes: See all changes falls back to the web release notes without signed notes, DEV page selects the newest nightly, harness restores the sidebar width, long card labels end in an ellipsis, 3 changelog lines everywhere (cx-7rone)

* cmux-next: update harness: dev_changelog.py captures a plain DEV build's What's New page; changelog shots list the windows (cx-7rone)

* cmux-next: a restored What's New tab keeps its title and page: the changelog provider registers at launch (cx-warpr)

The provider registered only on the first open, so a What's New tab that the
session restored after a relaunch (or that Reopen Closed brought back in a new
process) had no provider: the strip showed the store tab's title (Home) and
the pane showed no page, and the next open selected that empty tab.
reopen_page_title.py: open, close, Cmd-Shift-T action, relaunch, captures.

* cmux-next: update-harness proof scripts: socket runs ask for focus, check each reply, refuse a live tag socket, stop when the first instance does not quit (cx-warpr review)

* cmux-next: closing a non-last window closes it like a browser window: it keeps its workspaces as one closed group that Cmd-Shift-T reopens whole (cx-gzh.9.3)

Chief decision 2026-10-12. Before, a non-last window moved its workspaces
into the most recent other window and its record went away, so the close
made no closed group and could not be undone. Now every normal window
closes the way the last one did: kept closed with its workspaces (their
terminals and agents keep running), so the window record goes open:false
and the daemon stores one `window` closed group (cx-gzh.9.1); Cmd-Shift-T
or History reopens the window with its workspaces. WindowRecord.closed
(projection key `closed`) keeps a closed window closed across a relaunch;
when every saved window is closed, the front one opens, as before. The
launch window never adopts a closed window. Incognito windows still close
for good.

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

* test(cmux-tui): a personal workspace keeps its own remote-localhost choice

Red for cx-d0d.41: set-personal-workspace takes remote_localhost
(null clears), list-personal carries it, identify advertises
personal-remote-localhost-v1.

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

* cmux-next: restored closed windows through WindowRegistry.setRestored (windows has a private setter) (cx-gzh.9.3)

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

* cmux-next: DEV changelog notes: a DEV bundle names the stable feed URL, so key on the channel, not on a missing SUFeedURL (cx-7rone)

* cmux-tui: socket behavior test, a closed workspace's agent-home folder follows its reopen and goes to trash-pending on delete (cx-gzh.9.2, red)

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

* cmux-tui + cmux-next: a reopened workspace keeps its agent-home folder; a deleted closed workspace's folder goes to the Trash (cx-gzh.9.2)

AgentHomeHistory left with the Swift closed trackers (cx-gzh.9); since then
a reopened folderless workspace lost its chat files (the folder is named by
the workspace key, and a reopen gets a new key) and deleted entries left
their folders behind. The daemon owns closed history and the agent-home
base (agent_start), so it moves the folders now (state/agent_home_moves.rs):

- closed.reopen: after the commit, `<base>/<closed key>` becomes
  `<base>/<reopened key>` (before the reply, so before the first chat).
- closed.delete: the folders of the deleted workspace members that no live
  workspace uses move to `<base>/.trash-pending/`.
- Never a symlink, never over an existing target, only safe keys; a failure
  is logged and the folder stays.
- The app sends `.trash-pending` to the macOS Trash (AgentHome
  .emptyTrashPending) after its own deletes and at launch (CLI deletes),
  never a hard delete.

No workspace key crosses the v2 resource API (private identity).

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

* cmux-tui-core: module order (rustfmt) (cx-gzh.9.2)

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

* cmux-tui: a personal workspace keeps its own remote-localhost choice

cx-d0d.41 (call 2026-10-12: the per-workspace override is personal
state). personal_workspaces gains remote_localhost (0/1, NULL follows
browser.remoteLocalhost), added in place to older registries.
set-personal-workspace takes remote_localhost (null clears); the row,
list-personal and personal.workspace.updated carry it; identify
advertises personal-remote-localhost-v1. Spec (commands.md,
sdk-schema.json) and the generated SDK layers follow.

set-personal-workspace now hands its command to cmd_profiles, and the
per-column updates are one loop, so server.rs and personal_mutations.rs
stay within the god-file budget.

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

* cmux-tui + cmux-next: closed windows and agent-home folders, review fixes (cx-gzh.9.3, cx-gzh.9.2)

- A deleted window group (History Remove, Clear History) releases its closed
  window: the workspaces move to the most recent open window, or the window
  opens again; no running workspace stays hidden.
- On a daemon without window records a non-last window merges into the
  heir window as before (no closed group would record it).
- At launch, when every window left after reconciling is closed, the most
  recent one opens (the app never starts with no window).
- A closed window becomes the most recent one (a Dock click or a relaunch
  brings back the last window closed).
- Reopen: when the reopened workspace already has its own agent-home folder,
  the old one goes to trash-pending instead of staying orphaned; the daemon
  moves nothing under a non-canonical base. Docs updated.

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

* optchat-chief: Cargo.lock for image and url on the current tip

Resolved on a Testbox with cargo metadata after merging the tip (the
merge took the tip's lockfile, which lacks the link preview crates).

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

* cmux-next: crash ratchet: weak services in ClosedHistoryRetention, checked age math in ClosedRetention, link-filter cwd pushed instead of assumeIsolated (cx-zknal)

- CmuxNextApp unowned (0f2bf3e): services is weak; a gone AppServices stops the checks.
- CmuxNextDaemon int_conversion (0f2bf3e): UInt64(clamping:) and an overflow-checked
  multiply; an age too large for milliseconds keeps everything.
- CmuxNextTerminal assume_isolated (6780396): the surface view pushes the shell's
  folder to its link filter on each pwd report and surface swap, so Ghostty's callback no
  longer hops onto the main actor.

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

* cmux-next: namespace enums become extensions on their owning types (cx-zknal)

lint-ios-package-conventions flagged three all-static enums:
- CommandRetentionSetting (2d66749) -> CmuxConfigSnapshot.commandRetention* and
  parseCommandRetention, beside parseTerminalCommandHistory.
- AgentPageMoveCover (8698d64, #20017) -> AgentPaneView.hideMovedPage(until:) and
  revealMovedPage().
- AgentPaneCrashNoticeDebug (4069030, #20062) -> AgentPaneView.debugCrashNoticeTexts and
  debugPressCrashNoticeReload() (DEBUG only).
Callers updated; behavior unchanged.

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

* cmux-next: ClosedHistoryRetention check reads weak services inline (no line growth in CmuxNextApp) (cx-zknal)

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

* cmux-next: the window record mirror, the reopen wait and the agent-home trash sweep move out of CmuxNextApp (CmuxNextDaemon, CmuxNextAgentPane) so the App target does not grow (cx-gzh.9.3, cx-gzh.9.2)

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

* cmux-next: Show Agent History on the palette and the Window menu (cx-qno.24)

Lawrence 2026-10-11 (via the chief): the agent History page (cx-zlnl: every
coding agent chat on this device, with select and Bring into Active
Sessions) opens from a palette and Window-menu action, agentHistory.show,
not from the sidebar's space bar (its dot went in cx-qno.23). The page
opens beside the targeted or focused pane, as the dot did. CLI: GUI-only
for now (reachable as cmux action run agentHistory.show); a named verb
needs the Rust grammar check, follow-up filed.

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

* cx-urd.26: cmux-tui artifacts runs check their key's owner once instead of waiting (#20109)

* test: a cmux-tui artifacts run checks its key's owner once instead of waiting (cx-urd.26)

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

* ci: cmux-tui artifacts runs check their key's owner once instead of waiting (cx-urd.26)

tree-owner-wait polled a live owner every minute for up to 55 minutes:
21,801 runner minutes on 2026-10-12, and 221 of 765 waits built the key
anyway after a chain of failed owners. A run now looks once: a published
tree or a live owner means it builds nothing, an owner that ended is taken
over by winning the key's claim, and an unpublished key is built by the
next push of it or cmux-tui-tree-request.yml. The job's timeout drops from
60 to 5 minutes.

Since a feat-cmux-next push no longer waits for a branch run that owns its
key, a publishing run on any branch asks for the nightly-next promotion of
the feat-cmux-next head that shares its key, from that head's checkout.

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

* test: a failed owner hands its key on, and a pin push skips a published tree (cx-urd.26)

Review of #20109: a run that defers to a live owner must be re-run when
that owner ends unpublished, an unreadable owner status or a stale claim
must not strand the key, and a cmux-tui-pin-* push of a tree whose macOS
and Windows daemons are published builds nothing.

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

* ci: hand a failed owner's key to a deferred run, skip duplicate pin builds (cx-urd.26)

A run that defers to its key's live owner leaves a one-day
cmux-tui-tree-deferred-<key> artifact. An owner that ends without
publishing re-runs one deferring run (a feat-cmux-next push first), which
wins the claim and builds; each marker is used once. The claim now decides
whenever the tree is unpublished, so an unreadable owner status or a claim
that still names the ended owner no longer leaves the key unbuilt. The
owner check's timeout is 10 minutes.

A publishing run on another branch asks nightly-next to promote the newest
of the last 40 feat-cmux-next commits with its key, not only the head.

A cmux-tui-pin-* push whose tree is published with its Windows daemon
builds nothing (222 pin builds, 25,215 minutes on 2026-10-12). A pin with
a missing Windows daemon, and any dispatch, still builds.

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

* test: a pin push of a new commit with a published tree still publishes its binaries (cx-urd.26)

test_pin_push_keeps_commit_artifacts_enabled went red on c1402b0: a
pin push also publishes its commit's binaries for cmux-tui.pin.

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

* ci: a pin push skips only when its commit's binaries are published too (cx-urd.26)

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

* test: a live owner is not claimed past, and a failed re-run keeps its marker (cx-urd.26)

Second review of #20109. Red on 29319a5: 4 checks.

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

* ci: hand off after the owner's last job, never claim past a live owner (cx-urd.26)

notify-unpublished-tree now needs the macOS cross jobs, so the run it
re-runs finds the owner ended. A live owner means defer without asking
the claim, which could grant a second builder beside an owner the run-list
fallback elected. A claim that still names an ended owner is asked again
once before taking over, and a marker is deleted only after its run was
re-run.

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

* ci: run the cmux-tui owner and pin guards in the group that runs for this workflow (cx-urd.26)

The release-tooling group did not run for a cmux-tui-artifacts.yml change,
so both steps were skipped on #20109.

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

---------

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

* cmux-next: shorter docs for the closed-window paths (CmuxNextApp size cap) (cx-gzh.9.3)

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

* CmuxNext: plain text fields draw the theme's placeholder, text and caret (theme sweep 3, batch G) (#20108)

* test: plain SwiftUI fields and first-frame fallbacks draw the theme

Theme sweep 3, batch G, failing first: the new-task field, the app store
search, the feed inbox filter, the feed reply field and an app scene's
text field show their placeholder in macOS's gray and their text in the
appearance's ink under a purple room. The cookie card's link starts as
secondaryLabelColor and an unreadable session color is systemGray.

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

* CmuxNext: plain SwiftUI fields draw the theme's text, caret and placeholder

The new-task field, the app store search, the feed inbox filter, the
feed reply field and app scene text fields were stock SwiftUI .plain
fields: macOS's gray placeholder and the appearance's text and caret in
every theme. They are now ThemedField without its box (boxed: false),
which keeps the caller's own row, capsule or fill. ThemedField gains an
isFocused binding (New Task focus, Escape lets go) and onCancel (Escape).

The cookie card's link and an unreadable session color start in the
app theme's tertiary ink instead of secondaryLabelColor and systemGray.

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

* test: a page's first-frame separator follows the border switch

History's and Bookmarks' colors start with the theme's separator even
under appearance.borders none, where Palette.separator is clear. Also
drops the badge's always-true NSVisualEffectView check.

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

* CmuxNext: History and Bookmarks first-frame separator follows the border switch

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

* test: a themed field with text keeps its placeholder as its label

The stock SwiftUI field exposed its title as the accessibility label;
ThemedField only draws a themed placeholder, so once text is typed
VoiceOver has no name for the field.

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

* CmuxNext: a themed field's placeholder is its accessibility label

Kept in sync when the placeholder changes, so VoiceOver names the field
after text is typed, as the stock SwiftUI field's title did.

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

---------

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

* Name an older Cloud machine before Move to Cloud or Bring Back Local (#20110)

* cx-rppd.13: red tests for naming an older Cloud machine before a move

A Linux journey (machine_update_journey.rs) stands one cmux-tui binary in
for an older Cloud machine daemon through two debug-only seams (another
build id, newer capabilities left out of identify) and expects the link
CLIs that Move to Cloud, Bring Back Local and local access run to answer
machine_outdated before anything moves, with the chat still live on the
Mac. A same-build machine without a verb keeps its own refusal.

The Swift suites expect the app to name an older machine in plain words
(new catalog strings in every locale) and keep the chat where it was, with
Retry on that machine.

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

* cx-rppd.13: name an older Cloud machine before a move instead of failing

The link CLIs ask the machine daemon's identify first. A daemon of another
build that lacks the capability the verb needs (fs-v1 for sync and folder
grants, agent-transfer-v1 on either side of a move, access-request-v1 for
file request triage and answers) answers machine_outdated before anything
is copied or the chat freezes. A same-build daemon, or one whose identify
does not answer, keeps the verb's own refusal: the gate only names a cause
and never decides access.

The app maps machine_outdated to "<machine> runs an older cmux that can't
take this chat. Update the machine, then Retry." on Move to Cloud and the
matching Bring Back Local text, with Retry pinned to that machine.

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

* cx-rppd.13: word the older-machine toasts for an update, not a retry

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

* cx-rppd.13: test that a revoke and this Mac's own daemon are never the machine's fault

A revoke on an older machine must still end the grants on this Mac, and
a chat move whose source daemon is the outdated one must say which side
it is, so the app never asks to update the machine for this Mac's own
daemon.

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

* cx-rppd.13: never gate a revoke, name the outdated side and bound identify

`link access revoke` is no longer gated: it always ends the grants on
this Mac, as before the gate. `link agent move` answers which daemon is
outdated (`side`), and the app names the machine only when it is the
machine. The gate's `identify` now has one 5 second budget for the whole
answer, however a daemon trickles it. The French and Chinese strings use
the catalog's usual word for a chat.

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

* cx-rppd.13: word an outdated move side without blaming the machine

When the outdated daemon of a chat move is this Mac's own, the CLI's
error no longer says "update the machine": it names the side and the
build, and the app shows it as a plain move failure.

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

---------

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

* test(next): name why a PinnedPage pin never lands (#20123)

The pin wait's timeout now says whether Pin Tab refused, started no
daemon work, or the daemon failed it. The fleet report so far shows
the store tab still unpinned with a pane controller present.

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

* cmux-next: release terminals when their workspace closes (#20124)

* bench: pick the launched app's socket by identify pid; end the app before its daemons

lsof reports the app's bind path, not the socket name it is reached by,
so launch mode never found a socket. A live app respawns daemons ended
before it, which left two behind after a failed launch.

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

* bench: end the run through its cleanup on SIGTERM

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

* bench: --config passes cmux.json settings to a launched app

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

* bench: --mix none churns bare workspaces

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

* test: a closed workspace's terminal and preview go (cx-x15rz)

Red: closing a workspace only hides its terminals; the surface stays in
the warm set and the preview in the cache.

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

* cmux-next: release terminals whose workspace closed (cx-x15rz)

A terminal goes once its daemon tree, connected and loaded, has listed
its tab and no longer does, as agent pages already do. Before, a closed
workspace's terminals stayed in the warm set until the LRU took them and
their previews stayed in the 32 MB preview cache.

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

* bench: --fake-agent runs the chats on an ACP test agent whose turns end

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

* bench: clean up the launched app when the fake agent can't be added

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

---------

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

* Agent: New Agent Chat and Add Harness from Home open a new workspace (cx-bun) (#20118)

* test: New Agent Chat and Add Harness from Home open a new workspace (cx-bun)

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

* Agent: New Agent Chat and Add Harness from Home open a new workspace with the chat (cx-bun)

Over a top page the window's workspace is parked and its panes never
mount, so withAgentPane waited 10 s and refused. It now opens a workspace
whose only tab is the chat (the Harnesses page for Add Harness), as Cmd-I
does from a workspace. withAgentPane moves to AgentHandlers+Pane.swift.

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

* Agent: refuse at once over a top page when no chat can open; fix the missing import; Home shown as the app shows it in the test (cx-bun)

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

---------

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

* cmux-next: storm bench runs again; three main-thread lookups stop stalling under a CLI storm (cx-6so.21) (#20130)

* cmux-next: storm bench finds its stream pane from the split's reply (cx-6so.21)

An automation split-right no longer moves focus, so the bench waited for a
focused stream pane that never came and exited before any storm. It now
takes the split's created tab, and focuses left only when focus moved.

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

* cmux-next: a daemon capability check is one native-string set lookup (cx-6so.21)

ThemeCoordinator asks supports() per tab per snapshot and DaemonCompatibility
asks it per known capability; each scanned the decoded capability strings,
which held the main thread in CoreFoundation and allocator locks during a CLI
storm (2.3k of 6.6k lock samples on main). Keep a Set of native strings built
when the capabilities are set.

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

* cmux-next: isChat reads the tab it is given (cx-6so.21)

isAgentTab(tab.id) found the same tab again by walking every workspace, once
per tab whenever a pane's tab bar kind was computed: quadratic in tabs during
a CLI storm (618 main-thread lock samples under locateTab).

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

* cmux-next: focus topology looks tabs up by id once per pane (cx-6so.21)

focusPane and tabsInLayoutOrder searched the pane's tabs linearly for each
strip tab, an observation-tracked read per step: quadratic in a pane's tabs,
the top main-thread lock site left in the CLI storm profile.

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

---------

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

* cmux NEXT: a pane's top-right is one hover "…" in place of its buttons (#20119)

* test(cmux-next): a pane's top-right is one hover "…" that opens tabs, no split buttons (Leo 10-12)

Red: today the corner shows Split Right, Split Down and the tab's actions as
buttons, "…" holds only the overflow, and a zoomed pane lights a zoom button.
The seams (PaneToolbar init with newTabMenu, the view's HoverReveal) keep the
old behavior.

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

* test(cmux-next): splits stay in the pane's "…" menu (Leo's correction)

Dropping Split Right/Down was meant for the composer's ellipsis, not the pane.

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

* feat(cmux-next): a pane's top-right is one hover "…" in place of its buttons (Leo 10-12)

The row of buttons (Split Right, Split Down, the tab's actions) goes away.
One "…" fades in while the pointer is over the pane's top-right corner (the
shared HoverReveal, like the strip's plus) and while its menu is open; a
zoomed pane keeps it shown and lit. Its menu is the strip plus's (a terminal,
a browser, an agent chat in this pane), then Split Right, Split Down, the
tab's own actions and Toggle Pane Zoom. Hide/Show button menus go with the
buttons; an action in tabs.hiddenButtons is left out of the menu.

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

* fix(cmux-next): pane corner "…" review fixes

- The hover reaches 96 pt left of "…" over the tab bar (the pane's corner),
  not only "…"'s own cell; the corner's tracking area lives on the pane.
- The tint follows theme changes (set in layout and appearance hooks).
- tabs.hiddenButtons no longer filters "…": it hid buttons from the old
  row, which put them under "…", so everything stays reachable.
- "…" builds the plus's rows without its Chromium warmup.

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

* fix(cmux-next): the corner reveals "…" for any enter/exit it owns

A synthetic enter (debug.pointer_hover) carries no tracking area, so the
area check dropped it; the view owns no other area.

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

---------

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

* cmux-tui: bookmark icon doc avoids an HTML tag in rustdoc (#20129)

The bookmark-icon-v1 description's image:sha256-<hex> became an
unclosed <hex> HTML tag in the generated Rust SDK docs, which fails
`rust package` (rustdoc -D warnings) on every PR since #20083. The
placeholder is {hex}; the SDK layers are regenerated.

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

* acpmux: Claude's model choices come from the bundled catalog (cx-miuo) (#20091)

* test: a Claude Code session offers every catalog Claude model (cx-miuo)

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

* acpmux: Claude's model choices come from the bundled catalog (cx-miuo)

claude_stdio's hand list (no Opus 5.5 or Sonnet 5.5) fed the session's
configOptions model select, the pane's stand-in list before Claude Code's
own probe answers, and model-id hints. Claude Code's aliases stay static
(only Claude Code says what they run); every concrete release now comes
from catalog/models-v1.json.

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

* test: keep the catalog model-choices test with the catalog RPC tests (god-file budget)

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 (1M context) <noreply@anthropic.com>
Co-authored-by: safe-push <safe-push@cmux.local>
teamleaderleo added a commit that referenced this pull request Oct 13, 2026
Nothing has read it since the hover "…" replaced the pane's buttons (#20119). An old file
loads without a diagnostic and settings.set says it was removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 13, 2026
…on (#20144)

* test(settings): tabs.hiddenButtons is a retired key; an old list loads without a diagnostic

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

* settings: retire tabs.hiddenButtons; the pane's "…" lists every action

Nothing has read it since the hover "…" replaced the pane's buttons (#20119). An old file
loads without a diagnostic and settings.set says it was removed.

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

* test(pane toolbar): the New Tab page's "…" leaves the splits out (#20128)

#20128 dropped Split Right/Down from a New Tab page pane's "…" and left this expectation red on base.

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

* settings: regenerate the exports without tabs.hiddenButtons

Fleet export-settings-artifacts.sh at 324afbe, then strings:settings.

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

* settings: regenerate the exports on the merged head

Fleet export-settings-artifacts.sh at 0635e3f, then strings:settings. Also drops base's stale
tabs.newTabKind rows (retired in Swift by #20127).

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

* test(pane toolbar): follow base again; #20143 kept the splits in the New Tab page's "…"

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant