Skip to content

cmux-next: background and Chief picture settings (video playback, battery, keep clear of panes, picture motion) - #21746

Closed
teamleaderleo wants to merge 57 commits into
feat-cmux-nextfrom
leo/next-background-settings
Closed

teamleaderleo wants to merge 57 commits into
feat-cmux-nextfrom
leo/next-background-settings

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Phase 5 of the backgrounds plan: the settings Leo approved (through the coordinator). It's one schema PR, so the Air Blue coordinator clears WINDOW once.

Stacked: this branch is next-batch/leo-backgrounds (feat-cmux-next plus #21743, which carries #21709 and #21731, plus #21720 and #21740/#21737) with two commits on top. Review only fb08d69 (background video and framing) and 9d8f72c (Chief picture motion). It needs #21709's framing and #21720's video players.

What changes

Key Choices Default
appearance.backgroundVideo Play / Only While cmux Is Active / Still Picture Play
appearance.backgroundVideoOnBattery Keep Playing / Pause Keep Playing (Low Power Mode already pauses)
appearance.backgroundAvoidPanes on/off on
home.chiefPictureMotion Always / On Hover / Never On Hover (Never under Reduce Motion)
  • Rows sit under Appearance > Window Background (BackdropPlaybackSettingsSchema). They're agent-settable like appearance.background.
  • Parsing: BackdropPlaybackConfigParser uses ConfigFieldReader, so a bad value keeps the default and adds a diagnostic. SettingsApplier copies the result into DesignSettings.backdropPlayback.
  • Video (BackdropVideoView):
    • Still Picture never plays. It counts as unseen, so its decoder is given back after the phase 2 minute and the first frame stays.
    • Only While cmux Is Active follows NSApp.isActive (didBecome/ResignActive).
    • Pause on battery follows BackdropPower, a small IOKit power-source watcher on the main run loop.
    • Settings changes apply live through ObservationStream.
  • Framing (WindowMaterialView): with Keep Paintings Clear of Panes off, art frames itself in the whole window. The toggle applies live.
  • Chief pictures (home.chiefPictureMotion, a Home > Chief Pictures row, agent-settable):
    • Pages get the motion as data-picture-motion on <html>, carried with the theme (WebTheme.pictureMotion), the same way data-status-indicators is. ChiefPictureMotion.effective makes it Never under Reduce Motion.
    • The web Avatar shows a GIF or animated WebP as its first frame, drawn once to a still. It moves only when the setting is Always, or while the pointer is over it when it's On Hover.
    • The native Home header shows the first frame unless the setting is Always. It follows the setting and Reduce Motion live.
    • The sidebar tile is always a still square copy (squared() now covers animated pictures too), so a non-square GIF is no longer stretched there. The tile is a 20 pt icon, where motion is only noise.
    • Native hover animation in the header isn't wired: the header view is vendored MessagesLab, so On Hover shows the still there.
  • CmuxNextApp: 4 source lines down against base (no App changes in this commit).

WINDOW (settings schema)

Settings schema: 4 new keys. Air Blue coordinator, please clear.

  • Exports not hand-edited: schemas/settings/settings-schema.json, the MDM docs and the defaults snapshot are regenerated by the base-autofix bot (regenerate-settings-exports.sh), per that script.
  • Not in this PR:
    • built-in video ids in appearance.background, because there are no built-in videos yet; the value grammar change waits for the first one
    • the "Adjust Background…" catalog action, which needs an App handler, and the App is at its cap; phase 6's Settings button covers it for now

Strings

14 new keys, all 21 locales, in CmuxNextSettings/Localizable.xcstrings. It reuses the existing settings.group.windowBackground and settings.choice.always, onHover and never.

Tests

  • CmuxNextSettingsTests/BackdropPlaybackSettingTests:
    • defaults
    • every value parses
    • bad values keep the default and add a diagnostic
    • the rows offer every choice with the right defaults, and agents may set them
  • CmuxNextAppTests/VideoWallpaperTests.thePlaybackSettingsHoldTheVideo:
    • Still holds the video and gives back the decoder
    • Only While Active follows the app
    • Pause follows battery and power
  • CmuxNextAppTests/BackdropArtFramingTests: with avoidPanes off, the art frames to the whole window, and back.
  • chiefPictureMotionParsesAndHasItsRow: parse, a bad value, the row and agent-settable.
  • ChiefPictureMotionTests:
    • first-frame stills for an animated picture
    • a still picture is left untouched
    • Reduce Motion makes it never move
  • ChiefPictureFilesTests.anAnimatedPictureGetsAStillTile: square and non-square GIFs both get a still square PNG.
  • Web test/home-chief-picture-motion.test.tsx (3/3 pass): always, never, hover in and out, and a PNG is never held back.

Verification

Changelog

Added: Settings for video backgrounds (play, only while cmux is active, or a still picture; pause on battery), for keeping paintings clear of the panes, and for when animated Chief pictures move.

🤖 Generated with Claude Code

teamleaderleo and others added 20 commits October 18, 2026 14:55
…t chat

Every wide painting framed the same fixed point (0.5, 0.45), so with the sidebar
and a docked agent chat open the subject often sat under them, and wide or short
windows cut faces off. Each work now names its subject box in art/manifest.json,
and the art zooms, slides or shrinks (over a blurred fill) so that box lies in the
part of the window the columns leave visible.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…out, not the App

CmuxNextApp may not grow (check-app-size). The sidebar and each workspace's
layout root now tell the window's backdrop where they cover the art
(WindowMaterialView.setCover), and the App is unchanged.

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

The first frame now fades in over the poster instead of cutting. A video no
one can see (hidden, minimized, covered, screens asleep) for a minute drops
its items, freeing the decoder and frame buffers, and resumes at the frame it
held when seen again. A video held for Low Power Mode or Reduce Motion is
still on screen and keeps its frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings > Background gets Add from a Link (https only, judged by its bytes,
50 MB image / 500 MB video caps), a remove button on each of your images (not
the one in use), what they take on disk, and Show in Finder. A picked, dropped
or linked file must decode or play before it is copied in. Dropping a file on
Settings no longer needs experimental appearance (the page already said to).
The ops live in BackdropImagesPageProvider (CmuxNextPages); CmuxNextApp shrinks.

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

Chief sheet: a still is placed in the circle (drag, zoom) and drawn at 512 px,
so a photo of any size fits the daemon's 1 MB; a GIF goes as it is, keeping its
motion; Use Default Picture clears it (chief.profile.avatar.set clear).
Chief Settings (the default Chief): Choose Picture… and Use Default Picture;
GIF and WebP pictures now show in the header and tile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r edge correctly

Review fixes for the subject crop:
- keepsSubject is measured on the final placement, never assumed.
- A shrunk or slid image keeps the subject in the span first (the cover clamp
  overrode it), and the shrink fit counts the subject's height too.
- No vertical fallback that cannot fail; portrait subjects are the head and
  upper body (top 70%%), which fit a 21:9 window.
- The catalog test asserts the subject frame lies in the visible span.
- Only a column that comes or goes springs the art; resizes and live window
  resizes move it at once. Fill filters are made per view.

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

Review fixes: the seek after a decoder release waits for the new item to be
ready (a seek sent before then can be dropped or undone by the looper) and uses
zero tolerance both ways, not the next keyframe. The players take their clock,
so the release deadline test steps a manual clock; the restore test checks the
time it came back at.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eir name in their folder

Review fixes for Add from a Link:
- The link's name is sanitized (no folders, colons or leading dots), and the
  renamed file must stay in the download's own folder (an encoded ../ climbed out).
- A delegate-driven data task replaces download(from:delegate:), whose task
  delegate never saw progress: the declared length and the running count are
  checked against the cap for the declared type, so a huge body never lands.
- Redirects off https are refused; timeouts are a minute idle, ten in all.
- The temporary folder is removed on every failure.
- Remove refuses the background in use natively, not just on the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-ups to the Chief picture placement:
- A still's bytes and pixels (at most 50 megapixels) are checked before its
  preview decodes, and the drawing decodes it already scaled to the square.
- A GIF is capped at 1 MB on both sides (the daemon's cap), not 5 MB natively.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings > Background > Adjust Placement… fades the window's content and puts
an overlay over it: drag moves the picture, scroll or pinch zooms (1x-4x),
Reset goes back to the automatic subject framing, Done or Escape ends. The
placement (the point at the middle of the visible span, and the zoom) is kept
per background in background-placement.json, and every window follows it.
It starts from the framing on screen, so the first drag does not jump.
CmuxNextApp is unchanged: the op rides BackdropImagesPageProvider.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…me test holds still

Re-review fixes: the entry is stored before the readiness observation, so an
item already ready (reported inline) seeks and clears its pending restore
instead of leaving it set. The resume test restores while held in Low Power,
so a restart from 0 cannot reach the mark by playing; it checks 0.95-1.05 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re they start

Re-review fixes: LinkDownload keeps its state in a Mutex and is plainly
Sendable (no new unchecked conformance for the crash ratchet). A cancel that
comes before the task exists marks the download, and the task is cancelled as
soon as it is made, instead of invalidating the session first (making a task
on an invalidated session raises). A body that starts like a picture gets the
picture cap whatever type the server declared.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eep clear of panes)

appearance.backgroundVideo = play | whenActive | still (play),
appearance.backgroundVideoOnBattery = play | pause (play) and
appearance.backgroundAvoidPanes (on): Settings rows under Window Background,
agent-settable, parsed into DesignSettings.backdropPlayback by the applier.
Still Picture never plays and gives its decoder back; Only While cmux Is
Active follows the app; Pause watches the power source (IOKit). Turning
Keep Paintings Clear of Panes off frames art in the whole window.

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

coderabbitai Bot commented Oct 18, 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: 66ee39f6-1ede-429f-9916-482380479068

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.

home.chiefPictureMotion = always | hover | never (hover), a Home row and
agent-settable; Reduce Motion always holds pictures still. Pages get it as
data-picture-motion on <html> with the theme: a GIF or animated WebP avatar
shows its drawn first frame, and moves only always or while pointed at. The
native header shows the first frame unless Always. The sidebar tile is always
a still square copy, so a non-square GIF is no longer stretched there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo teamleaderleo changed the title cmux-next: background video and framing settings (play, on battery, keep clear of panes) cmux-next: background and Chief picture settings (video playback, battery, keep clear of panes, picture motion) Oct 18, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

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

@blacksmith-sh

This comment has been minimized.

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review fd58df3: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr21746 -f ref=leo/next-background-settings -f platform=macos -f backend_mode=local

Pending: CI is running on fd58df321f (run); the result below was for a3c55b67ae and no longer applies.

Last result: Unclear: web / react-apps-check failed: no known signature; failed step: Check webview test shards.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); it is rewritten when this head's CI completes.

red: cmux-next CmuxNextApp size cap; NEW: green on the base 2d57394; do not merge until green; see lane-rules
red: cmux-next checks (god files, concurrency, crash safety, l10n); base red: the same failure on the base 2d57394; does not block
red: web / react-apps-check (3); no finished base run on 2d57394 to compare; do not merge until green; see lane-rules
red: web / react-apps-check (4); no finished base run on 2d57394 to compare; do not merge until green; see lane-rules

CI fast guards failed on a3c55b67ae (https://github.com/manaflow-ai/cmux/actions/runs/39366174543). 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.

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Unclear: web / react-apps-check (1) failed: no known signature; failed step: Test webviews.

CI failed on 46884005a6 (run 39195821094 attempt 1): 3 unknown.
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped.

Job Verdict Why
web / react-apps-check (1) unknown no known signature; failed step: Test webviews
web / react-apps-check (2) unknown no known signature; failed step: Test webviews
web / react-apps-check unknown no known signature; failed step: Check webview test shards

Not re-run automatically: web / react-apps-check (1), web / react-apps-check (2), web / react-apps-check are not machine failures.

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.

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

CI fast guards failed on 46884005a6 (https://github.com/manaflow-ai/cmux/actions/runs/39195821094). 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.

teamleaderleo and others added 2 commits October 18, 2026 21:24
…ace window, saved in order

Review fixes for Adjust Placement:
- Compile: the store notification's object is compared by identity (the batch
  Swift run stopped on 'sending store risks causing data races').
- Saves run on one serial queue, in order; an unreadable file is kept beside.
- The overlay opens in the workspace window (WindowManager.active), never the
  Settings window; one at a time app-wide; it ends when its window shows
  another background, and fades views added while it is open.
- Keyboard: arrows nudge (Shift: further), + and - zoom, 0 resets.
- A drag past an edge has no dead travel; windows only re-place for their own
  background's change; removing a picture drops its placement; the page's
  'no picture' note clears when the background changes.
- init(coder:) returns nil instead of fatalError (crash ratchet).

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

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextDesign/BackdropArtPlacement.swift
#	Packages/macOS/CmuxNext/Sources/CmuxNextDesign/WindowMaterialView.swift
#	Packages/macOS/CmuxNext/Tests/CmuxNextDesignTests/BackdropArtPlacementTests.swift
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 19, 2026 03:37 — with GitHub Actions Inactive
…ckground-settings

# Conflicts:
#	schemas/settings-export/settings-schema.sources.sha256
#	schemas/settings/settings-schema.json
@cursor

cursor Bot commented Oct 19, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

From fleet export run (step 9fa138a873ed50b32dad9558).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 19, 2026 03:40 — with GitHub Actions Inactive
teamleaderleo added a commit that referenced this pull request Oct 19, 2026
* test(webviews): strings tables must not be tracked

Red on feat-cmux-next: every table gen-strings.mjs writes is committed, and
none is ignored (#21946).

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

* cmux-next: page strings tables become build output (#21946)

The generated strings tables (webviews/src/**/generated/strings.json, the diff
page's per-locale tables and loader, the home-channels pane table) are no longer
committed. bun install (postinstall, best effort), the webviews test preload
(only stale pages) and every bundle build (strict) write them from the
checkout's xcstrings catalogs and settings schema.

Committed copies conflicted in every pull request that changed a catalog or the
settings schema, and the PR autofix could rebuild one from a stale schema and
drop another branch's labels (#21746). A table made from the tree it sits in
can do neither.

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

* cmux-next: merge rules and atomic writes for every strings output (#21946)

The diff page's locale tables and loader and the home-channels pane table are
build output too: route them through cmux-generated-v1 and next_batch's
WEB_GENERATED, so a branch from before the change that edited one catches up
instead of stopping on a modify/delete conflict. gen-strings writes through a
temporary file (parallel test preloads), and --best-effort skips a page whose
catalog or schema is missing instead of failing bun install.

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

* chore(next): PR autofix at f2ef8f4

- regenerated schemas/settings-export/settings-schema.sources.sha256
- vp fmt webviews/scripts/pages/gen-strings.mjs
- vp fmt webviews/test/strings-build-output.test.ts

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
teamleaderleo added a commit that referenced this pull request Oct 19, 2026
…t frame of a click (#21976)

* test(ui): a picker trigger is marked pressed from press to release (red)

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

* fix(agent pane): a composer picker keeps its pressed tone on the first frame of a click

Opening a picker moves focus into its popup before the held frame paints,
which clears :active. The trigger carries data-press-active from press to
release, and the composer's picker buttons draw the open-chip tone under it.

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

* guards: drop the pull_request_target publish case removed with publish-pr-tree

17bb49c deleted the dead publish-pr-tree job, so release-notary's
'pull_request_target refs/heads/feat-cmux-next publishes once' cases failed
5 times (run 39205353024).

* ci(cmux-next media): rebuild a warm tour's prebuilt modules after a binary pin change (#21971)

* test(ci): a warm tour must rebuild after a GhosttyNextKit pin change

The cmux-next tour keeps each tag's DerivedData warm, and Xcode reused the
explicit module it had precompiled from the old pin's ghostty.h: "ghostty.h
has been modified since the module file ... was built" (job 117892119256).
This regression runs the tour's build step twice with a stand-in reload.sh
that fails the same way; it fails until the fix lands.

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

* fix(ci): drop a warm tour's prebuilt modules when a binary pin changes

A Mac that last built a pull request's tag with GhosttyNextKit e6a27a1fb
failed the next tour, which pinned ced99dcde: Xcode copied the new ghostty.h
into Build/Products/Debug/include but kept the explicit module it had
precompiled from the old one (job 117892119256).

scripts/ci/media_module_cache_stamp.py keys a tag's build on every sha256 in
the tracked Package.swift files and every Package.resolved. Before reload.sh
it removes SwiftExplicitPrecompiledModules, ExplicitPrecompiledModules and
ModuleCache.noindex when that key differs from the stamp of the tag's last
good build; after the build it writes the stamp. The rest of DerivedData
stays warm.

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

---------

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

* gallery advisory: sample the states a PR adds first (#21850)

* gallery advisory: sample the states a PR adds first

A PR that touches a shared file (strings, App.tsx) touches every gallery
entry, so the first 16 states in order came from unrelated entries and the
receipt never showed what the PR added. The manifests now list every touched
state and the sample puts the new ones (no base case with that id) first.

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

* gallery advisory: the base keeps every sampled state before removed ones

The base filter cut at 16 in base order, so removed states early in that
order could push out sampled states that exist in base, which were then
compared as new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0283a51)

* gallery advisory: each stage renders its own manifest

Both stages rendered the joined case list, so the base reported the states a
PR adds as not ready (it has no such variant) and the capture stopped before
the head ran. Now that new states come first, every PR that adds a variant
hit this (#21851's first run).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5b6d825)

---------

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

* docs(CLAUDE.md): merge freezes are issues; drop the WINDOW fallback (#21958)

* docs(CLAUDE.md): merge freezes are issues; drop the WINDOW fallback

The guarded fallback for when gh-merge-green couldn't read the coordinator's
WINDOW no longer applies: freezes are merge-freeze issues that every host
reads through the GitHub API (#21957).

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

* docs(merge-freeze): gh-merge-green enforces the freeze check

Same line as #21957 on main.

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

---------

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

* apps: preserve provider mutation context for owner handlers (#21406)

Replays #21406's change onto current feat-cmux-next as one commit.

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

* test(sidebar): a dragged group drops between every group

Regression test for scrawl-1792382497882-1673e-10: on the sidebar
document path a dragged group found a drop only on the thin bands of
each group header. Fails without the fix.

* fix(sidebar): fluid group drag, a drop at every gap between groups

- SidebarDragGeometry: a drop that cannot go into a group (a dragged
  group, groups do not nest) goes before or after the whole group, by
  the half of its block the probe is in, over its header or any member
  row. Before, every member row refused and the gap closed.
- A dragged group's header, not the tall card's centre, decides the drop.
- A refused place keeps the last open gap instead of springing it shut
  and dimming the card, so rows stop jumping during the drag.

* test(layout): a divider drag keeps one daemon command in flight

Adds GestureCommandCoalescer and its tests: 125 live values while one
command is in flight send the first and the newest only; the ended value
goes last; a failed live value with a newer one waiting is superseded.

* fix(layout): stop flooding the daemon during divider and column drags

NIGHTLY sent one set-viewport-pane-width per mouse move (~125/s); each
timed out at 2 s and every other command (pane and tab moves, saves,
attaches) timed out behind them. Divider and column gestures now go
through GestureCommandCoalescer: one command in flight per gesture, only
the newest value waits, the ended value always goes last. A failed live
value with a newer one waiting no longer rolls the divider back.

* scripts(cmux-next): drag-lag-live.py, column/tab/sidebar drag timing on a fleet app

* scripts(cmux-next): drag-lag-live probes the daemon during drags, bigger scene

* ci(nightly-next): one-command rollback of the cmux NEXT feed (#21887)

* ci(nightly-next): one-command rollback of the cmux NEXT feed

nightly-next-rollback.yml takes a bad build, and every newer one, off
appcast-arm64.xml so the feed serves the previous signed build again. It
runs in release-next on branch nightly-next, guards the appcast and R2 key
like publish-nightly, checks the fallback DMG exists, refuses to empty the
feed, and stops when the feed changed while it planned.

docs/ci/nightly-next-canary.md maps the nightly pipeline and its holds and
says what a pull can and cannot do: Sparkle never offers a lower build.

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

* ci(nightly): the delta lane never puts a pulled build back on the feed

republish-nightly-deltas skipped its rewrite only when the live NEXT feed
served a newer build. After nightly-next-rollback.yml pulls build N, the
feed serves N-1 and the lane's two-item appcast would restore N. It now
skips whenever the feed serves any other build. The regression cases in
test_nightly_republish_delta_guard.py fail on the old guard.

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

* ci(nightly-next): rollback runs-on keeps a fork branch

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

* ci(nightly-next): the rollback writes the feed only with If-Match

upload-r2-object.py --replace-sha256 HEX reads the object, requires the
bytes the caller planned from, and writes with If-Match on its ETag; a
changed object is not written (--conflict-exit-code). The rollback uses it
instead of a public re-read, so a publication that lands between the plan
and the write is never overwritten. Its summary no longer says "nothing
to pull" when the plan itself failed. (CodeRabbit on #21900.)

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

---------

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

* cmux-next: mark the sidebar card's Core Animation objects as reviewed motion exceptions (#21951)

* cmux-next: mark the sidebar card's Core Animation objects as reviewed motion exceptions

c5a1f90 built the update card's entrance, exit and crossfade from raw
CABasicAnimation, CASpringAnimation, CATransition and CAMediaTimingFunction, so
check-motion is red on feat-cmux-next (9 hits). Every duration and spring
already comes from the injected MotionPolicy's tokens; Motion.set would write
the model value and read the global policy instead. The 9 lines carry a
motion-allow and the type doc says why. Comments only.

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

* cmux-next: the sidebar card's motion-allow note names what stays literal

The curves and the spring's settle tolerance are literal, not tokens; say so,
so the allow does not overclaim (coord-2's review of #21951).

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

---------

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

* optchat-chief: auto-resume subagents that stop on a model or network error

A subagent run that ends on an API/harness error is classified
(auto_resume.rs): transient errors (overloaded, 429, 5xx, did not start)
are told to continue after a 30/60/120 s backoff, at most 3 times in a
row; network errors (connection lost, fetch failed, DNS) are not retried
blind: a thread waits until a TCP probe of the model endpoint (the
subrouter from ANTHROPIC_BASE_URL or ~/.claude/settings.json) answers,
then continues. The Chief's chat gets a quiet '[id] stopped: <reason>'
note; the subagent's chat shows each '[auto-resume N]' continue. A stop
auto-resume gives up on is reported as usual. OPTCHAT_AUTO_RESUME=off
turns it off.

* optchat-chief: auto-resume agent CLIs in cmux terminals; fix clone_on_copy

terminal_watch.rs: every 30 s the Chief host lists cmux terminals; a
Claude Code or Codex terminal that reports 'error', or sits idle at an
empty prompt with an API error as its last output, is classified as in
auto_resume.rs. Transient errors get '[auto-resume N] ... continue' typed
at the prompt after a backoff; network errors wait until the model
endpoint answers. Working, blocked (a question) or half-typed prompts are
never touched. Each event reaches the Chief chat as
'[term_...] stopped: <reason>' (a notice while a continue is planned, a
Chief note when it gives up). OPTCHAT_TERMINAL_RESUME=off turns it off.

Also: a short reply that only mentions an error is not a stop, and the
clippy clone_on_copy in workspaces.rs (a75b0c3) is fixed.

* optchat-chief: module order in lib.rs (rustfmt)

* fix(acpmux): clear stop timer on client close (#21615)

* Markdown: cmux:// workspace, pane, tab and chat links open in the app

A cmux:// link in rendered Markdown did nothing or left the app: the
Markdown file page classified every non-web scheme as unsafe, Home only
linked a Chief subagent's chat, and the agent pane drew only http(s).

- One opener, CmuxAppLinks (was ChiefSubagentLinks): a workspace, pane,
  tab or session link in the `cmux` scheme or this build's scheme, or this
  Chief's subagent link, runs link.open. Terminals and pages reach it via
  LinkRouter.appLink.
- Markdown file page: `cmux://...` is link kind "app" (hover: "Opens in
  cmux", 21 locales); cmux.markdown.openLink kind "app" opens it in the app
  or refuses, never the system.
- Home: URL.isCmuxAppLink draws and routes the same links to onAppLink.
- Agent chat: replies draw cmux app links; a click after a real gesture
  goes to the app opener, never NSWorkspace.
- javascript:, file:, data:, vbscript:, ssh: and malformed ids stay text.

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

* cmux-next palette: Go to Workspace (Cmd-P) searches groups, panes, tabs and agent sessions

Cmd-P now lists sidebar groups, panes and every tab (terminal, browser,
markdown, file, agent chat) next to the workspaces once you type, each row
with its kind and its parent workspace and group. Titles, cwd, URL, group
names and session ids are searchable; whole-title and prefix matches rank
first, then the recently used.

- Picking a group shows its first workspace, expanding the group when
  collapsed (WorkspaceGroupFocus, shared with Next/Previous Workspace Group).
- Picking a pane or tab shows its workspace and focuses it (pane.focus,
  revealTab as Search Tabs).
- Cmd-K in the palette shows the row's Actions menu; agent rows offer Copy
  Session ID: acp:<session id> for acpmux chats (what cmux agent message
  takes), the harness session UUID for Claude Code / Codex terminals.
  Handle format documented in skills/cmux-acpmux/SKILL.md.

* acpmux: require version evidence for already-current updates (#21398)

Replays #21398's change onto current feat-cmux-next as one commit.

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

* cx-dgsp follow-ups: scope-bound wire tickets, hold a refused feed token (#21725)

* test(api): the forwarded wire upgrade carries no credential

A native header upgrade with stray bearer.x and ticket.y subprotocols
reaches UserDO with no Authorization header and only cmux.wire.v1.
Green on the current strip; red when wireCredential keeps bearer. or
forwardHeaders keeps Authorization.

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

* test(api): a wire ticket opens only the scope it was minted for (red)

POST /v1/wire/ticket takes {scope}; a feed ticket must not open the user
socket, a refused try spends it, an unscoped ticket is a user ticket and
an unknown scope mints nothing. Fails now: the feed ticket opens
/v1/wire/user (101).

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

* test(cloud-client): openWire mints its ticket for the scope it opens (red)

Fails now: POST /v1/wire/ticket goes out with no body.

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

* fix(api): bind a wire ticket to the socket scope it was minted for

POST /v1/wire/ticket takes {scope} (user|team|feed|cloud|conv|mux; no
body or no scope means user, which the dashboard panel and any client
minted before this send). The ticket record keeps the scope; the router
entry still spends a ticket on any path, and wire() refuses one of
another scope with the usual 401. openWire and the dashboard's
wireCredential name their scope.

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

* test(cmux-next): a 401 on the feed wire holds the refused token (red)

CloudFeedSource takes an internal Connect seam and reconnect Backoff so
the loop runs against a fake; the socket session throws Unauthorized
when the upgrade's response is 401. The test fails now: the loop opens a
socket for every repeat of the refused token (a, a, a, b), not a then b.
Compile-only on this Linux host; it runs in CmuxNextAppTests on macOS.

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

* fix(cmux-next): stop reconnecting the feed wire with a token the server refused

A 401 on the upgrade fed the outer reconnect loop, which opened a socket
with the same token every 500 ms..30 s. The loop now remembers the
refused token, reports a disconnected reason once, and opens no socket
with it again: it asks the token provider on the Backoff schedule (no
request reaches the server) until it returns another token. start and
reset still restart the loop on a sign-in or account change.

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

* test(api): the expired-ticket test mints a feed ticket for the feed socket

With scope binding, an unscoped (user) ticket on /v1/wire/feed is refused
for its scope, so the test no longer reached the expiry check. Red when
WireTicketDO.spend ignores expires_at.

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

---------

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

* cmux-next: the Desktop Wallpaper tile shows the desktop, never a broken image

macOS can report a desktop picture that is gone (a deleted file in /tmp),
a folder it rotates through, or a .madesktop description. The Settings
thumbnail and the window backdrop loaded that URL as is, so the tile drew
the webview's broken-image mark and choosing it showed nothing.

DesktopPicture resolves the reported URL to a picture that decodes: the
file, a picture in the folder, the still a description names, or else a
picture of the desktop fill color. The picker swaps a thumbnail that fails
to load for an empty framed box.

* Home dock: pinned column kind (pinned-dock-v1)

Lawrence 2026-10-18: "I should not be able to close Home inside the Home
thing ... I should not be able to create more tabs in the Home thing
unless I explicitly enable it."

Daemon (cmux-tui): a dock column may be pinned. Its first tab stays (no
close, move out or reorder away from the first place) and it takes no
other tab unless its allow_tabs is on (column.update {allow_tabs}). The
column is never removed, undocked or unpinned. Enforced on every path
(resource ops, JSON-lines, layout undo, terminal tab creation before the
spawn) with reason_code dock-column-pinned. set-column-dock takes pinned;
column.update takes pinned and allow_tabs. Spec, docs, bindings updated.

App: Home pins its left chat dock on connect (page tab first). Home's tab
shows no close button, the strip no new-tab button while the column
takes no tabs, Cmd-W keeps Home's tab, new agent chats open beside a
pinned dock, and a refused close says "Home stays open in its column."

* cmux NEXT: compact attachment strip in the composer (#21739)

* test(acpmux): the attachment strip is compact

Leo 2026-10-18: pasted screenshots took ~100 px off the composer. One row of
64 px tiles 6 px apart, 8 px under the composer's top, the text 6 px under it.

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

* fix(acpmux): compact attachment strip, like the Claude desktop composer

64 px tiles (from 80), a 6 px gap, an 8 px top inset, and the prompt starts
6 px under the strip instead of 15. The strip adds about 78 px, not ~100.

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

* fix(acpmux): shell field follows the compact strip; tighten the gallery budget

Review: in shell mode under attachments the field and glyph kept the 15 px
inset, so typing ! moved the text 9 px. The attach-screenshot gallery budget
still allowed the old 96 px row.

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

* test(acpmux): wait for the thumbnails to load instead of decode()

CI: decode() rejected while a thumbnail's source was swapped for its copy.

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

---------

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

* cmux-next: the popup address field draws no focus ring (#21963)

fa8b3fc made the popup URL label selectable without focusRingType = .none, which turns check:text-focus red on every webviews job.

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

* composer: picking another provider's model keeps the typed prompt (#21937)

* test: a harness switch keeps the typed prompt (red)

* composer: picking another provider's model keeps the typed prompt through the harness switch

* composer: a chat's own saved draft wins over a carried prompt (review)

* fix(acpmux): the empty prompt mid-turn teaches ↵ and ⌘↵ (#21870)

* test(acpmux): a running turn's empty prompt teaches Return and ⌘Return

Leo 2026-10-18: like Claude's "↵ to send, ⌘↵ to queue for later", the
empty prompt mid-turn says "↵ to queue, ⌘↵ to steer" (or interrupt, for an
agent that cannot steer). The Quick Composer keeps "Queue a follow-up".

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

* fix(acpmux): the empty prompt mid-turn teaches ↵ and ⌘↵

Mid-turn the empty prompt says "↵ to queue, ⌘↵ to steer" (or "to interrupt"
when the agent cannot take steering), as Claude's composer does. The Quick
Composer keeps "Queue a follow-up", since its ⌘↵ opens the window.

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

* test(acpmux): an attachment-only prompt mid-turn names only the queue key

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

* fix(acpmux): drop the ⌘↵ hint with only attachments, match the Steer button's verb

⌘Return needs typed text, so an attachment-only prompt mid-turn says only
"Queue a follow-up". The es, fr, it, ja, ko and uk hints now use the Steer
button's own verb.

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

* docs(acpmux): say when the queued placeholder still shows

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

---------

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

* acpmux: schema doc names peer masking and the lane error cut (#21924)

* docs(acpmux): the schema names peer masking and the lane error cut

The SessionSummary lane note still said every other field stays raw;
since #21871 a peer daemon reads them masked, and since #21799 the lane
error is cut to 3072 characters.

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

* docs(acpmux): raw for every client but a proven peer

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

---------

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

* cmux-next: every markdown viewer is read-write; Cmd-=/-/0 text size and a default size setting

Root cause: a markdown file opened by cmux open, an agent or a link (not the
user's own Open File...) was writable only inside a files.roots folder. The
roots setting is empty by default and never holds home, so viewers opened
from the Home workspace (and any workspace) came up read only with an
"outside the workspace" note.

- Markdown pages are writable for every document they show; only a real
  reason keeps one read only (not UTF-8, binary, not writable, or a chat
  transcript the host opens read only), and the page names the reason.
  The code editor keeps its root rule.
- The markdown page handles the zoomIn/zoomOut/zoomReset page commands the
  app already sent: one pixel per step (8-40) in rich and source mode,
  Cmd-0 back to markdown.font.size. Cmd-Shift-= is a Zoom In alias.
- Settings > Appearance > Markdown Viewer > Default Font Size
  (markdown.font.size, 16, 8-40), localized.

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

* fix(home): pasted pictures are small thumbnails in a row in the composer

The vendored MessagesLab compose strip copies macOS 27 Messages: every
attachment is a 177 x 118 pt tile stacked top to bottom above the text.
Two pasted screenshots made the field ~280 pt tall and pushed the text
line to the bottom. Images are now aspect-fit in 128 x 64 pt, files are
200 x 52 pt pills, and tiles flow left to right and wrap at the field's
right edge.

Tests: ComposeAttachmentLayoutTests (MessagesLabHome).

* test(home): qualify Attachment (Swift Testing has one too)

* cmux-next: page strings tables become build output (#21970)

* test(webviews): strings tables must not be tracked

Red on feat-cmux-next: every table gen-strings.mjs writes is committed, and
none is ignored (#21946).

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

* cmux-next: page strings tables become build output (#21946)

The generated strings tables (webviews/src/**/generated/strings.json, the diff
page's per-locale tables and loader, the home-channels pane table) are no longer
committed. bun install (postinstall, best effort), the webviews test preload
(only stale pages) and every bundle build (strict) write them from the
checkout's xcstrings catalogs and settings schema.

Committed copies conflicted in every pull request that changed a catalog or the
settings schema, and the PR autofix could rebuild one from a stale schema and
drop another branch's labels (#21746). A table made from the tree it sits in
can do neither.

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

* cmux-next: merge rules and atomic writes for every strings output (#21946)

The diff page's locale tables and loader and the home-channels pane table are
build output too: route them through cmux-generated-v1 and next_batch's
WEB_GENERATED, so a branch from before the change that edited one catches up
instead of stopping on a modify/delete conflict. gen-strings writes through a
temporary file (parallel test preloads), and --best-effort skips a page whose
catalog or schema is missing instead of failing bun install.

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

* chore(next): PR autofix at f2ef8f4

- regenerated schemas/settings-export/settings-schema.sources.sha256
- vp fmt webviews/scripts/pages/gen-strings.mjs
- vp fmt webviews/test/strings-build-output.test.ts

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

---------

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 new terminal tab spawns at its pane's grid, so its first frame is final

The Mac's new-tab request sent no size. The daemon then spawned the PTY at
the grid of whichever terminal reported last, anywhere
(client_sizing_state.rs creation_size), the shell printed its first prompt
at that width, and the pane snapped when the view's first report resized it.

newTerminalTab now sends cols/rows (NewTerminalGrid): the fitted grid of the
terminal the pane shows (Ghostty's own fit of the same content host), else
the content host size, backing scale, a live terminal's cell size and the
configured padding through Ghostty's formula (TerminalGridEstimate; Cmd-T's
New Tab page, a page or agent tab). A zoomed terminal does not count. With
neither known the daemon's fallback applies as before.

docs/shared-terminal-sizing.md: a Creation size section (who sizes a new
terminal, and why a later attach never draws a wrong frame for others).

* cmux NEXT: NewTerminalGridTests builds its padding from the public default

* perf-10x daemon: request-latency bench (raw, 10 clients, CLI; 1 and 200 terminals)

* cmux-next: clear five base reds in the cmux-next checks lane (#21981)

The ErrorJournal (1597dc9) and browser popup (fa8b3fc) commits left
five checks red on feat-cmux-next, so every PR into it fails "cmux-next checks"
and its web lane skips its tests. Comments and baselines only, no behavior change:

- Main-thread concurrency: the harvest loop runs in a detached utility task and
  the flush delay in the ErrorJournal actor's task, so neither is on main;
  reviewed concurrency-allow and wakeup-allow comments.
- No literal state roots: the app's ~/.acpmux/daemon.log is only read; a
  keep row in the baseline.
- Package conventions: lint:allow on BrowserPopupWindowGeometry (pure geometry).
- Type scale: BrowserPopupPanel.swift has no raw font sizes left; drop its row.
- Text focus: the popup address field sets focusRingType = .none (was #21963).

Motion tokens (SidebarCardTransition) stays with #21951 for its owner.

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

* cx-ncc.104: Debug toggle for the agent pane's in-session status (agent.sessionStatus) (#20953)

Recut of #20953 onto the current feat-cmux-next as one commit: the
In-session status Debug toggle, its sessionStatus page event, and
useSessionStatus() for the plan and recap cards. The base merge kept
both sides of the tunables list, the view's observations and the page
host's events (the sidebar preview variant landed meanwhile).

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

* acpmux: route/list says which routes serve the session's family (#21852)

* test(acpmux): route/list names a session's family and which routes serve it

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

* feat(acpmux): route/list says which routes serve the session's family

With a sessionId, _acpmux/route/list now returns the session's family and a
serves flag on each route (Route::serves), so the composer's Account menu can
list only the routes that can run this chat without copying that logic.

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

---------

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

* cmux-next: tunable registries list one entry per line (#21972)

* cmux-next: tunable registries list one entry per line (#21946)

AgentPaneTunables.all and SidebarTunables.all were single lines, so every pull
request that added a tunable conflicted with every other one there (14 open
PRs on AgentPaneTunables, 4 on SidebarTunables). Each entry now has its own
line in alphabetical order, so two additions insert different lines and merge
cleanly; TunableCatalog.all lists one module per line. Debug Settings shows the
agent-pane and sidebar tunables alphabetically; nothing else depends on the
order.

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

* Keep the TunableCatalog list as it was: CmuxNextApp may not grow

The one-per-line TunableCatalog added 20 source lines to CmuxNextApp, which
check-app-size.sh refuses. The conflict hotspots are the AgentPaneTunables and
SidebarTunables lists, which live in their own targets and stay one per line.

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

---------

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

* test(next): cover dictation auto-send prompt routing (#21169)

* test(next): cover dictation auto-send prompt routing

* fix(next): satisfy dictation test formatting

* chore(next): PR autofix at 60f4a49

- regenerated Packages/macOS/CmuxNext/ci-target-graph.json
- vp fmt webviews/src/agent-session/acpmux/replyHref.ts
- vp fmt webviews/src/pages/markdown/README.md
- vp fmt webviews/src/pages/markdown/links.ts
- vp fmt webviews/test/markdown-cmux-app-links.test.ts

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

* chore: drop the autofix bot's unrelated markdown-link and target-graph edits from this PR

They aren't part of this change; base carries its own version.

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

* fix(agent pane): a pressed pill keeps the pill's own open tone

The model, effort and access pills open at --acpmux-pill-hover; under the
press marker alone they drew the brighter --acpmux-pill-open.

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

* test(ui): window blur, Escape and a drag-out release clear the press marker (red)

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

* fix(ui): the press marker clears on window blur, Escape and unmount

A press whose pointerup never reaches the page (Cmd-Tab away, Escape)
left the pill tinted. The plain footer's resting and hover rules also
exclude the marker, so it can't flash there.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com>
Co-authored-by: Lucas Ribeiro <lucascamribeiro@gmail.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
teamleaderleo added a commit that referenced this pull request Oct 19, 2026
* feat(agent-pane): accept new tab inline suggestions safely

* fix(agent-pane): complete visited URL suggestions

* test(new-tab): Tab without a highlighted row moves focus; history rows are not inline-accepted

Red: Tab is prevented whenever suggestions show, and Right/End inline-accepts
history rows.

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

* fix(new-tab): Tab steps only a keyboard-highlighted row; inline completion skips history rows

Tab is taken only after Down/Up/Tab moved the highlight, so keyboard users can
Tab to Send. Right/End inline-accept only top-row open results (typed address
or top site), matching the comment that history rows stay explicit picks.

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

* guards: drop the pull_request_target publish case removed with publish-pr-tree

17bb49c deleted the dead publish-pr-tree job, so release-notary's
'pull_request_target refs/heads/feat-cmux-next publishes once' cases failed
5 times (run 39205353024).

* ci(cmux-next media): rebuild a warm tour's prebuilt modules after a binary pin change (#21971)

* test(ci): a warm tour must rebuild after a GhosttyNextKit pin change

The cmux-next tour keeps each tag's DerivedData warm, and Xcode reused the
explicit module it had precompiled from the old pin's ghostty.h: "ghostty.h
has been modified since the module file ... was built" (job 117892119256).
This regression runs the tour's build step twice with a stand-in reload.sh
that fails the same way; it fails until the fix lands.

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

* fix(ci): drop a warm tour's prebuilt modules when a binary pin changes

A Mac that last built a pull request's tag with GhosttyNextKit e6a27a1fb
failed the next tour, which pinned ced99dcde: Xcode copied the new ghostty.h
into Build/Products/Debug/include but kept the explicit module it had
precompiled from the old one (job 117892119256).

scripts/ci/media_module_cache_stamp.py keys a tag's build on every sha256 in
the tracked Package.swift files and every Package.resolved. Before reload.sh
it removes SwiftExplicitPrecompiledModules, ExplicitPrecompiledModules and
ModuleCache.noindex when that key differs from the stamp of the tag's last
good build; after the build it writes the stamp. The rest of DerivedData
stays warm.

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

---------

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

* gallery advisory: sample the states a PR adds first (#21850)

* gallery advisory: sample the states a PR adds first

A PR that touches a shared file (strings, App.tsx) touches every gallery
entry, so the first 16 states in order came from unrelated entries and the
receipt never showed what the PR added. The manifests now list every touched
state and the sample puts the new ones (no base case with that id) first.

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

* gallery advisory: the base keeps every sampled state before removed ones

The base filter cut at 16 in base order, so removed states early in that
order could push out sampled states that exist in base, which were then
compared as new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0283a51)

* gallery advisory: each stage renders its own manifest

Both stages rendered the joined case list, so the base reported the states a
PR adds as not ready (it has no such variant) and the capture stopped before
the head ran. Now that new states come first, every PR that adds a variant
hit this (#21851's first run).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5b6d825)

---------

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

* docs(CLAUDE.md): merge freezes are issues; drop the WINDOW fallback (#21958)

* docs(CLAUDE.md): merge freezes are issues; drop the WINDOW fallback

The guarded fallback for when gh-merge-green couldn't read the coordinator's
WINDOW no longer applies: freezes are merge-freeze issues that every host
reads through the GitHub API (#21957).

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

* docs(merge-freeze): gh-merge-green enforces the freeze check

Same line as #21957 on main.

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

---------

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

* apps: preserve provider mutation context for owner handlers (#21406)

Replays #21406's change onto current feat-cmux-next as one commit.

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

* test(sidebar): a dragged group drops between every group

Regression test for scrawl-1792382497882-1673e-10: on the sidebar
document path a dragged group found a drop only on the thin bands of
each group header. Fails without the fix.

* fix(sidebar): fluid group drag, a drop at every gap between groups

- SidebarDragGeometry: a drop that cannot go into a group (a dragged
  group, groups do not nest) goes before or after the whole group, by
  the half of its block the probe is in, over its header or any member
  row. Before, every member row refused and the gap closed.
- A dragged group's header, not the tall card's centre, decides the drop.
- A refused place keeps the last open gap instead of springing it shut
  and dimming the card, so rows stop jumping during the drag.

* test(layout): a divider drag keeps one daemon command in flight

Adds GestureCommandCoalescer and its tests: 125 live values while one
command is in flight send the first and the newest only; the ended value
goes last; a failed live value with a newer one waiting is superseded.

* fix(layout): stop flooding the daemon during divider and column drags

NIGHTLY sent one set-viewport-pane-width per mouse move (~125/s); each
timed out at 2 s and every other command (pane and tab moves, saves,
attaches) timed out behind them. Divider and column gestures now go
through GestureCommandCoalescer: one command in flight per gesture, only
the newest value waits, the ended value always goes last. A failed live
value with a newer one waiting no longer rolls the divider back.

* scripts(cmux-next): drag-lag-live.py, column/tab/sidebar drag timing on a fleet app

* scripts(cmux-next): drag-lag-live probes the daemon during drags, bigger scene

* ci(nightly-next): one-command rollback of the cmux NEXT feed (#21887)

* ci(nightly-next): one-command rollback of the cmux NEXT feed

nightly-next-rollback.yml takes a bad build, and every newer one, off
appcast-arm64.xml so the feed serves the previous signed build again. It
runs in release-next on branch nightly-next, guards the appcast and R2 key
like publish-nightly, checks the fallback DMG exists, refuses to empty the
feed, and stops when the feed changed while it planned.

docs/ci/nightly-next-canary.md maps the nightly pipeline and its holds and
says what a pull can and cannot do: Sparkle never offers a lower build.

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

* ci(nightly): the delta lane never puts a pulled build back on the feed

republish-nightly-deltas skipped its rewrite only when the live NEXT feed
served a newer build. After nightly-next-rollback.yml pulls build N, the
feed serves N-1 and the lane's two-item appcast would restore N. It now
skips whenever the feed serves any other build. The regression cases in
test_nightly_republish_delta_guard.py fail on the old guard.

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

* ci(nightly-next): rollback runs-on keeps a fork branch

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

* ci(nightly-next): the rollback writes the feed only with If-Match

upload-r2-object.py --replace-sha256 HEX reads the object, requires the
bytes the caller planned from, and writes with If-Match on its ETag; a
changed object is not written (--conflict-exit-code). The rollback uses it
instead of a public re-read, so a publication that lands between the plan
and the write is never overwritten. Its summary no longer says "nothing
to pull" when the plan itself failed. (CodeRabbit on #21900.)

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

---------

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

* cmux-next: mark the sidebar card's Core Animation objects as reviewed motion exceptions (#21951)

* cmux-next: mark the sidebar card's Core Animation objects as reviewed motion exceptions

c5a1f90 built the update card's entrance, exit and crossfade from raw
CABasicAnimation, CASpringAnimation, CATransition and CAMediaTimingFunction, so
check-motion is red on feat-cmux-next (9 hits). Every duration and spring
already comes from the injected MotionPolicy's tokens; Motion.set would write
the model value and read the global policy instead. The 9 lines carry a
motion-allow and the type doc says why. Comments only.

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

* cmux-next: the sidebar card's motion-allow note names what stays literal

The curves and the spring's settle tolerance are literal, not tokens; say so,
so the allow does not overclaim (coord-2's review of #21951).

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

---------

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

* optchat-chief: auto-resume subagents that stop on a model or network error

A subagent run that ends on an API/harness error is classified
(auto_resume.rs): transient errors (overloaded, 429, 5xx, did not start)
are told to continue after a 30/60/120 s backoff, at most 3 times in a
row; network errors (connection lost, fetch failed, DNS) are not retried
blind: a thread waits until a TCP probe of the model endpoint (the
subrouter from ANTHROPIC_BASE_URL or ~/.claude/settings.json) answers,
then continues. The Chief's chat gets a quiet '[id] stopped: <reason>'
note; the subagent's chat shows each '[auto-resume N]' continue. A stop
auto-resume gives up on is reported as usual. OPTCHAT_AUTO_RESUME=off
turns it off.

* optchat-chief: auto-resume agent CLIs in cmux terminals; fix clone_on_copy

terminal_watch.rs: every 30 s the Chief host lists cmux terminals; a
Claude Code or Codex terminal that reports 'error', or sits idle at an
empty prompt with an API error as its last output, is classified as in
auto_resume.rs. Transient errors get '[auto-resume N] ... continue' typed
at the prompt after a backoff; network errors wait until the model
endpoint answers. Working, blocked (a question) or half-typed prompts are
never touched. Each event reaches the Chief chat as
'[term_...] stopped: <reason>' (a notice while a continue is planned, a
Chief note when it gives up). OPTCHAT_TERMINAL_RESUME=off turns it off.

Also: a short reply that only mentions an error is not a stop, and the
clippy clone_on_copy in workspaces.rs (a75b0c3) is fixed.

* optchat-chief: module order in lib.rs (rustfmt)

* test(new-tab): a visited page completes inline on Right and opens nothing (red)

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

* fix(new-tab): a visited page completes inline again

The Tab fix also dropped history rows from inline completion to match an
older comment, undoing 1d8f0db. Accepting a completion only fills the field
on an explicit Right or End, and Enter or Send still opens it, so a visited
page completes as in Chrome. The comment now says so.

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

* fix(acpmux): clear stop timer on client close (#21615)

* Markdown: cmux:// workspace, pane, tab and chat links open in the app

A cmux:// link in rendered Markdown did nothing or left the app: the
Markdown file page classified every non-web scheme as unsafe, Home only
linked a Chief subagent's chat, and the agent pane drew only http(s).

- One opener, CmuxAppLinks (was ChiefSubagentLinks): a workspace, pane,
  tab or session link in the `cmux` scheme or this build's scheme, or this
  Chief's subagent link, runs link.open. Terminals and pages reach it via
  LinkRouter.appLink.
- Markdown file page: `cmux://...` is link kind "app" (hover: "Opens in
  cmux", 21 locales); cmux.markdown.openLink kind "app" opens it in the app
  or refuses, never the system.
- Home: URL.isCmuxAppLink draws and routes the same links to onAppLink.
- Agent chat: replies draw cmux app links; a click after a real gesture
  goes to the app opener, never NSWorkspace.
- javascript:, file:, data:, vbscript:, ssh: and malformed ids stay text.

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

* cmux-next palette: Go to Workspace (Cmd-P) searches groups, panes, tabs and agent sessions

Cmd-P now lists sidebar groups, panes and every tab (terminal, browser,
markdown, file, agent chat) next to the workspaces once you type, each row
with its kind and its parent workspace and group. Titles, cwd, URL, group
names and session ids are searchable; whole-title and prefix matches rank
first, then the recently used.

- Picking a group shows its first workspace, expanding the group when
  collapsed (WorkspaceGroupFocus, shared with Next/Previous Workspace Group).
- Picking a pane or tab shows its workspace and focuses it (pane.focus,
  revealTab as Search Tabs).
- Cmd-K in the palette shows the row's Actions menu; agent rows offer Copy
  Session ID: acp:<session id> for acpmux chats (what cmux agent message
  takes), the harness session UUID for Claude Code / Codex terminals.
  Handle format documented in skills/cmux-acpmux/SKILL.md.

* acpmux: require version evidence for already-current updates (#21398)

Replays #21398's change onto current feat-cmux-next as one commit.

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

* cx-dgsp follow-ups: scope-bound wire tickets, hold a refused feed token (#21725)

* test(api): the forwarded wire upgrade carries no credential

A native header upgrade with stray bearer.x and ticket.y subprotocols
reaches UserDO with no Authorization header and only cmux.wire.v1.
Green on the current strip; red when wireCredential keeps bearer. or
forwardHeaders keeps Authorization.

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

* test(api): a wire ticket opens only the scope it was minted for (red)

POST /v1/wire/ticket takes {scope}; a feed ticket must not open the user
socket, a refused try spends it, an unscoped ticket is a user ticket and
an unknown scope mints nothing. Fails now: the feed ticket opens
/v1/wire/user (101).

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

* test(cloud-client): openWire mints its ticket for the scope it opens (red)

Fails now: POST /v1/wire/ticket goes out with no body.

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

* fix(api): bind a wire ticket to the socket scope it was minted for

POST /v1/wire/ticket takes {scope} (user|team|feed|cloud|conv|mux; no
body or no scope means user, which the dashboard panel and any client
minted before this send). The ticket record keeps the scope; the router
entry still spends a ticket on any path, and wire() refuses one of
another scope with the usual 401. openWire and the dashboard's
wireCredential name their scope.

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

* test(cmux-next): a 401 on the feed wire holds the refused token (red)

CloudFeedSource takes an internal Connect seam and reconnect Backoff so
the loop runs against a fake; the socket session throws Unauthorized
when the upgrade's response is 401. The test fails now: the loop opens a
socket for every repeat of the refused token (a, a, a, b), not a then b.
Compile-only on this Linux host; it runs in CmuxNextAppTests on macOS.

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

* fix(cmux-next): stop reconnecting the feed wire with a token the server refused

A 401 on the upgrade fed the outer reconnect loop, which opened a socket
with the same token every 500 ms..30 s. The loop now remembers the
refused token, reports a disconnected reason once, and opens no socket
with it again: it asks the token provider on the Backoff schedule (no
request reaches the server) until it returns another token. start and
reset still restart the loop on a sign-in or account change.

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

* test(api): the expired-ticket test mints a feed ticket for the feed socket

With scope binding, an unscoped (user) ticket on /v1/wire/feed is refused
for its scope, so the test no longer reached the expiry check. Red when
WireTicketDO.spend ignores expires_at.

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

---------

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

* cmux-next: the Desktop Wallpaper tile shows the desktop, never a broken image

macOS can report a desktop picture that is gone (a deleted file in /tmp),
a folder it rotates through, or a .madesktop description. The Settings
thumbnail and the window backdrop loaded that URL as is, so the tile drew
the webview's broken-image mark and choosing it showed nothing.

DesktopPicture resolves the reported URL to a picture that decodes: the
file, a picture in the folder, the still a description names, or else a
picture of the desktop fill color. The picker swaps a thumbnail that fails
to load for an empty framed box.

* Home dock: pinned column kind (pinned-dock-v1)

Lawrence 2026-10-18: "I should not be able to close Home inside the Home
thing ... I should not be able to create more tabs in the Home thing
unless I explicitly enable it."

Daemon (cmux-tui): a dock column may be pinned. Its first tab stays (no
close, move out or reorder away from the first place) and it takes no
other tab unless its allow_tabs is on (column.update {allow_tabs}). The
column is never removed, undocked or unpinned. Enforced on every path
(resource ops, JSON-lines, layout undo, terminal tab creation before the
spawn) with reason_code dock-column-pinned. set-column-dock takes pinned;
column.update takes pinned and allow_tabs. Spec, docs, bindings updated.

App: Home pins its left chat dock on connect (page tab first). Home's tab
shows no close button, the strip no new-tab button while the column
takes no tabs, Cmd-W keeps Home's tab, new agent chats open beside a
pinned dock, and a refused close says "Home stays open in its column."

* cmux NEXT: compact attachment strip in the composer (#21739)

* test(acpmux): the attachment strip is compact

Leo 2026-10-18: pasted screenshots took ~100 px off the composer. One row of
64 px tiles 6 px apart, 8 px under the composer's top, the text 6 px under it.

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

* fix(acpmux): compact attachment strip, like the Claude desktop composer

64 px tiles (from 80), a 6 px gap, an 8 px top inset, and the prompt starts
6 px under the strip instead of 15. The strip adds about 78 px, not ~100.

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

* fix(acpmux): shell field follows the compact strip; tighten the gallery budget

Review: in shell mode under attachments the field and glyph kept the 15 px
inset, so typing ! moved the text 9 px. The attach-screenshot gallery budget
still allowed the old 96 px row.

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

* test(acpmux): wait for the thumbnails to load instead of decode()

CI: decode() rejected while a thumbnail's source was swapped for its copy.

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

---------

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

* cmux-next: the popup address field draws no focus ring (#21963)

fa8b3fc made the popup URL label selectable without focusRingType = .none, which turns check:text-focus red on every webviews job.

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

* composer: picking another provider's model keeps the typed prompt (#21937)

* test: a harness switch keeps the typed prompt (red)

* composer: picking another provider's model keeps the typed prompt through the harness switch

* composer: a chat's own saved draft wins over a carried prompt (review)

* fix(acpmux): the empty prompt mid-turn teaches ↵ and ⌘↵ (#21870)

* test(acpmux): a running turn's empty prompt teaches Return and ⌘Return

Leo 2026-10-18: like Claude's "↵ to send, ⌘↵ to queue for later", the
empty prompt mid-turn says "↵ to queue, ⌘↵ to steer" (or interrupt, for an
agent that cannot steer). The Quick Composer keeps "Queue a follow-up".

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

* fix(acpmux): the empty prompt mid-turn teaches ↵ and ⌘↵

Mid-turn the empty prompt says "↵ to queue, ⌘↵ to steer" (or "to interrupt"
when the agent cannot take steering), as Claude's composer does. The Quick
Composer keeps "Queue a follow-up", since its ⌘↵ opens the window.

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

* test(acpmux): an attachment-only prompt mid-turn names only the queue key

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

* fix(acpmux): drop the ⌘↵ hint with only attachments, match the Steer button's verb

⌘Return needs typed text, so an attachment-only prompt mid-turn says only
"Queue a follow-up". The es, fr, it, ja, ko and uk hints now use the Steer
button's own verb.

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

* docs(acpmux): say when the queued placeholder still shows

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

---------

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

* acpmux: schema doc names peer masking and the lane error cut (#21924)

* docs(acpmux): the schema names peer masking and the lane error cut

The SessionSummary lane note still said every other field stays raw;
since #21871 a peer daemon reads them masked, and since #21799 the lane
error is cut to 3072 characters.

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

* docs(acpmux): raw for every client but a proven peer

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

---------

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

* cmux-next: every markdown viewer is read-write; Cmd-=/-/0 text size and a default size setting

Root cause: a markdown file opened by cmux open, an agent or a link (not the
user's own Open File...) was writable only inside a files.roots folder. The
roots setting is empty by default and never holds home, so viewers opened
from the Home workspace (and any workspace) came up read only with an
"outside the workspace" note.

- Markdown pages are writable for every document they show; only a real
  reason keeps one read only (not UTF-8, binary, not writable, or a chat
  transcript the host opens read only), and the page names the reason.
  The code editor keeps its root rule.
- The markdown page handles the zoomIn/zoomOut/zoomReset page commands the
  app already sent: one pixel per step (8-40) in rich and source mode,
  Cmd-0 back to markdown.font.size. Cmd-Shift-= is a Zoom In alias.
- Settings > Appearance > Markdown Viewer > Default Font Size
  (markdown.font.size, 16, 8-40), localized.

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

* fix(home): pasted pictures are small thumbnails in a row in the composer

The vendored MessagesLab compose strip copies macOS 27 Messages: every
attachment is a 177 x 118 pt tile stacked top to bottom above the text.
Two pasted screenshots made the field ~280 pt tall and pushed the text
line to the bottom. Images are now aspect-fit in 128 x 64 pt, files are
200 x 52 pt pills, and tiles flow left to right and wrap at the field's
right edge.

Tests: ComposeAttachmentLayoutTests (MessagesLabHome).

* test(home): qualify Attachment (Swift Testing has one too)

* cmux-next: page strings tables become build output (#21970)

* test(webviews): strings tables must not be tracked

Red on feat-cmux-next: every table gen-strings.mjs writes is committed, and
none is ignored (#21946).

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

* cmux-next: page strings tables become build output (#21946)

The generated strings tables (webviews/src/**/generated/strings.json, the diff
page's per-locale tables and loader, the home-channels pane table) are no longer
committed. bun install (postinstall, best effort), the webviews test preload
(only stale pages) and every bundle build (strict) write them from the
checkout's xcstrings catalogs and settings schema.

Committed copies conflicted in every pull request that changed a catalog or the
settings schema, and the PR autofix could rebuild one from a stale schema and
drop another branch's labels (#21746). A table made from the tree it sits in
can do neither.

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

* cmux-next: merge rules and atomic writes for every strings output (#21946)

The diff page's locale tables and loader and the home-channels pane table are
build output too: route them through cmux-generated-v1 and next_batch's
WEB_GENERATED, so a branch from before the change that edited one catches up
instead of stopping on a modify/delete conflict. gen-strings writes through a
temporary file (parallel test preloads), and --best-effort skips a page whose
catalog or schema is missing instead of failing bun install.

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

* chore(next): PR autofix at f2ef8f4

- regenerated schemas/settings-export/settings-schema.sources.sha256
- vp fmt webviews/scripts/pages/gen-strings.mjs
- vp fmt webviews/test/strings-build-output.test.ts

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

---------

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 new terminal tab spawns at its pane's grid, so its first frame is final

The Mac's new-tab request sent no size. The daemon then spawned the PTY at
the grid of whichever terminal reported last, anywhere
(client_sizing_state.rs creation_size), the shell printed its first prompt
at that width, and the pane snapped when the view's first report resized it.

newTerminalTab now sends cols/rows (NewTerminalGrid): the fitted grid of the
terminal the pane shows (Ghostty's own fit of the same content host), else
the content host size, backing scale, a live terminal's cell size and the
configured padding through Ghostty's formula (TerminalGridEstimate; Cmd-T's
New Tab page, a page or agent tab). A zoomed terminal does not count. With
neither known the daemon's fallback applies as before.

docs/shared-terminal-sizing.md: a Creation size section (who sizes a new
terminal, and why a later attach never draws a wrong frame for others).

* cmux NEXT: NewTerminalGridTests builds its padding from the public default

* perf-10x daemon: request-latency bench (raw, 10 clients, CLI; 1 and 200 terminals)

* cmux-next: clear five base reds in the cmux-next checks lane (#21981)

The ErrorJournal (1597dc9) and browser popup (fa8b3fc) commits left
five checks red on feat-cmux-next, so every PR into it fails "cmux-next checks"
and its web lane skips its tests. Comments and baselines only, no behavior change:

- Main-thread concurrency: the harvest loop runs in a detached utility task and
  the flush delay in the ErrorJournal actor's task, so neither is on main;
  reviewed concurrency-allow and wakeup-allow comments.
- No literal state roots: the app's ~/.acpmux/daemon.log is only read; a
  keep row in the baseline.
- Package conventions: lint:allow on BrowserPopupWindowGeometry (pure geometry).
- Type scale: BrowserPopupPanel.swift has no raw font sizes left; drop its row.
- Text focus: the popup address field sets focusRingType = .none (was #21963).

Motion tokens (SidebarCardTransition) stays with #21951 for its owner.

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

* cx-ncc.104: Debug toggle for the agent pane's in-session status (agent.sessionStatus) (#20953)

Recut of #20953 onto the current feat-cmux-next as one commit: the
In-session status Debug toggle, its sessionStatus page event, and
useSessionStatus() for the plan and recap cards. The base merge kept
both sides of the tunables list, the view's observations and the page
host's events (the sidebar preview variant landed meanwhile).

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

* acpmux: route/list says which routes serve the session's family (#21852)

* test(acpmux): route/list names a session's family and which routes serve it

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

* feat(acpmux): route/list says which routes serve the session's family

With a sessionId, _acpmux/route/list now returns the session's family and a
serves flag on each route (Route::serves), so the composer's Account menu can
list only the routes that can run this chat without copying that logic.

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

---------

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

* cmux-next: tunable registries list one entry per line (#21972)

* cmux-next: tunable registries list one entry per line (#21946)

AgentPaneTunables.all and SidebarTunables.all were single lines, so every pull
request that added a tunable conflicted with every other one there (14 open
PRs on AgentPaneTunables, 4 on SidebarTunables). Each entry now has its own
line in alphabetical order, so two additions insert different lines and merge
cleanly; TunableCatalog.all lists one module per line. Debug Settings shows the
agent-pane and sidebar tunables alphabetically; nothing else depends on the
order.

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

* Keep the TunableCatalog list as it was: CmuxNextApp may not grow

The one-per-line TunableCatalog added 20 source lines to CmuxNextApp, which
check-app-size.sh refuses. The conflict hotspots are the AgentPaneTunables and
SidebarTunables lists, which live in their own targets and stay one per line.

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

---------

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

* test(next): cover dictation auto-send prompt routing (#21169)

* test(next): cover dictation auto-send prompt routing

* fix(next): satisfy dictation test formatting

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com>
Co-authored-by: Lucas Ribeiro <lucascamribeiro@gmail.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextApp/Pages/PageFactory.swift
#	Packages/macOS/CmuxNext/Sources/CmuxNextPages/BackdropImagesPageProvider.swift
#	Packages/macOS/CmuxNext/Sources/CmuxNextSettings/Schema/HomeSettingsSchema.swift
#	Packages/macOS/CmuxNext/Sources/CmuxNextSettings/Schema/SettingsSchema.swift
#	Packages/macOS/CmuxNext/Tests/CmuxNextPagesTests/BackdropImagesPageProviderTests.swift
#	schemas/defaults/cmux-next.json
#	schemas/settings-export/settings-schema.sources.sha256
#	webviews/src/pages/home-channels/generated/strings.json
#	webviews/src/pages/settings/components/HostCards.tsx
#	webviews/src/pages/settings/generated/strings.json
#	webviews/src/pages/settings/hostCards.test.tsx
#	webviews/src/pages/settings/mockProvider.ts
#	webviews/src/pages/settings/ops.ts
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

high-signal review link / link is red on 27 of 27 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@greptile-apps

greptile-apps Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 0/5

[Medium impact] The changes affect window backgrounds and Home views across cmux.

The PR should not merge until the CI graph mismatch and the two outstanding repository-rule findings are fixed.

Findings

  1. P0 Test controls ship in production ▶
  2. P0 New helpers lack instance owners ▶
  3. P1 CI graph check fails ▶
  4. P2 Background adjustments use different centers ▶

Summary

Adds settings for video background playback, battery use, pane-aware framing, and animated Chief pictures.

  • Video backgrounds follow playback, app activity, and battery settings.
  • Paintings can frame around the panes or the whole window.
  • People can adjust and save the framing of a still background.
  • Chief pictures can move always, on hover, or never.
  • Settings and managed preferences expose the new background and picture choices.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Settings schema and parser] --> B[SettingsApplier]
  B --> C[DesignSettings]
  C --> D[Background playback and framing]
  C --> E[WebTheme]
  E --> F[Chief picture motion]
Loading

Reviews (2) · Last reviewed commit: "chore(next): PR autofix at a3c55b67ae33" · Reviewed by Greptile

Comment on lines +31 to +33
/// Tests: pins the playback settings, or being on battery, for this view.
public var playbackOverride: BackdropPlaybackSettings? { didSet { update() } }
public var batteryOverride: Bool? { didSet { update() } }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P0 Test controls ship in production

playbackOverride and batteryOverride add test-only controls to shipping BackdropVideoView.swift. The repository rule forbids new test/debug seams under Sources/. Remove these overrides. Tests should use @testable import for internal state and inject a real power-source dependency where needed.

Rule Used: Flag Swift files under a production Sources path (matching /Sources/ and not under /Tests/) that add a test-only or debug-only seam: a #if DEBUG (or other test-build-guarded) extension/member exposing internal state only for tests or a debugg... (source)

Comment on lines +5 to +8
enum BackdropPlaybackConfigParser {
static let path = ["appearance"]

static func parse(_ root: JSONValue, diagnostics: inout [SettingsDiagnostic]) -> BackdropPlaybackSettings {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P0 New helpers lack instance owners

BackdropPlaybackConfigParser is a new caseless enum used only for static helpers. BackdropPlaybackSettingsSchema adds the same pattern. The repository rule forbids new static-only namespaces. Make parsing an instance method on a constructable parser, and put the descriptors on the owning SettingsSchema type or a constructable schema provider.

Rule Used: Flag new ambient global state in production Swift: a top-level (file-scope) func used as API, a top-level mutable var or a stub class/struct holding a global flag/once-token, a caseless enum/empty struct used purely as a static func/static let namesp... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +395 to +397
// `appearance.backgroundAvoidPanes` off: the art frames itself in the whole window.
let visible = DesignSettings.shared.backdropPlayback.avoidsPanes
? BackdropVisibleSpan.uncovered(width: bounds.width, by: coveredColumns) : .whole

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Background adjustments use different centers

Turning off appearance.backgroundAvoidPanes makes drawing use .whole, but adjustable and visibleMiddle still use the space beside the panes. With a sidebar present, Adjust Placement derives its starting point and drag limits from a different center than the renderer uses. The picture can jump on the first adjustment and stop before reaching an edge. Use the same setting-aware span for drawing, the starting placement, and drag limits.

@cursor

cursor Bot commented Oct 20, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

"CmuxNextDaemonRecovery",
"CmuxNextDaemonService",
"CmuxNextDiff",
"CmuxNextFeed",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 CI graph check fails

CmuxNextFeed is added to CmuxNextAppTests.targets, but CmuxNextAppTests does not declare that dependency in Package.swift. The graph generator records direct dependencies, so regeneration removes this edge and the generated-files check fails. Remove the edge, or declare the dependency in Package.swift if the tests need it, then regenerate the graph.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

#22562 and #22566 supersede the 'keep clear of panes' part of this PR: the picture fills the window and readability comes from tint, blur and glass. Video playback and the battery settings may still be worth landing, rebased without that part.

@teamleaderleo

This comment has been minimized.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

owner: 01a15c08-af2e-7471-8e77-d62753886e4f

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Landing this through the backgrounds batch, #22033, in reduced form.

I'll close this PR with a pointer once #22033 merges.

Agent session: cosmic-gray

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by nx-settings on big-red

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Partially superseded by #22929 and #21784, which provide the current video engine, Chief picture transport, and aerial catalog. The stale branch is not reusable: the requested user settings policy is absent, while the old keep-clear framing conflicts with the current resize-invariant design in #22562. Closing this stale PR; the remaining video-policy slice needs a fresh owner/cut with current localization and the motion work stays owner-bound.

This branch was previously deployed

2 inactive (1 outdated) deployments
cmux-tui-merge-tree — fd58df32 Deployed Oct 20, 2026 by manaflow-cmux-pr-autofix[bot]
artifacts — a3c55b67 Deployed Oct 20, 2026 by teamleaderleo
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