Skip to content

CmuxNext: sidebar rows badge their logo's corner instead of a count - #20492

Merged
teamleaderleo merged 85 commits into
feat-cmux-nextfrom
op-next-icons/row-badge
Oct 14, 2026
Merged

teamleaderleo merged 85 commits into
feat-cmux-nextfrom
op-next-icons/row-badge

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

NEXT sidebar rows drop the count number. Leo said "Remove the count badges", and Lawrence (via Leo, 2026-10-14) said "maybe we can do unreads like this", pointing at T3 Code's thread list. Each row now:

  • Leads with an icon. Agent rows show their harness logo; terminal and browser rows show their kind icon; a user-chosen icon still wins.
  • Badges that icon's bottom-right corner, like an avatar's presence badge. The badge is one glyph on a disc of the row's backdrop, which cuts a thin ring out of the icon (it follows the hover and selection fills too). It shows the most urgent state: failed > needs approval > question > working > done/unread.
    • Every badge glyph is the static glyphs' 10 pt box in a whole-point cell, so the 1x pixel art lands on the grid, with a 1.5 pt cutout ring. The unread dot is 6 pt.
    • Unread or done is Codex's blue dot.
    • Working is the grey spinner, a still ring under Reduce Motion.
    • Waiting and failed use their Codex-like glyphs.
  • Shows the dim age of the last change on the right, instead of the count. That comes from the newest agent update or tab notification time. Rows whose items are only OSC 7501 program states or notifications without a time (created_at_ms absent on the tab) show no age; no other timestamp exists for them today.
  • Keeps the count readable elsewhere: the hover card has an "N unread" line, and the badge's tooltip and the row's accessibility label carry the count and the states.

Alternatives for SHOW LEO, under Debug Settings sidebar.rowBadge (live; debug.tunables set sidebar.rowBadge stack):

  • stack (Leo's idea): one item per pending thing in the trailing slot, most urgent first. Each item is a full chip: a tonal disc around the glyph, ringed by the backdrop. A chip tucks under the one before it only as far as its padding, so every glyph stays whole and the overlap reads as a stack of avatars. Up to five show by default; Debug Settings sidebar.rowBadgeStack picks 3, 5 or 7. The stack's tooltip says how many more ("2 more"), and the hover card has the count.
  • trailing: the previous count badge and trailing glyph.

SidebarModel.rowBadgeStyle pins a style for one sidebar. The tests use it, so two suites that measure the old trailing look keep doing so without racing a global setting. Classic is untouched.

Pending items: the row knows one merged activity state plus the unread count. The stack draws that state, then one done dot per unread item. Per-item states for each unread notification would need the daemon to send them; that is not part of this PR.

Stack cap and preview rows (Leo, 2026-10-14)

Leo asked for a cap of 5, adjustable in the same Debug menu, plus rows to preview 1 to 7 items without real sessions.

  • Cap: sidebar.rowBadgeStack (Debug Settings, sidebar): 3, 5 (default) or 7 glyphs. Items past the cap read in the stack's tooltip ("N more") and in the hover card.
  • Preview rows: sidebar.rowBadgePreview (Debug Settings toggle, off by default) adds a "Badge preview" section with seven rows holding 1 to 7 pending items in mixed states. They are placeholders: nothing selects, opens, drags or closes them.
  • Hover card: shows the unread notification's text when there is one, otherwise "N unread". The default hover-card template lists the new unread kind.
  • Review fixes: the dim time on the right comes from the row content and the layout's clock. It is left off when the second line already shows the last activity, and in minimal mode. Only the stack's tooltip names the overflow (the corner badge shows one glyph). The tooltip is the themed one.

In the app (show-leo)

A DEV build of this head at 2x zoom. In each image the left column is the default (one badge on the corner of the harness logo or kind icon) and the right column is the Debug stack alternative. Rows from top: an unread Codex chat, a working Claude chat, then 1, 2, 3 and 5 pending items (question + 2, error + 4, approval + 1, unread). The stack shows at most 3; the rest are in the tooltip and hover card.

Two-line rows (Latest Notification on):

rowbadge two line

One-line rows (the default once #20483 lands):

rowbadge one line

Tests

SidebarRowCornerBadgeTests, red on the test commit and green on the fix (fleet receipts below):

  • Rows lead with a logo or kind icon, draw no count badge, and show the time on the right.
  • The corner badge is the most urgent state: unread is the dot, and an error outranks three unread.
  • The hover card and the accessibility label carry the count.
  • Stack mode draws up to the cap of overlapping glyphs on one center line, most urgent first.

SidebarRowBadgeStackCapTests:

  • The stack shows five items by default, and the tooltip names the other two.
  • Debug Settings picks 3, 5 or 7, and the tooltip says "N more" only when items are left out.
  • The corner badge's tooltip lists every state without a "more" count.
  • The preview set shows 1 to 7 items, and its rows are placeholders without a close button.

Fleet receipts:

  • Red, test commit f149dcb0d8c5 (cmux-ci light step 93404e27aad06875ce621775): SidebarRowCornerBadgeTests fails. The count badge draws, there is no leading icon or time, there is no corner badge, and the hover card has no count.

  • Green, fix a609f41166a8 (step 3768816bf7dfb667f2c48c9d): SidebarRowCornerBadgeTests, SidebarRowGutterTests, SidebarStatusGlyphCenterTests and every other sidebar suite pass.

  • Green after the capture fixes, a67330d57e06 (step 7de8f24cfa1e81b301e5bce0): every suite passes (exit 0), including the new 10 pt pixel-grid and whole-glyph stack checks.

  • Release compile on a609f41166a8 (step aeea122b898eaa42220ceac6): BUILD SUCCEEDED.

  • Release compile on a67330d57e06 (step e782b2942693cf64a5563ca2): BUILD SUCCEEDED. An earlier run of the same head hung in CompileSwift CmuxNextApp on one worker for 80 min and was cancelled; the rerun passed in about 8 min.

  • Red, stack-cap test commit 075b07cc91af (step fbb11c2c): SidebarRowBadgeStackCapTests fails on behavior. The stack shows 3 items, not 5, and the preview set has no rows.

  • Green on 442275e81fe1 (light step 369f28a41bca248bd8c8f635): SidebarRowBadgeStackCapTests, SidebarRowCornerBadgeTests and 291 other suites pass. Two reds, neither from this PR:

    • AgentChatFromHomeTests fails on base since f9490028b939. The docked Home became the default, so TopPages.show(.home) returns nil, and the test still expects the classic Home page.
    • The step's same-tree guard exits 1 because the worker lacked this head's cmux-tui tree, so the daemon suites skipped.
      The corner-tooltip test arrived with the review fixes, not on a red commit first.
  • Release compile on 442275e81fe1 (isolated step d2ab0ef88e46b631a4ec4300): BUILD SUCCEEDED.

  • Settings exports on 442275e81fe1 (exclusive step a8dd8739b4dfd32d4d5e1222): the regenerated MDM and schema files match the branch byte for byte.

Changelog

  • Changed: Sidebar rows in cmux NEXT show no unread count; each row leads with its agent's logo or its kind icon with a small status badge on the corner (blue dot for unread, spinner while working, the waiting or error mark when it needs you), and the right side shows how long ago it changed. The count is in the row's hover card.

Showcase

showcase
showcase

Open the live gallery

🤖 Generated with Claude Code

teamleaderleo and others added 21 commits October 13, 2026 20:14
…l and theme-colored

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

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

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

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review: StatusIconSet.still drew the working glyph for an agent that reports
progress, dropping how far along it is. That ring never moves, so the Static
set keeps it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…icons/static-status

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextDesign/StatusIndicator/StatusIconSet.swift
Red: StatusIconSet has no codex case, there is no spinner or unread
tint, and the web sheet has no codex glyphs (shieldLine, info, dot).

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Red: with motion off, the Codex-like set turned every arc into the
still work ring, so a busy page or command loader read as agent work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only working without progress becomes the still ring when nothing
animates; a busy loader keeps its stopped arc. The glyph generator now
checks that every set picks a glyph for every state from its own set or
the static set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex's blue moves to --cmux-status-unread in webviews/src/theme, so
the generated glyph table holds no blue literal (check-system-blue).
At 2x the still work ring and the error ring differ only by the i,
16 whole pixels, and they differ in color too; the test says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Red: the default is still symbols.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gate.sh runs split, newterm, split with a slow host, new-tab and cold new-tab proofs on the GUI host and fails on a lost key; typeahead-compare.py counts keys sent by terminal id; creation-hold-proof.py is superseded (named in the gate header)
Leo 2026-10-14, a team decision: the grey spinner (a still ring under
Reduce Motion), Codex's blue done dot, muted line glyphs and a red
line i for errors, for everyone. Symbols, Static and the other sets
stay as Debug alternates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A literal color in a page stylesheet trips the theme-colors ratchet, so
the blue moves to STATUS_UNREAD in webviews/src/theme/statusColors.ts
and the generator writes it as the fallback of --cmux-status-unread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- vp fmt webviews/src/agent-session/acpmux/modelPickerLayout.ts

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38521104749
…e line or two

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… 6 pt done dot

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd draw no count

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

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

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

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

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

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Important

Review skipped

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

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

⚙️ Run configuration
  • Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 5014501d-83a0-4c25-89dd-82310dbc98e7

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

Use the checkbox below for a quick retry:

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

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

❤️ Share

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

@blacksmith-sh

blacksmith-sh Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Found 14 test failures on Blacksmith runners:

Failures

Test View Logs
GitHub-hosted runner label: .github/workflows/
pr-red-checks-comment.yml:16: runs-on: ubuntu-24.04
View Logs
test_every_mac_job_reads_the_placement (main.CmuxNextWiring.test_every_mac_job_read
s_the_placement) (job='cmux-scheme-compile')/
test_every_mac_job_reads_the_placement (main.CmuxNextWiring.test_every_mac_job_read
s_the_placement) (job='cmux-scheme-compile')
View Logs
test_every_mac_job_reads_the_placement (main.CmuxNextWiring.test_every_mac_job_read
s_the_placement) (job='daemon-test')/
test_every_mac_job_reads_the_placement (main.CmuxNextWiring.test_every_mac_job_read
s_the_placement) (job='daemon-test')
View Logs
test_every_mac_job_reads_the_placement (main.CmuxNextWiring.test_every_mac_job_read
s_the_placement) (job='generated-files')/
test_every_mac_job_reads_the_placement (main.CmuxNextWiring.test_every_mac_job_read
s_the_placement) (job='generated-files')
View Logs
test_every_mac_job_reads_the_placement (main.CmuxNextWiring.test_every_mac_job_read
s_the_placement) (job='release-compile')/
test_every_mac_job_reads_the_placement (main.CmuxNextWiring.test_every_mac_job_read
s_the_placement) (job='release-compile')
View Logs
test_every_mac_job_reads_the_placement (main.CmuxNextWiring.test_every_mac_job_read
s_the_placement) (job='swift-test')/
test_every_mac_job_reads_the_placement (main.CmuxNextWiring.test_every_mac_job_read
s_the_placement) (job='swift-test')
View Logs
test_every_shadow_job_is_compare_only (main.ShadowJobTests.test_every_shadow_job_is
_compare_only) (workflow='cmux-tui-artifacts.yml', shadow='macos-cross-runtime-x86_64')
/test_every_shadow_job_is_compare_only (main.ShadowJobTests.test_every_shadow_job_i
s_compare_only) (workflow='cmux-tui-artifacts.yml', shadow='macos-cross-runtime-x86_64'
)
View Logs
test_every_shadow_job_is_compare_only (main.ShadowJobTests.test_every_shadow_job_is
_compare_only) (workflow='cmux-tui-artifacts.yml', shadow='macos-cross-shadow')/
test_every_shadow_job_is_compare_only (main.ShadowJobTests.test_every_shadow_job_is
_compare_only) (workflow='cmux-tui-artifacts.yml', shadow='macos-cross-shadow')
View Logs
test_fork_pull_requests_into_manaflow_ai_never_reach_an_owned_runner_label (main.Fo
rkRunnerRoutingTests.test_fork_pull_requests_into_manaflow_ai_never_reach_an_owned_runn
er_label)/
test_fork_pull_requests_into_manaflow_ai_never_reach_an_owned_runner_label (main.Fo
rkRunnerRoutingTests.test_fork_pull_requests_into_manaflow_ai_never_reach_an_owned_runn
er_label)
View Logs
test_main_full_suite_admission_compiles_on_the_seed_pool_and_xcode (main.Wiring.tes
t_main_full_suite_admission_compiles_on_the_seed_pool_and_xcode)/
test_main_full_suite_admission_compiles_on_the_seed_pool_and_xcode (main.Wiring.tes
t_main_full_suite_admission_compiles_on_the_seed_pool_and_xcode)
View Logs

...and 4 more test failures. View all on Blacksmith

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

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

CI fast guards passes on 442275e81f (https://github.com/manaflow-ai/cmux/actions/runs/38556818194). This covers only the fast guards, not CI: CI's result is the ci-status check, and the CI failure attribution comment names any failing test.

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

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

CI passes on 442275e81f (run 38556818194 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.

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

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

cursor Bot commented Oct 14, 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.

teamleaderleo and others added 3 commits October 14, 2026 01:18
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A time today ("1:26 AM") measured a few points short of its text in the compact row's
trailing slot and drew "1:26…".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… switch back is saved; every chat gets the cmux MCP server (red) (cx-wx93i)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
lawrencecchen and others added 21 commits October 14, 2026 04:02
…20534)

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

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

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

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

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

* chore(next): PR autofix at 7be0d24

- regenerated schemas/defaults/cmux-next.json

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
* webviews: test that every popup draws one hairline edge

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

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

* webviews: popups draw one hairline, not two

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

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

* chore(next): PR autofix at 1cc6616

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* chore(next): PR autofix at 54dd59d

- regenerated schemas/defaults/cmux-next.json

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

---------

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(clears the concurrency red) (#20485)

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

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

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

* fix: keep PaneAnimationsSetting internal to CmuxNextSettings

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

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

* chore(next): PR autofix at c2bdb98

- regenerated schemas/defaults/cmux-next.json

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

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

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

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

* chore(next): PR autofix at 7cf07e3

- regenerated schemas/defaults/cmux-next.json

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: cmux-autofix[bot] <cmux-autofix[bot]@users.noreply.github.com>
…hover card (#20483)

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

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

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

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

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

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

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

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

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

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

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

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

* chore: regenerate the settings exports on the merged base

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

* chore: refresh the defaults snapshot on the merged base

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

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Review fixes: the compact row's dim time comes from the layout's now and
skips rows whose second line shows the last activity or minimal mode; the
tooltip names what the stack left out only for the stack; preview rows
have no close button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…icons/row-badge

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextDesign/SidebarCustomization.swift
#	Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Logic/WorkspaceHoverCardContent.swift
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 14, 2026 11:28 — with GitHub Actions Inactive
@teamleaderleo
teamleaderleo merged commit f90377f into feat-cmux-next Oct 14, 2026
121 of 136 checks passed
@teamleaderleo
teamleaderleo deleted the op-next-icons/row-badge branch October 14, 2026 12:18
teamleaderleo added a commit that referenced this pull request Oct 14, 2026
…20536)

* cmux-next Home: vendor MessagesLab pin 258b385 (main 63c98d9): per-view render scale (cx-3azz6)

Pin 258b385 (cx-3cb view-scale, code 59e8e94): every MessagesWindowView renders
at its own renderScale (row bitmaps in a cache keyed by row and scale, soft-edge
masks, preview heights and window-pixel snaps, compose bar, header, border,
morph, Markdown overlays, long-text tiles, Sizing.textBalloonWidth); the guard
of setRenderScale is per view. Our cmux appliedRenderScale WindowView patch and
its README row are gone (sync-messageslab.sh --check: 258b385 + 20 patches).

Home follows each pane's own window (Cmux/PaneHost+Scale.swift,
ChatController.followWindowScale): setRenderScale(window.backingScaleFactor)
when the pane goes into a window, when its window view is installed, when it is
shown again (HostView.viewDidUnhide) and on every backing scale change, never
skipped by a compare with Fixture.renderScale. ScaleKeeper (moved there from
PaneHost.swift) walks each pane's trees at that pane's renderScale; the video
badge and debugRowFrames' balloon use the pane's scale. README: the process-wide
items that remain (Fixture.inactive/lightAppearance, Metrics.current,
LongTextCenter.handler, DisplayScale.colorSpace, the screen-change handler) as
known limits of one view per pane.

home-scale-change.py: the return to 2x is now checked (the virtual display
ends, the window goes back to the 2x display), and at each scale one frame
after the change the view, cells, compose bar and header are at the window's
scale and no visible row shows an old-scale bitmap (debugGeometry scale fields,
MessagesLabHomeView+ScaleDebug.swift); snapshots at 1x and 2x checked for crisp
incoming balloon edges (home_pixels.check_crisp).

* home-scale-change.py: end with endAgents after the quit fixture, as the tagged-run rule (cx-3azz6)

* Home scale check (review): old-scale bitmap count on shown text rows only, time to clear printed, the 2x return proves the 2x snap (cx-3azz6)

* Home scale check: report image pixels per point of the visible text row bitmaps (cx-3azz6)

* cmux-next sidebar: Comfortable density (sidebar.density comfortable; Compact stays the default)

Comfortable keeps one line per row on taller rows with more room, at the
system sidebar's medium size: rows, tab rows and group headers 24 -> 28 pt,
headers 22 -> 26, horizontal inset 6 -> 8, section gap 8 -> 12. Pinned bands
and tiles take the same row step (SidebarDensityTokens is the one source).
Compact and Expanded metrics are unchanged. Text size and weight unchanged.
Adds sidebar.density.comfortable (palette, sidebar Options), the setting
choice, and its strings in every language.

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

* cmux-next sidebar density: generated exports for sidebar.density comfortable; sidebar-density-live.py GUI proof

Settings schema and MDM manifests regenerated on the fleet (run-swift-testing-suites
export-settings step 48e261cdad5495d135053f0c, aws-m4pro-6, Xcode 26.6). The action
exports (action-surfaces.json, actions.md) add sidebar.density.comfortable after
compact; the Options folder renumbers.

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

* cmux-next sidebar density: review fixes (doc comments; proof script reports a missing Options folder, checks the row inset and pinned/section heights)

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

* Home scale check: the move to 1x is a recorded upstream known limit (old 2x bitmaps up to 0.32 s, bound 0.5 s); README names RowCell.configureScoped (cx-3azz6)

* cmux-next action exports: regenerate on the merged head (base drift from tab groups and Move to Group, plus sidebar.density.comfortable)

Values are the exporter's own output from check-action-surfaces step
5e8d777e0ea4439015191829 (aws-m4pro-8) at 5770b1a7007c.

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

* sidebar-density-live.py: terminal ids from the action result, a done row that stays unseen, spinner state match (working(progress: nil))

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

* Home: a screen's colour space change keeps the row bitmap cache (no removeAll, no setInactive toggle); a window back on 2x finds its 2x bitmaps (cx-3azz6)

* cmux-tui move-tab-to-split/-column keep_focus: an agent-opened split does not take focus; subagent panes use it (cx-3hw0i)

Daemon: move-tab-to-split and move-tab-to-column take keep_focus
(tab-split-keep-focus-v1). With it the target screen keeps its active pane,
the active screen and workspace stay, the new pane is least recently active
and gets no focus stamp. When the focused pane closes in the move, the new
pane takes focus as before. keep_focus with respawn is a bad request. Spec,
sdk-schema, protocol.md and regenerated bindings.

App: DaemonConnection.moveTabToSplit/moveTabToColumn(keepFocus:) send it only
to a daemon that advertises the capability; TabMoves.toNewSplit (and its
no-room fallback toNewColumn) and tab.moveToNewSplit (internal keep_focus
argument) carry it; an unfocused subagent open (origin script) passes it.
The app no longer focuses the chat again after the move (AgentSubagentPanes;
that refocus did not keep focus either, proof 1013-222711 kept: False).

Behavior test: wire test a_keep_focus_split_leaves_the_focused_pane_focused
(split, stacked split, column, respawn refusal, plain drop still focuses).
Review: subagent, no P1; P2 column path fixed here; skew refocus and the
rest in cx-3hw0i.1.

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

* scripts: agent-pane-live.py subagent-panes types real keys while the panes open (CMUX_LIVE_TYPE_KEYS=1, nx-remote --needs input); every key must land in the chat's composer (cx-3hw0i)

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

* test: a long chat opens on its tail and pages older turns in on scroll-up (cx-2lv1k)

Red: nothing calls loadOlder, so scrolling up never reaches the first turn.

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

* agent pane: page older turns in as the reader scrolls up (cx-2lv1k)

The pane opened a chat on its newest 400 records and never asked for more:
nothing called loadOlder, and it read acpmux's paging flag as `more`
where acpmux sends `hasMore` (so missed-event catch-up also stopped after
one page). Now the transcript asks for the page before its oldest record
whenever the view is within two viewports of it, one request at a time
with a short backoff after a failure, and attach's hasMore says when the
start is already loaded. The reader's row holds its place as pages land,
at offset 0 too, and when a page completes a turn cut at its edge.

Each older page holds whole turns (the records before its first prompt
wait for the next page) and folds on its own above the held rows, so a
page costs the same however much history is loaded: re-folding every
held event cost 105 ms a page at 45k events and grew without bound.
Rows a rebuild makes again unchanged keep their objects, so drawn heights
and memoized rows survive it.

Opening asks for at most 128 KiB and each older page 256 KiB (maxBytes),
so a remote session's open stays small however large its tool output.

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

* webviews: settings page strings on the merged head (sidebar.density help with Comfortable)

Output of webviews/scripts/pages/gen-strings.mjs settings at the merge.

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

* cx-ebm.59.3: red test, workspace.open_url uses the frontend browser path when the app renders browsers

Behavior test at the daemon socket: a client that declares
frontend-browser-tabs-v1 gets a frontend browser tab on the right side,
the next link reuses it, and a same-key retry after a selection change
answers the first result.

* test: a paged-in turn is timed from its own prompt (red)

An older page folded while the newer rows were still held, so a turn's
"Worked for" counted from the newest prompt and showed 0s.

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

* agent pane: time a paged-in turn from its own prompt (cx-2lv1k)

An older page folds below the held rows, so a turn ending there now
totals only the rows before them: its "Worked for" no longer counts
from the newest prompt. Review follow-ups: a steer opens no turn, so a
page never splits at one; a fork only an older page holds is kept;
a rebuild keeps an earlier row object only when its plain fields match
too; and a page in flight for the session before no longer blocks the
next session's first page.

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

* cmux-next exports: regenerate on the merged head (sidebar.density comfortable over feat's freeModel, chromeStyle and paneAnimations)

The merge kept feat's copies of the generated exports; these are the
exporters' own output at 58d30c75b854: action-surfaces.json and actions.md
from scripts/measure/export-action-surfaces.sh (cmux-ci step
b47b7cd1c57a48cea6be557b, aws-m4pro-6; links.json and
cmux-shortcuts.generated.json unchanged), settings schema and MDM from
run-swift-testing-suites.sh export-settings (cmux-ci step
ce51138281d5838d03e059f7, aws-m4pro-8, Xcode 26.6).

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

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

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

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

* chore(next): PR autofix at 6b20321bb088

- regenerated schemas/defaults/cmux-next.json

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

* sidebar-v2 S6: live TUI proof on the sidebar document (red)

scripts/cmux-next/sidebar-tui-doc-live.py drives a real headless daemon and a
real cmux attach TUI in tmux: pin/unpin from the TUI rail must land in
sidebar.get, and a CLI sidebar.move/sidebar.pin must change the drawn order.
Red on the base: the TUI has no pin and draws the session order.

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

* sidebar-v2 S6: the TUI reads and edits the sidebar document

With a daemon that advertises sidebar-doc-v1, the TUI draws the document's
workspace order (Pinned first, sections in region/key order, group members
after their group), read with sidebar.get and kept current by the
sidebar_item/sidebar_section/sidebar_doc changes of a session.events stream.
Both are cmux.protocol/2 lines on the existing session connection; a gap,
a refused request or the stream's end refetches on a worker thread.

Writes go to the document: the workspace context menu and the rail's p key
pin or unpin (sidebar.pin/sidebar.unpin), and a workspace drop is one
sidebar.move before (else after) the drawn neighbor. An older daemon keeps
move-workspace and shows no pin. The workspace pin flag is never written.

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

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

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

* cmux-next: window, group, section and agent-archive verbs on palette, CLI, MCP and menus; Close Window in the File menu (cx-5qewo)

Lawrence 2026-10-14 (audit answers): add all.
- window.closeOthers "Close Other Windows" (cmux app close-other-windows, File
  menu); closeAllWindows is a CLI verb and MCP tool now; Close Window is in the
  File menu (Ctrl-Cmd-W).
- workspaceGroup.closeOthers "Close Other Groups" (destructive; each other group
  closes as Close All Workspaces in Group) and workspaceGroup.duplicate
  "Duplicate Workspace Group" (a duplicate of each member, then one new group).
- sidebar.section.closeWorkspaces "Close All Workspaces in Section" (destructive,
  Pinned too) and sidebar.section.newWorkspace "New Workspace in Section".
- agentPane.archiveChat is a CLI verb (cmux agent archive-chat --target pane:...)
  and MCP tool: ActionRegistry.targetContext lets an explicit target bring its
  own facts (an agent chat's pane: agentPaneFocused).
- The no-undo prompt says incognito workspaces cannot be reopened.

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

* cx-ebm.59.3: workspace.open_url makes frontend browser tabs when the app renders browsers

A session with a connected client that declared frontend-browser-tabs-v1
(the cmux app) gets the link as a frontend browser tab through the
new-frontend-browser-tab commit (selected, after the pane's selected tab,
keyboard focus kept); reuse navigates a frontend tab through its record
(update-frontend-browser-tab URL semantics). The daemon-engine path stays
when no such client is attached. The request key records its result in
the mutation ledger: a same-key retry after a selection change answers
the first result; the same key with another URL is idempotency.conflict.

* cmux-next: WorkspaceSetVerbs imports CmuxNextBridge; proof script submits the palette and names the MCP window (cx-5qewo)

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

* cx-ebm.59.3: open_url review fixes: replay a committed frontend create, ignore page clients

A retry after the frontend create step committed but before the ledger
entry answers that tab (reused:false) instead of reusing and navigating
it. A page relay or page WebSocket that declares frontend-browser-tabs-v1
does not switch the session to frontend tabs.

* cx-ebm.59.3: rustfmt

* sidebar-v2 S6: poison-tolerant locks in the sidebar read model

The crash ratchet refused 17 new .unwrap() calls on the sidebar document
locks. A poisoned lock still holds a usable read model, so take it.

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

* cmux-next: slice 2 review fixes: pure target facts only when needed, section item index, Close Other Groups and Duplicate Group in the group's window, group undo restores groups, app headers drop the section workspace verbs (cx-5qewo)

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

* sidebar doc: indexed reducer, in-place commits, gap positions for personal rows

The sidebar document keeps indexes (id -> item, (section, parent) ->
ordered (order key, id) set, workspace refs, placement refs, per-section
counts). Ops change it in place through a journal of first old copies;
only the invariants of the touched rows are checked (check.rs), a
refusal restores them, and the changed rows and the inverse come from
the journal. No op copies, sorts, checks or compares the whole document.

The daemon leases its cached document for a commit instead of cloning
it; a nested writer marks the lease stale. The old-store sync and closes
run as one Batch (one revision). write_back walks display order from
the changed items and places a personal row after its neighbor.

Personal workspace rows: positions have gaps (row_position.rs); a move
writes the moved row and the group slots at its old and new place, and
respaces in one statement only when no integer fits. workspace.place
restates only the placements whose index moved; placement snapshots read
the public ids once; many queued changes are one pending row; the
topology workspace order is one UPDATE ... FROM json_each.

The app mirror keeps its shown document in step (pending intents undone
by their inverses, the batch applied, intents replayed) instead of
copying it per call. Section scroll (fixed|flow, default by kind and
region), SectionPatch.scroll, url body icon_url and ItemPatch.icon_url
(an old extra.icon_url moves into the body at add and at open).

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

* sidebar doc: section scroll and max_rows in the spec, cmux sidebar section-scroll / section-max-rows

SidebarSectionSnapshot gains scroll (fixed|flow, default by kind and
region) and documents max_rows (grow, then scroll inside); the url body
documents icon_url; section.add/update and item.update describe the new
patch fields. CLI: `cmux sidebar section-scroll <section> fixed|flow`
and `cmux sidebar section-max-rows <section> <1-50>|none`, each one
sidebar.section.update with a partial patch.

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

* scripts/cmux-next: sidebar-doc live checks for scroll, max_rows, the CLI verbs and url icon_url

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

* cmux-next: regenerate action exports for the window, group, section and archive verbs (cx-5qewo)

scripts/measure/export-action-surfaces.sh at bb768ba8c90c (cmux-ci step 4d532d471d146d1a54780d98, aws-m4pro-2: 13 parity + link tests passed).

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

* spec: regenerate SDK descriptors and app-host generated types (section scroll)

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

* sidebar doc store: box the cached document (clippy large_enum_variant)

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

* sidebar doc: rustfmt

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

* sidebar-v2 S6: review fixes for the TUI sidebar document client

- A drop on the dragged row's own slot is a no-op; a slot on the border of
  two containers (end of Pinned / start of the machine list, end of a group
  / next loose row) keeps the workspace in its own one, else joins the one
  above. Before, such a drop unpinned the last pinned row.
- The stream restart cap resets only on a batch that changed the document,
  and a reopened stream resumes from the last item's cursor (deltas, not a
  full snapshot); a refused cursor reopens without one.
- Only a structured refusal turns the document off for the connection; a
  timeout retries at the next tree refresh. An unanswered stream open and a
  dropped stream are released with stream.cancel.
- Waiting protocol/2 requests wake when the connection shuts down.
- A batch with several sidebar_doc markers spans the lowest previous to the
  highest revision (no false gap).
- spec/inventory.json and check-spec-inventory.py list the PinWorkspace menu
  action (direct: sidebar.pin or sidebar.unpin).
- The live proof also checks the drawn order differs from list-workspaces
  after a pin, a mouse drag into Pinned, and a drop on the own slot.

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

* test(optchat-chief): a Chief's profile, tool grant and self-edit through its brain (red) (cx-ebm.60.5)

Behavior test against a real headless cmux-tui daemon and the real
optchat-chief mcp server: the person makes a Chief with a role,
personality, instructions, tools [tell] and self_edit [avatar, role];
the brain renders the profile in the cloud brain's format, offers only
the granted tools plus profile, refuses spawn at the tools socket, and
changes only its allowed fields through its own bound connection.

Red: the brain has no chief_profile module (no profile, no grant).

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

* cmux NEXT: a restoring daemon never leaves a page stuck at "not connected"; show the real cause (cx-4bd8m)

After Austin's update the owner spent minutes restoring a 10.7 GB session.
server ensure gives up at its 2-minute restore bound (server.restore_timeout);
each such failure spent the first-connect / reconnect timed retry budget, and
past the budget the loop waits for an event only. A restore that ends behind
the same socket fires none, so the app could stay "not connected" next to a
ready daemon until it was reactivated. A restore timeout now keeps the budget:
the next ensure waits for the same owner again (the daemon bounds each wait).

Every surface that said only "cmux-tui is not connected" now says why:
DaemonService.notConnectedReason (still restoring, the last connect error, or
the drop reason) feeds the App Store banner, app sections and app action
refusals (AppsUnavailableReason.notReady) and command failures.

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

* fix(cmux-next): the bookmark name follows the page title while the tab stays on the bookmarked host; live check: real HID click on the star, wait for the late title, the old name check expects the title (cx-x9o52 follow-up)

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

* cmux NEXT: wake the daemon retry loop on the socket and ready marker before the first connect (cx-4bd8m)

Review P1: the retry wake watched the daemon socket only after a first
successful handshake, so a first connect that spent its timed budget during a
long owner shutdown or restore (closed connections, launch failures, identify
timeouts) waited for app activation only. The loop now watches the socket the
last launch used from the start, and the connection also watches the owner's
ready marker <socket>.websocket.json, which it writes once its restore is done,
so a restore that ends behind the same socket wakes the loop too.

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

* cx-ebm.59.3: no unwrap on the client registry lock (crash ratchet)

* feat(optchat-chief): each Chief's profile, tool grant and self-edit tool in its brain (cx-ebm.60.5)

The brain reads its own record from the daemon's Chief registry at host
start (chief.profile.get on its own bound connection):

- prompt: the profile goes after the base prompt, the person's
  instructions and the channels paragraph, in the cloud brain's format
  (backend chief-brain/prompts.ts): '## This Chief (the rules above come
  first)' with Name/Role/Personality, then '## Added instructions'. Turns
  and compactions share it (one cache entry); subagents do not get it.
  The default Chief with no profile text renders nothing, so its prompt
  stays byte-identical. Read once per host start: text changes reach the
  prompt at the brain's next start (the picture shows at once).
- grant: the optchat MCP server asks the host's tools socket for the
  granted model tools at tools/list (zoom and date always; spawn, tell
  when granted; profile when self_edit is not empty). The tools socket
  refuses an ungranted spawn/tell/profile by name (the chief launcher of a
  non-Claude harness asks the socket directly). A spawn whose subagent
  harness is outside harnesses starts nothing.
- profile tool (schemas/chief-profile/tool.json): reads the record, or
  changes display_name/role/personality/instructions and the picture
  (avatar_path, sniffed PNG/JPEG/GIF/WebP <= 1 MiB; clear_avatar) through
  chief.profile.update / avatar.set; the daemon enforces self_edit and the
  refusal names the fields. Also 'chief profile [JSON]' from a shell.
- no record (cloud source, older daemon, a Chief started before it had a
  record): every tool, no profile, as before. A failed read is fatal only
  for a non-default Chief (its grant must not open up).

Green: tests/profile_daemon.rs against a real headless daemon and the
real mcp server.

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

* sidebar doc: review fixes; head room before the first personal row; order index

Review (subagent) fixes:
- A Batch dropped without finish (an error mid-sync) restores its rows,
  so the leased cache never keeps a change the transaction rolled back.
- section.add into another region gets that region's default scroll
  (one rule in place_section for add and move).
- The app mirror undoes and replays pending intents only when an intent
  goes or rows apply; before the first load it only drops the intent.
- The old-store sync queues one pending row per change (the open path
  queues outside a journaled commit); queue_changes is for commits only.
- The cache is cleared at registry open; duplicate rows keep the first;
  reduce::apply and Outcome (no caller) are removed.

Personal rows: a renumbering (and an empty list) starts at 2^40, so moves
and creations at the top find room before the first row instead of
renumbering every row each time (sidebar.move to a machine section's top
went from 3.4 ms to 1.1 ms). Row moves use the new order index
personal_workspaces_order (position, session_id, workspace_key) with
range scans. Live check: a workspace.place writes only the moved row.

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

* fix(optchat-chief): review fixes for the Chief profile in the brain (cx-ebm.60.5)

- A bound participant with no record is refused by the registry as
  chief_registry_caller, not chief_not_found: typed LoadError (NoRecord,
  NoRegistry, Other). No record or no registry keeps every tool, so
  hq-ec's instruction-file VM Chiefs start as before; any other failure is
  fatal only for a non-default Chief. The behavior test now covers a
  Chief with no record.
- A harness-refused spawn no longer reports its queued ids as lost to a
  host restart: they are failed with the refusal when the brain dequeues
  them.
- profile: a refused picture after a committed text change says which
  fields did change.

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

* debug.key palette reply: report the query and whether its rank is still running (cx-9ce.45 follow-up)

nxdog100 read palette_rows right after the last debug.key and saw the previous
query's rows (a typed query ranks off the main actor), so shortcut search looked
broken. The reply now carries palette_query and palette_ranking.

* ci(cx-cmux-cua): media tour clears the last run's base worktree (#20578)

* fix(cx-cmux-cua): tour job clears the last run's base worktree

Self-hosted runners wipe RUNNER_TEMP but keep .git, so the next tour on the same Mac failed with 'missing but already registered worktree' before building (5 of 9 recent UI PRs).

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

* fix(cx-cmux-cua): take a locked stale base worktree too

A job killed mid-add leaves the entry locked; prune skips it and one --force still exits 128. Cleanup prune no longer adds a second red step when checkout failed.

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

---------

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

* webviews: red test: IME composition keys reach page handlers (History search Escape/ArrowDown) (cx-dd5b4)

* webviews: one IME key layer: keys an input method uses never reach page handlers (cx-dd5b4)

The desktop layer (every first-party page) now installs a capture-phase window
listener that stops keydown/keyup with isComposing or keyCode 229 (WebKit's
commit key after compositionend). The input method still gets the key (no
preventDefault). Fixes Escape clearing a search, ArrowDown moving a list and
Return submitting while a Japanese/Chinese/Korean/Vietnamese composition or a
dead key is active, in all ~35 fields whose key handlers had no composition
check.

* terminal: NSTextInputClient matches upstream Ghostty exactly (cx-dd5b4)

- every commit outside keyDown (IME commit on click/focus change, emoji
  picker, dictation) is a key event with text and no physical key, as
  upstream #13817 (was ghostty_surface_text_input unless marked text);
- insertText needs an event (NSApp.currentEvent), as upstream;
- UnicodeHexInput surrogate pairs join across two insertText calls, a lone
  trail is dropped (upstream #13737);
- markedRange/selectedRange answer NSRange() when empty and characterIndex
  answers 0, as upstream (input methods are tuned to these answers).

* ci: IME input guard: no new key synthesis from characters, terminal text only through its NSTextInputClient, every page entry has the IME key layer (cx-dd5b4)

* IME review fixes: ProseMirror and Monaco keep their own composition keys; composer submit ignores the WebKit commit key; guard refuses ghostty_surface_text_input everywhere; workflow paths (cx-dd5b4)

Review (subagent): P2 x4 fixed. ProseMirror clears its Safari after-composition
guard on the 229 keydown and Monaco tracks it for the press-and-hold accent
menu, so targets inside those editors still get the keys; MarkdownField's
submit now tests isImeKeyEvent (isComposing or 229). Note: a dictated newline
outside keyDown is now LF (upstream key path), not CR.

* webviews: mark the IME key layer listeners ui-allow (not widget key handlers) (cx-dd5b4)

* CmuxNext new tab: the daemon's raw tab-added of a conversation tab replaces its provisional stand-in in the same apply (Cmd-T showed both, then closed one and grew the other)

Cmd-T and + show a New Tab page at once as a provisional tab (pending_tab_*). The
daemon makes the conversation tab and sends a raw tab-added for it with no
transaction; the copy that carries the create's transaction and the reply trail
it. The store listed the daemon's tab BESIDE the provisional one until then, so
the strip showed two cells and, a sync later, closed the stand-in on the spring
while the store's tab grew in (frame strips: nxrevert-v1, b35b1c6, 358d010c).

ProvisionalTab.matched: a transaction-less tab-added of a conversation tab is the
tab of the oldest pending create intent in the same pane with the same record
(agent session host and session, page, route) that has no tab yet; the intent
notes it as created, so the store drops the stand-in in the same apply and the
strip's handover (TabStripModel.successors) gives the store's tab the cell in
place. Two quick Cmd-T pair in order; a tab of another kind or record never takes
a provisional tab's place. (hq-1d Cmd-T fix)

* CmuxNext new tab: a transaction-less tab-added replaces a provisional tab only when the pane has exactly one pending create without a tab, same record, and the tab is the pane's newest (hq-4e review)

* CmuxNext tabs: a new tab is there at once at its slot and full width, the strip makes room in the same frame (no grow-in by default; a drag or drop keeps its springs)

* CmuxNext tabs: a tab inserted at once also ends the strip's closing tabs in that frame (close then Cmd-T: no jump over a shrinking tab; hq-4e review)

* test: live check for a bottom-edge drop split (cx-jin7d) (#20542)

* test: live check for a bottom-edge drop split (cx-jin7d)

pane-edge-drop-e2e.py: the golden edge.new-split-below case (H split, tab
dragged at hand pace to 90% down the other pane) splits below. Red on feat
DEV bc7d0c5d (the 0.25 s split dwell joined the pane), green on 3158c157
(cx-imr7n). split-dwell-e2e.py sets the dwell it tests, now opt-in.

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

* chore(next): PR autofix at b835ce7daa37

- regenerated schemas/defaults/cmux-next.json

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

* test: tighten the edge-drop checks (review)

pane-edge-drop-e2e.py: the new pane must be neither source nor target, sit
under the target and hold the one moved tab; a failed debug.mouse call stops
the drag and releases the button; 10 s for the split; drop dead helpers.
split-dwell-e2e.py: put tabDrag.splitDwell back to its default afterwards.

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

---------

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

* test(cmux-tui): an agent caller cannot make, delete or widen a Chief (red, cx-ebm.60.9)

* fix(cmux-tui): cmux chief refuses make, delete and grant changes from an agent caller (cx-ebm.60.9 layer 1)

* sidebar-v2 S6: drops land where dropped; fallback read after repeated stream ends

hq-2d decisions:
- A drop lands where it is dropped. A workspace row's name row (upper half)
  is the slot before it; its second row (lower half) and the gap under it
  are the slot after it, on that row's side, so on a section border the
  pointer picks the section: the lower half of the last pinned row pins.
  A drop on the dragged row's own slot stays a no-op.
- After more stream ends in a row than the restart limit, every turn of the
  app loop (at least each idle wait) reads the document again, at most once
  per second, and opens the stream with it, so the drawn order never stays
  old. Each stream end is logged.
- Menu strings: the TUI catalog has English and Japanese only; kept.

The live proof attaches the TUI through a line proxy that ends its event
stream and every reopened one (fault injection at the protocol boundary),
and checks that a CLI sidebar.move still reaches the drawn order; it also
checks a lower-half drop under the last pinned row and a name-row drop on
the first machine row.

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

* debug.key palette reply: palette_loading too; doc fixes after review (cx-9ce.45 follow-up)

* fix(nightly-next): promote a green commit even when a newer push published its tree (#20574)

* test(nightly-next): promote a green commit older than its tree's publisher; never re-request a promoted one

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

* fix(nightly-next): bound promotion candidates by nightly-next, not the tree's publisher

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

* fix(nightly-next): skip the promoted commit without history; comment and help fixes

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

---------

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

* sidebar-v2 S6: the fallback check asserts the move changes the order

Review: the idle-tick check passed trivially if the CLI move did not change
the document order. A negative control (the tick only on idle timeouts, run
s6run9) left the order stale with four logged stream ends, so no tree refresh
carries the move.

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

* Browser record: a web address another client writes loads in the tab, not written back over (cx-ebm.59.6)

workspace.open_url (S3b, 9bb89aa8aeba) reuses a right-side frontend browser
tab by writing the new URL into its daemon record. BrowserRecordWriter.rebase
only re-based its copy, then wrote the page's old address back. Now a record
URL change that is not this writer's own echo (it keeps the last 8 addresses
it sent) and is not the page's address loads in the page (http/https only,
not a machine page), and the pending write-back is dropped: no write-back
loop, keyboard focus unchanged (BrowserTab.load).

BrowserRecordWriter.swift moved from CmuxNextApp to CmuxNextBrowserHistory
(same dependencies; types made public), so CmuxNextApp shrinks 226 lines.

* feat(cx-cmux-cua): nightly after-clip reel builder (#20593)

* feat(cx-cmux-cua): nightly after-clip reel builder

reel.py reads a build's signed notes, finds each PR's after clip on pr-media in one tree listing, and joins title cards and clips into reel.mp4 with reel.json and index.html.

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

* test(cx-cmux-cua): reel tests drive the CLI only

Every reel test runs reel.py as a command and checks its exit code, stderr, reel.json, index.html and reel.mp4; the internal-function tests are gone.

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

* fix(cx-cmux-cua): harden the reel builder's inputs

Unsigned or buildless notes, truncated listings, off-branch or redirected downloads and non-mov clips are refused; partial GraphQL heads are kept and non-https clips are not linked.

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

---------

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

* Fix release rails base red: staleness guard follows relative re-exports (#20594)

* test: the staleness guard follows a child module's re-exported capability

Red: 'cannot resolve capability open_url::CAPABILITY', as release rails
fails on feat-cmux-next since 45aa9fa07 (workspace.open_url).

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

* fix: the staleness guard reads a relative re-export from its own module

resource_router.rs re-exports its child's constant with
use open_url::CAPABILITY as OPEN_URL_CAPABILITY; the guard resolved
open_url from the crate root and found 18 CAPABILITY constants, so
release rails has been red on feat-cmux-next since 45aa9fa07.

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

* fix: follow a relative re-export only into a child module that exists

Review: the relative attempt ended in the unique-name lookup, so a
missing child could return a same-named constant from another module
instead of falling back to the crate-root path.

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

---------

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

* Browser record follow: review fixes (cx-ebm.59.6)

- An echo entry goes with its echo or its reply (cleared before the
  equality guard and when the send ends), so a later open_url to an address
  the tab visited before still loads.
- After a follow, page changes at the old address (a hibernated page loads
  on wake) and flushNow write nothing over the new record address.
- The followed address compares in URL form. Loading it uses BrowserTab.load,
  as cmux browser navigate already does (same automatic-download gesture).

* test(optchat-chief): the default Chief serves zoom and date only until it reads its record; chief profile in the launcher prompt (red, cx-ebm.60.5 follow-up)

* fix(optchat-chief): the default Chief gets zoom and date only until it reads its record; harness grant read live; chief profile in the launcher prompt (cx-ebm.60.5 follow-up)

* test(optchat-chief): a spawn on a harness outside the Chief's grant starts nothing (cx-ebm.60.5 gap 4)

* test: an SSH machine's chat folder trust is answered through agent-session-trust, then the start runs (cx-1bhjj)

Red: the daemon has no agent-session-trust verb, so the person cannot
answer a remote folder's trust question and the start stays refused.

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

* sidebar-v2 S6: workspace drops use the drawn rows; the marker shows the border side

hq-2d: a drop lands under the pointer at any rail scroll, row gap or
action placement. The rail stores its drawn geometry (area, visible body,
scroll offset, row height and stride) each frame, and the drop slot comes
from it: a row's upper half is the slot before it, its lower half and the
gap under it the slot after it; above the visible body is the first visible
row, below it the row after the last. Before, the slot assumed an unscrolled
rail with a one-row pad, two rows and one gap per workspace.

On a border of two sidebar sections the drop marker's right corner points
at the section the drop joins (up: the section above, down: the one below).

The live proof adds the border marker in both directions (released on its
own row: no change) and a drop in a rail scrolled to its end. Against the
previous head (792315d8bca9) these three checks fail: no corner, and the
scrolled drop landed in Pinned at the top.

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

* cmux-next: a chat in an SSH workspace asks to trust its folder on that machine; Cmd-I there starts in that workspace's folder (cx-1bhjj)

New daemon verb agent-session-trust-v1 {surface, cwd, level?}: reads acpmux's
acp.trust.get for a folder of this machine, or records the person's answer
(acp.trust.set), for an agent tab of this store; same trusted-local, machine
kind and acpmux-start rules as agent-session-start. The daemon never answers.

The remote chat wire serves acp.trust.get/set through it and lists
acp.trust.set, so the page's existing trust question (TrustAsk) shows for a
chat on another machine (a remote browser still cannot answer: acpmux never
lists it). The relay's fresh-gesture rule for a set that trusts is unchanged.
A start refused for an untrusted folder routes to the same question. A new
remote chat's handshake is newSession, so its seed folder is asked about
before the first send.

Cmd-I in an SSH workspace (new workspace, lawrence-call-1006 D, unchanged)
seeds the chat with a folder of the source workspace when the focused tab has
none, so it no longer fails with 'Open a terminal in a folder first'.
WorkspaceModel.firstTabFolder / DaemonStore.tabFolders replace three copies.

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

* sidebar mirror: rustfmt

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

* cmux-next: review fixes: Trust on a remote folder re-runs the start it held; at most 8 agent-session-trust calls at once (cx-1bhjj)

A start refused for an untrusted folder spent the send's gesture, and the
Trust click's gesture went to acp.trust.set, so the held prompt's start was
refused for want of a gesture. A trusting acp.trust.set on a remote wire now
also allows one session/new on that connection within a gesture's lifetime
(as a held prompt goes after Trust on this Mac). The daemon caps trust calls
(agent_session.limit); the spec says any trusted local unix client can set
trust there, as on acpmux's own socket. Drops a no-op newSession line.

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

* cmux-next: a remote chat's folder follows the Mac's rules: the home folder is asked about first and trusted only after that answer; an old remote cmux-tui shows how to update instead of a failing Trust (cx-1bhjj)

hq-a3 decisions (a) and (b).
(a) A new chat on another machine gets its folder from that machine's store
(workspace.agent_start.get, its own home folder): a home or above-home seed
never starts there by itself, and Use Home Folder asks first, as on this Mac.
agent-session-trust refuses / and the folders above the home folder
(agent_session.above_home) and trusts the home folder only with
home_confirmed (agent_session.home_unconfirmed); the relay adds that mark
only for a folder the person chose by Use Home Folder (a page cannot send it).
(b) The wire offers the trust question only when the machine's daemon serves
agent-session-trust-v1; otherwise an untrusted folder says 'Update cmux on
<machine> to trust folders from here.' (en, ja; 19 needs_review).

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

* spec: agent-session-trust home folder rule (cx-1bhjj)

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

* cmux-next: review fixes: home rule fails closed and also guards agent-session-start; remote home choices kept apart from this Mac's roots; typed home refusals (cx-1bhjj)

home_rule refuses / even with no home known, and with no home folder every
trusted answer needs home_confirmed. agent-session-start takes the same rule
(home_confirmed when the person chose the remote home by Use Home Folder), so
a fallback folder never starts a chat in the home folder by itself. The
remote Use Home Folder answer goes to transport.remoteHomeChosen, never to
addedRoots (which this Mac's file opens read), and is cleared on a local
connection. agent_session.home_unconfirmed / above_home get localized text.

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

* spec: agent-session-start takes the home folder rule (cx-1bhjj)

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

* cmux-next: AgentTabRemoteHost trust closure without a ternary (the type checker failed on it) (cx-1bhjj)

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

* test: only the agent page reads folder trust, and its answer needs the person's gesture, checked by the daemon (cx-1bhjj, hq-6d)

Red: pages have no route to the folder trust at all.

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

* cmux-tui-core: pages reach folder trust only as page agent's routed operations, the answer behind the person's gesture (cx-1bhjj, hq-6d)

hq-6d approved agent-session-trust-v1 with one change: the daemon itself
checks the person's gesture for a page's Trust answer, and only the pages
that ask the trust question may read it. page-router.json gets namespace
agent_session (owner agent_session.trust): agent_session.trust.get (read)
and agent_session.trust.set (mutation, gesture), listed only for page
agent (the agent pane and its New Tab page). Every other page gets
origin.forbidden, the read included; a page relay still cannot send the
legacy line. A plain unix client keeps acpmux's own rule (known limit in
commands.md). pageRouter.ts regenerated.

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

* settings: the free AI model row has its home in the Agents category (cx-ds09i; fixes categories.test.ts on the tip)

* cmux-tui-core: review fixes: a page never trusts a home folder; agent_session namespace is Unix only; legacy agent-session-trust stays local-Unix (cx-1bhjj, hq-6d)

A page's one confirmed click cannot prove the person chose the home folder
first, so the routed set refuses home_confirmed (the home rule then keeps the
home folder untrusted for pages). The namespace text says Unix socket only
and one relay connection per pane. commands.md says the legacy command stays
local-Unix only and the page path is the v2 names through the router
(hq-6d). The test also checks that forgetting an answer needs the gesture,
a get with a level is a bad request, and the relay refusal code.

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

* ci: the fast lane runner is an owned pool (no bead) (#20600)

* test: the fast lane runner is an owned pool (red)

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

* fix: the fast lane runner is an owned pool (no bead)

CI_FAST_LANE_RUNNER names a glaeda-fast-* runner. OWNED_LABEL did not match
it, so owned_pool_rescue would not see a fast-tier job stuck on it.

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

---------

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

* fix(optchat-chief): a pending default Chief also reads its record on each conversation change (review, cx-ebm.60.5 follow-up)

* sidebar-v2 S6: review fixes for drawn-geometry drops

- The dragged row's own slot draws no border corner (the drop is a no-op).
- Above a scrolled body there is no slot (it would be off screen); below
  the body the slot is after the last fully drawn row.
- The drop geometry is cleared each frame and set only when the workspace
  rows are drawn.

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

* ci: defer the IME guard workflow step (cmux-next.yml is reserved by another lane); the guard lands as a script first (cx-dd5b4)

* ci: cmux NIGHTLY-identity copies the newest cmux NEXT build, whichever run published it (cx-x7hwx)

#20518 hands a build still in Apple's queue to recovery, which publishes it
in a later (or continue-only) run. The legacy job needed publish-nightly ==
success in its own run and the build from that job, so a run that published
nothing never caught the old feed up. New Linux job legacy-identity-target
compares the cmux NEXT feed with the old feed
(scripts/ci/nightly-next-legacy-target.py) in every nightly-next run and
names the newest cmux NEXT build when it is newer; the macOS job starts only
then and copies that exact build. Kill switch, no-older-build re-check,
concurrency and the notary wait are kept.

Behavior test (tests/test_nightly_universal_build.sh): a recovery-published
build newer than the old feed starts the job; a build the old feed has, or
an older one, does not; a wrong enclosure stops it; the wiring does not
require a publication in the same run.

* ci: cmux NIGHTLY-identity shell checks without A && B || C (cx-x7hwx)

* cx-ebm.59.3: red test, open_url in an empty Home makes the screen and pane, then a frontend tab

* style(cmux-tui): rustfmt the agent-caller Chief test (cx-ebm.60.9)

* sidebar doc store: an open marks a held lease stale; drop the redundant position index

Re-review fixes: clearing the cache at open while another registry's
commit holds the document would hide a nested writer from that commit,
so an open marks a held lease stale instead. personal_workspaces_order
covers the older (position) index, so the open drops it (an older binary
makes it again at its own open).

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

* ci: cmux NIGHTLY-identity target never turns a run red and stops retrying a build that failed twice (cx-x7hwx)

* cx-ebm.59.3: open_url makes the pane, then a frontend tab, in an empty Home

With the app attached (frontend-browser-tabs-v1), a workspace with no
screen gets the screen and pane that tab.create_browser makes, holding a
frontend browser tab: its record is registered under the create step's
key first (the keyed new-frontend-browser-tab path, now a shared helper),
so a retry answers the same tab. The lone-dock column fallback does the
same with pane.split. No browser engine is needed.

* acpmux: behavior test, the person's latest mode pick is the start mode of every new chat on every harness (red) (cx-wx93i)

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

* acpmux: the person's latest mode pick is the start mode of every new chat, on every harness and model (cx-wx93i)

Lawrence 2026-10-14: "should just apply to the ones going forward, all
harnesses/models, should always use the latest user selected one".

- A mode pick from the person's app pane saves its level (fullAccess,
  standard or ask) in config.json lastPermissionMode; it wins over
  defaultPermissionMode and the cmux setting, maps onto each harness's own
  mode ids at the next new chat, and survives a restart. Chats that exist
  keep their mode. Plan stays a toggle and is not saved.
- The per-profile defaults.<profile>.mode (and its clear guard) is removed.
- Config reload picks up lastPermissionMode.

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

* cmux-next exports: regenerate after merging feat-cmux-next b8e30f7291b7 (section close/new actions beside sidebar.density comfortable)

Exporters' own output at 80666c4d8a0b: action exports from
scripts/measure/export-action-surfaces.sh (cmux-ci step
8632c96bc28b11972b0d46b3, aws-m4pro-8; links.json and
cmux-shortcuts.generated.json unchanged), settings schema and MDM from
run-swift-testing-suites.sh export-settings (cmux-ci step
bb959d3e5ba2c994b8d5a2ed, aws-m4pro-6, Xcode 26.6).

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

* Browser record follow: type the load closure (the compiler could not type the ternary closure) (cx-ebm.59.6)

* settings: schema page export lists the AI Models group in Agents (cx-ds09i; export-settings cmux-ci step 1282c157a3f8)

* Video wallpapers in the wallpaper picker (cx-14vdf) (#20477)

* test: video wallpapers loop while seen, hold still otherwise, and follow light and dark (cx-14vdf)

Behavior tests with a generated clip and a fake Desktop Pictures folder: a
video wallpaper loops muted behind the window while it is seen, holds its
frame when hidden, in Low Power Mode or under Reduce Motion, shares one
decoder across windows and lets it go when they close; Apple's moving
wallpaper and downloaded aerials are listed as Moving wallpapers, and
Apple's plays its light or dark cut with the window. The listing and the
playback are stubs here.

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

* feat: video wallpapers in the wallpaper picker (cx-14vdf)

A video is a normal wallpaper-picker choice next to the still ones; the
default stays a still background. Moving wallpapers lists Apple's moving
wallpaper and the aerials macOS downloaded, read where macOS keeps them,
and the person's own movies (Choose Image… takes movies now). The video
loops muted through AVPlayerLayer, one decoder per file shared by every
window, and holds its frame while the window is not seen, the screens
sleep, in Low Power Mode, at a serious thermal state or under Reduce
Motion; its first frame is the poster. Apple's moving wallpaper plays its
light or dark cut with the window.

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

* test: wallpaper tiles whose id is a file path reach the app as one thumbnail segment (cx-14vdf)

The Settings page asks for backdrop/<encodeURIComponent(id)>; the page scheme
decodes the path before splitting it on /, so a system: or file: id arrives as
several segments and the tile shows a broken image.

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

* fix: wallpaper tiles whose id is a file path show their thumbnail (cx-14vdf)

Encode the id twice in backdrop/<id>: the page scheme decodes the URL path
before splitting it on /, and the thumbnail source decodes its one segment.
macOS stills, moving wallpapers and the person's own images showed a broken
image in the picker.

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

* test: closing a window lets its video wallpaper's decoder go (cx-14vdf)

Review of #20477: the harness removed the backdrop before closing, which the app never does, so
a closed window's share of the shared player was never handed back. Red: the player outlives
both windows.

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

* fix: video wallpaper review fixes (cx-14vdf)

- A closing window hands its share of the shared player back (NSWindow.willCloseNotification):
  the app closes windows with their views in them, so the decoder lived until quit.
- Readiness from AVPlayerLayer's KVO reaches the main actor through MainDelivery instead of
  assuming it already runs there.
- Light and dark cuts swap only in the file name, never in a folder name.
- A wallpaper thumbnail for a choice that arrived after Settings opened (a newly downloaded
  aerial) re-reads the catalog instead of answering nothing.
- The gallery's Settings mock decodes thumbnail ids twice, as HostCards encodes them.
- The doc says a held video keeps the frame it is on.

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

---------

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

* fix(cmux-next): DEV daily waits for its cold cmux-tui tree instead of deferring forever (#20602)

* test(cmux-next): a DEV daily run waits for its own cmux-tui tree, then builds it

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

* fix(cmux-next): a DEV daily run waits for its cold cmux-tui tree instead of deferring forever

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

* fix(cmux-next): DEV tree wait looks again after a failed answer before giving up

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

---------

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

* cmux-tui: behavior test: ensure --replace-unresponsive replaces a frozen owner (red, cx-jniyz)

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

- A pick's level uses the merged asking table, and Codex read-only counts
  as ask, so a narrow pick never becomes the harness's wider own mode.
- Test: a unix socket client's set_mode is shown not to change the pick.

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

* test(cmux-tui): an agent caller cannot write any Chief (red, cx-ebm.60.9)

hq-6d decision 2026-10-14: layer 1 refuses every cmux chief new|edit|delete
write from an agent caller, any field, any Chief. Reads stay open.

* backend: Cloud Chiefs on Claude Opus 5.5 through the metered Bedrock route, Kimi as fallback

Lawrence 2026-10-14: Cloud Chiefs use the metered Claude route (the cr claude-david path) and bill
toward it. New model alias claude-opus-5-5: streamed Messages requests to
https://chatmux.dev/api/claude-david/v1/messages, signed by X-Stack-Auth from the staging-only
secret CHIEF_BRAIN_CLAUDE_STACK_AUTH. One holder object refreshes the access token before expiry
and keeps it in its own storage; a failed sign-in runs the turn on CHIEF_BRAIN_FALLBACK_MODEL
(staging: Workers AI Kimi) and logs only 'claude-david auth failed'. Staging defaults: turns on
Claude, compactor on Kimi. The daily turn cap stays.

Behavior test with the route and the refresh faked at their HTTP boundary: signed, streamed Opus
requests with the refreshed token and no API key, one refresh for two turns, the fallback on a
failed refresh, and no token bytes in replies, runs, the settings response or logs.

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

* backend: Claude Chiefs review fixes: owner allow-list and deployment cap, short refresh margin, no fake fallback

Review (REVISE, no token leak found) fixed:
- P1: the route bills one person's account. Claude only for owners in CHIEF_BRAIN_CLAUDE_OWNERS
  (staging: the dogfood owner), refused at PUT (chief_claude_unavailable) and at each turn; a
  daily cap for all Chiefs (CHIEF_BRAIN_CLAUDE_DAILY_TURNS, default 500) beside each Chief's 300.
- P2: refresh margin at most min(3 min, half the token's life), so a short-lived token is not
  refreshed on every request; test token lives 10 min.
- P2: with no fallback model a failed sign-in fails the turn (never the fake model); the compactor
  never runs on Claude (refused at PUT, mapped to the fallback).
- P3: a 401 from the route drops the stored access token; a rotated refresh token is kept in the
  holder's storage; refresh headers are asserted outside the fake.

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

* fix(cmux-tui): cmux chief refuses every write from an agent caller (cx-ebm.60.9)

Layer 1 stricter (hq-6d 2026-10-14): new, edit (any field, picture
included) and delete of any Chief exit 1 with origin.forbidden /
agent_caller for an agent caller. list stays open. A Chief changes its own
profile only through its profile tool.

* CmuxNext: sidebar rows badge their logo's corner instead of a count (#20492)

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

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

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

* CmuxNext: static status glyphs for the Static status mode

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

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

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

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

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

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

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

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

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

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

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

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

* feat: Codex-like status icon set

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

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

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

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

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

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

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

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

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

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

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

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

Red: the default is still symbols.

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

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

* feat: Codex-like status icons are the default

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

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

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

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

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

* chore(next): PR autofix at a8608cf74601

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

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

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

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

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

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

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

* test: sidebar rows lead with a logo or kind icon, badge its corner, and draw no count

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

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

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

Leo and Lawrence 2026-10-14 ("maybe we can do unreads like this", T3 Code's thread list):
- a row leads with its harness logo or its kind icon; a user's icon still wins;
- one badge on that icon's bottom-right corner, on a disc of the row's backdrop so it cuts
  a thin ring: the most urgent of failed, needs approval, …
teamleaderleo added a commit that referenced this pull request Oct 14, 2026
A one-line status on an otherwise one-line row (one reported entry, or
the hooks' status line) now sits dim on the right of the name's line,
truncated at 40% of the row, instead of a second line that grew the row
from 24 to 40 pt. A richer block (several entries, a log line or a
progress label) still draws under the name, as do rows with another
second-line element. The hover card lists every entry; VoiceOver reads
the text.

A status's state already reaches the row's badge on this base: busy
draws the grey spinner (the default row shows progress), and failed and
waiting draw their colored glyphs (#20492). The tests guard those.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 14, 2026
* test: session list pages neither repeat nor skip (red)

Pages of _acpmux/sessions and _acpmux/watch with limit+cursor, while a
later session moves to the top and the cursor's session is deleted; no
limit still returns the full list. Bead cx-wtndw.

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

* test: session list pages use distinct, ordered stamps (red)

Stamp each fixture session with a draft write in a known order, wait
between writes, and assert the full list against that order with
strictly decreasing updatedAt. Bead cx-wtndw.

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

* feat(acpmux): keyset pages for the session list

_acpmux/sessions and _acpmux/watch take optional limit + cursor; with a
limit the reply adds nextCursor (k<updatedAt hex>.<fnv1a id hex>, at
most 30 chars, null when done). The stamp is clamped to 12 hex digits
in the cursor and both comparisons. The list order is updatedAt desc,
then sessionId asc. Without a limit the reply is the full list, as
before. Bead cx-wtndw.

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

* refactor(acpmux): move presets request out of requests.rs; fold paging test

requests.rs was over the 1000-line god-file budget, so the session list
change could not touch it: the _acpmux/presets arm moves verbatim into
server/requests/presets.rs. The session list paging test joins
session_list_order.rs instead of growing hub_integration.rs (over its
budget on the base). Bead cx-wtndw.

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

* agent pane: page older turns in as the reader scrolls up (cx-2lv1k) (#20536)

* cmux-next Home: vendor MessagesLab pin 258b385 (main 63c98d9): per-view render scale (cx-3azz6)

Pin 258b385 (cx-3cb view-scale, code 59e8e94): every MessagesWindowView renders
at its own renderScale (row bitmaps in a cache keyed by row and scale, soft-edge
masks, preview heights and window-pixel snaps, compose bar, header, border,
morph, Markdown overlays, long-text tiles, Sizing.textBalloonWidth); the guard
of setRenderScale is per view. Our cmux appliedRenderScale WindowView patch and
its README row are gone (sync-messageslab.sh --check: 258b385 + 20 patches).

Home follows each pane's own window (Cmux/PaneHost+Scale.swift,
ChatController.followWindowScale): setRenderScale(window.backingScaleFactor)
when the pane goes into a window, when its window view is installed, when it is
shown again (HostView.viewDidUnhide) and on every backing scale change, never
skipped by a compare with Fixture.renderScale. ScaleKeeper (moved there from
PaneHost.swift) walks each pane's trees at that pane's renderScale; the video
badge and debugRowFrames' balloon use the pane's scale. README: the process-wide
items that remain (Fixture.inactive/lightAppearance, Metrics.current,
LongTextCenter.handler, DisplayScale.colorSpace, the screen-change handler) as
known limits of one view per pane.

home-scale-change.py: the return to 2x is now checked (the virtual display
ends, the window goes back to the 2x display), and at each scale one frame
after the change the view, cells, compose bar and header are at the window's
scale and no visible row shows an old-scale bitmap (debugGeometry scale fields,
MessagesLabHomeView+ScaleDebug.swift); snapshots at 1x and 2x checked for crisp
incoming balloon edges (home_pixels.check_crisp).

* home-scale-change.py: end with endAgents after the quit fixture, as the tagged-run rule (cx-3azz6)

* Home scale check (review): old-scale bitmap count on shown text rows only, time to clear printed, the 2x return proves the 2x snap (cx-3azz6)

* Home scale check: report image pixels per point of the visible text row bitmaps (cx-3azz6)

* cmux-next sidebar: Comfortable density (sidebar.density comfortable; Compact stays the default)

Comfortable keeps one line per row on taller rows with more room, at the
system sidebar's medium size: rows, tab rows and group headers 24 -> 28 pt,
headers 22 -> 26, horizontal inset 6 -> 8, section gap 8 -> 12. Pinned bands
and tiles take the same row step (SidebarDensityTokens is the one source).
Compact and Expanded metrics are unchanged. Text size and weight unchanged.
Adds sidebar.density.comfortable (palette, sidebar Options), the setting
choice, and its strings in every language.

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

* cmux-next sidebar density: generated exports for sidebar.density comfortable; sidebar-density-live.py GUI proof

Settings schema and MDM manifests regenerated on the fleet (run-swift-testing-suites
export-settings step 48e261cdad5495d135053f0c, aws-m4pro-6, Xcode 26.6). The action
exports (action-surfaces.json, actions.md) add sidebar.density.comfortable after
compact; the Options folder renumbers.

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

* cmux-next sidebar density: review fixes (doc comments; proof script reports a missing Options folder, checks the row inset and pinned/section heights)

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

* Home scale check: the move to 1x is a recorded upstream known limit (old 2x bitmaps up to 0.32 s, bound 0.5 s); README names RowCell.configureScoped (cx-3azz6)

* cmux-next action exports: regenerate on the merged head (base drift from tab groups and Move to Group, plus sidebar.density.comfortable)

Values are the exporter's own output from check-action-surfaces step
5e8d777e0ea4439015191829 (aws-m4pro-8) at 5770b1a7007c.

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

* sidebar-density-live.py: terminal ids from the action result, a done row that stays unseen, spinner state match (working(progress: nil))

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

* Home: a screen's colour space change keeps the row bitmap cache (no removeAll, no setInactive toggle); a window back on 2x finds its 2x bitmaps (cx-3azz6)

* cmux-tui move-tab-to-split/-column keep_focus: an agent-opened split does not take focus; subagent panes use it (cx-3hw0i)

Daemon: move-tab-to-split and move-tab-to-column take keep_focus
(tab-split-keep-focus-v1). With it the target screen keeps its active pane,
the active screen and workspace stay, the new pane is least recently active
and gets no focus stamp. When the focused pane closes in the move, the new
pane takes focus as before. keep_focus with respawn is a bad request. Spec,
sdk-schema, protocol.md and regenerated bindings.

App: DaemonConnection.moveTabToSplit/moveTabToColumn(keepFocus:) send it only
to a daemon that advertises the capability; TabMoves.toNewSplit (and its
no-room fallback toNewColumn) and tab.moveToNewSplit (internal keep_focus
argument) carry it; an unfocused subagent open (origin script) passes it.
The app no longer focuses the chat again after the move (AgentSubagentPanes;
that refocus did not keep focus either, proof 1013-222711 kept: False).

Behavior test: wire test a_keep_focus_split_leaves_the_focused_pane_focused
(split, stacked split, column, respawn refusal, plain drop still focuses).
Review: subagent, no P1; P2 column path fixed here; skew refocus and the
rest in cx-3hw0i.1.

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

* scripts: agent-pane-live.py subagent-panes types real keys while the panes open (CMUX_LIVE_TYPE_KEYS=1, nx-remote --needs input); every key must land in the chat's composer (cx-3hw0i)

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

* test: a long chat opens on its tail and pages older turns in on scroll-up (cx-2lv1k)

Red: nothing calls loadOlder, so scrolling up never reaches the first turn.

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

* agent pane: page older turns in as the reader scrolls up (cx-2lv1k)

The pane opened a chat on its newest 400 records and never asked for more:
nothing called loadOlder, and it read acpmux's paging flag as `more`
where acpmux sends `hasMore` (so missed-event catch-up also stopped after
one page). Now the transcript asks for the page before its oldest record
whenever the view is within two viewports of it, one request at a time
with a short backoff after a failure, and attach's hasMore says when the
start is already loaded. The reader's row holds its place as pages land,
at offset 0 too, and when a page completes a turn cut at its edge.

Each older page holds whole turns (the records before its first prompt
wait for the next page) and folds on its own above the held rows, so a
page costs the same however much history is loaded: re-folding every
held event cost 105 ms a page at 45k events and grew without bound.
Rows a rebuild makes again unchanged keep their objects, so drawn heights
and memoized rows survive it.

Opening asks for at most 128 KiB and each older page 256 KiB (maxBytes),
so a remote session's open stays small however large its tool output.

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

* webviews: settings page strings on the merged head (sidebar.density help with Comfortable)

Output of webviews/scripts/pages/gen-strings.mjs settings at the merge.

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

* cx-ebm.59.3: red test, workspace.open_url uses the frontend browser path when the app renders browsers

Behavior test at the daemon socket: a client that declares
frontend-browser-tabs-v1 gets a frontend browser tab on the right side,
the next link reuses it, and a same-key retry after a selection change
answers the first result.

* test: a paged-in turn is timed from its own prompt (red)

An older page folded while the newer rows were still held, so a turn's
"Worked for" counted from the newest prompt and showed 0s.

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

* agent pane: time a paged-in turn from its own prompt (cx-2lv1k)

An older page folds below the held rows, so a turn ending there now
totals only the rows before them: its "Worked for" no longer counts
from the newest prompt. Review follow-ups: a steer opens no turn, so a
page never splits at one; a fork only an older page holds is kept;
a rebuild keeps an earlier row object only when its plain fields match
too; and a page in flight for the session before no longer blocks the
next session's first page.

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

* cmux-next exports: regenerate on the merged head (sidebar.density comfortable over feat's freeModel, chromeStyle and paneAnimations)

The merge kept feat's copies of the generated exports; these are the
exporters' own output at 58d30c75b854: action-surfaces.json and actions.md
from scripts/measure/export-action-surfaces.sh (cmux-ci step
b47b7cd1c57a48cea6be557b, aws-m4pro-6; links.json and
cmux-shortcuts.generated.json unchanged), settings schema and MDM from
run-swift-testing-suites.sh export-settings (cmux-ci step
ce51138281d5838d03e059f7, aws-m4pro-8, Xcode 26.6).

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

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

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

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

* chore(next): PR autofix at 6b20321bb088

- regenerated schemas/defaults/cmux-next.json

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

* sidebar-v2 S6: live TUI proof on the sidebar document (red)

scripts/cmux-next/sidebar-tui-doc-live.py drives a real headless daemon and a
real cmux attach TUI in tmux: pin/unpin from the TUI rail must land in
sidebar.get, and a CLI sidebar.move/sidebar.pin must change the drawn order.
Red on the base: the TUI has no pin and draws the session order.

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

* sidebar-v2 S6: the TUI reads and edits the sidebar document

With a daemon that advertises sidebar-doc-v1, the TUI draws the document's
workspace order (Pinned first, sections in region/key order, group members
after their group), read with sidebar.get and kept current by the
sidebar_item/sidebar_section/sidebar_doc changes of a session.events stream.
Both are cmux.protocol/2 lines on the existing session connection; a gap,
a refused request or the stream's end refetches on a worker thread.

Writes go to the document: the workspace context menu and the rail's p key
pin or unpin (sidebar.pin/sidebar.unpin), and a workspace drop is one
sidebar.move before (else after) the drawn neighbor. An older daemon keeps
move-workspace and shows no pin. The workspace pin flag is never written.

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

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

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

* cmux-next: window, group, section and agent-archive verbs on palette, CLI, MCP and menus; Close Window in the File menu (cx-5qewo)

Lawrence 2026-10-14 (audit answers): add all.
- window.closeOthers "Close Other Windows" (cmux app close-other-windows, File
  menu); closeAllWindows is a CLI verb and MCP tool now; Close Window is in the
  File menu (Ctrl-Cmd-W).
- workspaceGroup.closeOthers "Close Other Groups" (destructive; each other group
  closes as Close All Workspaces in Group) and workspaceGroup.duplicate
  "Duplicate Workspace Group" (a duplicate of each member, then one new group).
- sidebar.section.closeWorkspaces "Close All Workspaces in Section" (destructive,
  Pinned too) and sidebar.section.newWorkspace "New Workspace in Section".
- agentPane.archiveChat is a CLI verb (cmux agent archive-chat --target pane:...)
  and MCP tool: ActionRegistry.targetContext lets an explicit target bring its
  own facts (an agent chat's pane: agentPaneFocused).
- The no-undo prompt says incognito workspaces cannot be reopened.

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

* cx-ebm.59.3: workspace.open_url makes frontend browser tabs when the app renders browsers

A session with a connected client that declared frontend-browser-tabs-v1
(the cmux app) gets the link as a frontend browser tab through the
new-frontend-browser-tab commit (selected, after the pane's selected tab,
keyboard focus kept); reuse navigates a frontend tab through its record
(update-frontend-browser-tab URL semantics). The daemon-engine path stays
when no such client is attached. The request key records its result in
the mutation ledger: a same-key retry after a selection change answers
the first result; the same key with another URL is idempotency.conflict.

* cmux-next: WorkspaceSetVerbs imports CmuxNextBridge; proof script submits the palette and names the MCP window (cx-5qewo)

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

* cx-ebm.59.3: open_url review fixes: replay a committed frontend create, ignore page clients

A retry after the frontend create step committed but before the ledger
entry answers that tab (reused:false) instead of reusing and navigating
it. A page relay or page WebSocket that declares frontend-browser-tabs-v1
does not switch the session to frontend tabs.

* cx-ebm.59.3: rustfmt

* sidebar-v2 S6: poison-tolerant locks in the sidebar read model

The crash ratchet refused 17 new .unwrap() calls on the sidebar document
locks. A poisoned lock still holds a usable read model, so take it.

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

* cmux-next: slice 2 review fixes: pure target facts only when needed, section item index, Close Other Groups and Duplicate Group in the group's window, group undo restores groups, app headers drop the section workspace verbs (cx-5qewo)

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

* sidebar doc: indexed reducer, in-place commits, gap positions for personal rows

The sidebar document keeps indexes (id -> item, (section, parent) ->
ordered (order key, id) set, workspace refs, placement refs, per-section
counts). Ops change it in place through a journal of first old copies;
only the invariants of the touched rows are checked (check.rs), a
refusal restores them, and the changed rows and the inverse come from
the journal. No op copies, sorts, checks or compares the whole document.

The daemon leases its cached document for a commit instead of cloning
it; a nested writer marks the lease stale. The old-store sync and closes
run as one Batch (one revision). write_back walks display order from
the changed items and places a personal row after its neighbor.

Personal workspace rows: positions have gaps (row_position.rs); a move
writes the moved row and the group slots at its old and new place, and
respaces in one statement only when no integer fits. workspace.place
restates only the placements whose index moved; placement snapshots read
the public ids once; many queued changes are one pending row; the
topology workspace order is one UPDATE ... FROM json_each.

The app mirror keeps its shown document in step (pending intents undone
by their inverses, the batch applied, intents replayed) instead of
copying it per call. Section scroll (fixed|flow, default by kind and
region), SectionPatch.scroll, url body icon_url and ItemPatch.icon_url
(an old extra.icon_url moves into the body at add and at open).

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

* sidebar doc: section scroll and max_rows in the spec, cmux sidebar section-scroll / section-max-rows

SidebarSectionSnapshot gains scroll (fixed|flow, default by kind and
region) and documents max_rows (grow, then scroll inside); the url body
documents icon_url; section.add/update and item.update describe the new
patch fields. CLI: `cmux sidebar section-scroll <section> fixed|flow`
and `cmux sidebar section-max-rows <section> <1-50>|none`, each one
sidebar.section.update with a partial patch.

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

* scripts/cmux-next: sidebar-doc live checks for scroll, max_rows, the CLI verbs and url icon_url

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

* cmux-next: regenerate action exports for the window, group, section and archive verbs (cx-5qewo)

scripts/measure/export-action-surfaces.sh at bb768ba8c90c (cmux-ci step 4d532d471d146d1a54780d98, aws-m4pro-2: 13 parity + link tests passed).

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

* spec: regenerate SDK descriptors and app-host generated types (section scroll)

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

* sidebar doc store: box the cached document (clippy large_enum_variant)

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

* sidebar doc: rustfmt

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

* sidebar-v2 S6: review fixes for the TUI sidebar document client

- A drop on the dragged row's own slot is a no-op; a slot on the border of
  two containers (end of Pinned / start of the machine list, end of a group
  / next loose row) keeps the workspace in its own one, else joins the one
  above. Before, such a drop unpinned the last pinned row.
- The stream restart cap resets only on a batch that changed the document,
  and a reopened stream resumes from the last item's cursor (deltas, not a
  full snapshot); a refused cursor reopens without one.
- Only a structured refusal turns the document off for the connection; a
  timeout retries at the next tree refresh. An unanswered stream open and a
  dropped stream are released with stream.cancel.
- Waiting protocol/2 requests wake when the connection shuts down.
- A batch with several sidebar_doc markers spans the lowest previous to the
  highest revision (no false gap).
- spec/inventory.json and check-spec-inventory.py list the PinWorkspace menu
  action (direct: sidebar.pin or sidebar.unpin).
- The live proof also checks the drawn order differs from list-workspaces
  after a pin, a mouse drag into Pinned, and a drop on the own slot.

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

* test(optchat-chief): a Chief's profile, tool grant and self-edit through its brain (red) (cx-ebm.60.5)

Behavior test against a real headless cmux-tui daemon and the real
optchat-chief mcp server: the person makes a Chief with a role,
personality, instructions, tools [tell] and self_edit [avatar, role];
the brain renders the profile in the cloud brain's format, offers only
the granted tools plus profile, refuses spawn at the tools socket, and
changes only its allowed fields through its own bound connection.

Red: the brain has no chief_profile module (no profile, no grant).

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

* cmux NEXT: a restoring daemon never leaves a page stuck at "not connected"; show the real cause (cx-4bd8m)

After Austin's update the owner spent minutes restoring a 10.7 GB session.
server ensure gives up at its 2-minute restore bound (server.restore_timeout);
each such failure spent the first-connect / reconnect timed retry budget, and
past the budget the loop waits for an event only. A restore that ends behind
the same socket fires none, so the app could stay "not connected" next to a
ready daemon until it was reactivated. A restore timeout now keeps the budget:
the next ensure waits for the same owner again (the daemon bounds each wait).

Every surface that said only "cmux-tui is not connected" now says why:
DaemonService.notConnectedReason (still restoring, the last connect error, or
the drop reason) feeds the App Store banner, app sections and app action
refusals (AppsUnavailableReason.notReady) and command failures.

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

* fix(cmux-next): the bookmark name follows the page title while the tab stays on the bookmarked host; live check: real HID click on the star, wait for the late title, the old name check expects the title (cx-x9o52 follow-up)

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

* cmux NEXT: wake the daemon retry loop on the socket and ready marker before the first connect (cx-4bd8m)

Review P1: the retry wake watched the daemon socket only after a first
successful handshake, so a first connect that spent its timed budget during a
long owner shutdown or restore (closed connections, launch failures, identify
timeouts) waited for app activation only. The loop now watches the socket the
last launch used from the start, and the connection also watches the owner's
ready marker <socket>.websocket.json, which it writes once its restore is done,
so a restore that ends behind the same socket wakes the loop too.

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

* cx-ebm.59.3: no unwrap on the client registry lock (crash ratchet)

* feat(optchat-chief): each Chief's profile, tool grant and self-edit tool in its brain (cx-ebm.60.5)

The brain reads its own record from the daemon's Chief registry at host
start (chief.profile.get on its own bound connection):

- prompt: the profile goes after the base prompt, the person's
  instructions and the channels paragraph, in the cloud brain's format
  (backend chief-brain/prompts.ts): '## This Chief (the rules above come
  first)' with Name/Role/Personality, then '## Added instructions'. Turns
  and compactions share it (one cache entry); subagents do not get it.
  The default Chief with no profile text renders nothing, so its prompt
  stays byte-identical. Read once per host start: text changes reach the
  prompt at the brain's next start (the picture shows at once).
- grant: the optchat MCP server asks the host's tools socket for the
  granted model tools at tools/list (zoom and date always; spawn, tell
  when granted; profile when self_edit is not empty). The tools socket
  refuses an ungranted spawn/tell/profile by name (the chief launcher of a
  non-Claude harness asks the socket directly). A spawn whose subagent
  harness is outside harnesses starts nothing.
- profile tool (schemas/chief-profile/tool.json): reads the record, or
  changes display_name/role/personality/instructions and the picture
  (avatar_path, sniffed PNG/JPEG/GIF/WebP <= 1 MiB; clear_avatar) through
  chief.profile.update / avatar.set; the daemon enforces self_edit and the
  refusal names the fields. Also 'chief profile [JSON]' from a shell.
- no record (cloud source, older daemon, a Chief started before it had a
  record): every tool, no profile, as before. A failed read is fatal only
  for a non-default Chief (its grant must not open up).

Green: tests/profile_daemon.rs against a real headless daemon and the
real mcp server.

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

* sidebar doc: review fixes; head room before the first personal row; order index

Review (subagent) fixes:
- A Batch dropped without finish (an error mid-sync) restores its rows,
  so the leased cache never keeps a change the transaction rolled back.
- section.add into another region gets that region's default scroll
  (one rule in place_section for add and move).
- The app mirror undoes and replays pending intents only when an intent
  goes or rows apply; before the first load it only drops the intent.
- The old-store sync queues one pending row per change (the open path
  queues outside a journaled commit); queue_changes is for commits only.
- The cache is cleared at registry open; duplicate rows keep the first;
  reduce::apply and Outcome (no caller) are removed.

Personal rows: a renumbering (and an empty list) starts at 2^40, so moves
and creations at the top find room before the first row instead of
renumbering every row each time (sidebar.move to a machine section's top
went from 3.4 ms to 1.1 ms). Row moves use the new order index
personal_workspaces_order (position, session_id, workspace_key) with
range scans. Live check: a workspace.place writes only the moved row.

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

* fix(optchat-chief): review fixes for the Chief profile in the brain (cx-ebm.60.5)

- A bound participant with no record is refused by the registry as
  chief_registry_caller, not chief_not_found: typed LoadError (NoRecord,
  NoRegistry, Other). No record or no registry keeps every tool, so
  hq-ec's instruction-file VM Chiefs start as before; any other failure is
  fatal only for a non-default Chief. The behavior test now covers a
  Chief with no record.
- A harness-refused spawn no longer reports its queued ids as lost to a
  host restart: they are failed with the refusal when the brain dequeues
  them.
- profile: a refused picture after a committed text change says which
  fields did change.

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

* debug.key palette reply: report the query and whether its rank is still running (cx-9ce.45 follow-up)

nxdog100 read palette_rows right after the last debug.key and saw the previous
query's rows (a typed query ranks off the main actor), so shortcut search looked
broken. The reply now carries palette_query and palette_ranking.

* ci(cx-cmux-cua): media tour clears the last run's base worktree (#20578)

* fix(cx-cmux-cua): tour job clears the last run's base worktree

Self-hosted runners wipe RUNNER_TEMP but keep .git, so the next tour on the same Mac failed with 'missing but already registered worktree' before building (5 of 9 recent UI PRs).

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

* fix(cx-cmux-cua): take a locked stale base worktree too

A job killed mid-add leaves the entry locked; prune skips it and one --force still exits 128. Cleanup prune no longer adds a second red step when checkout failed.

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

---------

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

* webviews: red test: IME composition keys reach page handlers (History search Escape/ArrowDown) (cx-dd5b4)

* webviews: one IME key layer: keys an input method uses never reach page handlers (cx-dd5b4)

The desktop layer (every first-party page) now installs a capture-phase window
listener that stops keydown/keyup with isComposing or keyCode 229 (WebKit's
commit key after compositionend). The input method still gets the key (no
preventDefault). Fixes Escape clearing a search, ArrowDown moving a list and
Return submitting while a Japanese/Chinese/Korean/Vietnamese composition or a
dead key is active, in all ~35 fields whose key handlers had no composition
check.

* terminal: NSTextInputClient matches upstream Ghostty exactly (cx-dd5b4)

- every commit outside keyDown (IME commit on click/focus change, emoji
  picker, dictation) is a key event with text and no physical key, as
  upstream #13817 (was ghostty_surface_text_input unless marked text);
- insertText needs an event (NSApp.currentEvent), as upstream;
- UnicodeHexInput surrogate pairs join across two insertText calls, a lone
  trail is dropped (upstream #13737);
- markedRange/selectedRange answer NSRange() when empty and characterIndex
  answers 0, as upstream (input methods are tuned to these answers).

* ci: IME input guard: no new key synthesis from characters, terminal text only through its NSTextInputClient, every page entry has the IME key layer (cx-dd5b4)

* IME review fixes: ProseMirror and Monaco keep their own composition keys; composer submit ignores the WebKit commit key; guard refuses ghostty_surface_text_input everywhere; workflow paths (cx-dd5b4)

Review (subagent): P2 x4 fixed. ProseMirror clears its Safari after-composition
guard on the 229 keydown and Monaco tracks it for the press-and-hold accent
menu, so targets inside those editors still get the keys; MarkdownField's
submit now tests isImeKeyEvent (isComposing or 229). Note: a dictated newline
outside keyDown is now LF (upstream key path), not CR.

* webviews: mark the IME key layer listeners ui-allow (not widget key handlers) (cx-dd5b4)

* CmuxNext new tab: the daemon's raw tab-added of a conversation tab replaces its provisional stand-in in the same apply (Cmd-T showed both, then closed one and grew the other)

Cmd-T and + show a New Tab page at once as a provisional tab (pending_tab_*). The
daemon makes the conversation tab and sends a raw tab-added for it with no
transaction; the copy that carries the create's transaction and the reply trail
it. The store listed the daemon's tab BESIDE the provisional one until then, so
the strip showed two cells and, a sync later, closed the stand-in on the spring
while the store's tab grew in (frame strips: nxrevert-v1, b35b1c6, 358d010c).

ProvisionalTab.matched: a transaction-less tab-added of a conversation tab is the
tab of the oldest pending create intent in the same pane with the same record
(agent session host and session, page, route) that has no tab yet; the intent
notes it as created, so the store drops the stand-in in the same apply and the
strip's handover (TabStripModel.successors) gives the store's tab the cell in
place. Two quick Cmd-T pair in order; a tab of another kind or record never takes
a provisional tab's place. (hq-1d Cmd-T fix)

* CmuxNext new tab: a transaction-less tab-added replaces a provisional tab only when the pane has exactly one pending create without a tab, same record, and the tab is the pane's newest (hq-4e review)

* CmuxNext tabs: a new tab is there at once at its slot and full width, the strip makes room in the same frame (no grow-in by default; a drag or drop keeps its springs)

* CmuxNext tabs: a tab inserted at once also ends the strip's closing tabs in that frame (close then Cmd-T: no jump over a shrinking tab; hq-4e review)

* test: live check for a bottom-edge drop split (cx-jin7d) (#20542)

* test: live check for a bottom-edge drop split (cx-jin7d)

pane-edge-drop-e2e.py: the golden edge.new-split-below case (H split, tab
dragged at hand pace to 90% down the other pane) splits below. Red on feat
DEV bc7d0c5d (the 0.25 s split dwell joined the pane), green on 3158c157
(cx-imr7n). split-dwell-e2e.py sets the dwell it tests, now opt-in.

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

* chore(next): PR autofix at b835ce7daa37

- regenerated schemas/defaults/cmux-next.json

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

* test: tighten the edge-drop checks (review)

pane-edge-drop-e2e.py: the new pane must be neither source nor target, sit
under the target and hold the one moved tab; a failed debug.mouse call stops
the drag and releases the button; 10 s for the split; drop dead helpers.
split-dwell-e2e.py: put tabDrag.splitDwell back to its default afterwards.

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

---------

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

* test(cmux-tui): an agent caller cannot make, delete or widen a Chief (red, cx-ebm.60.9)

* fix(cmux-tui): cmux chief refuses make, delete and grant changes from an agent caller (cx-ebm.60.9 layer 1)

* sidebar-v2 S6: drops land where dropped; fallback read after repeated stream ends

hq-2d decisions:
- A drop lands where it is dropped. A workspace row's name row (upper half)
  is the slot before it; its second row (lower half) and the gap under it
  are the slot after it, on that row's side, so on a section border the
  pointer picks the section: the lower half of the last pinned row pins.
  A drop on the dragged row's own slot stays a no-op.
- After more stream ends in a row than the restart limit, every turn of the
  app loop (at least each idle wait) reads the document again, at most once
  per second, and opens the stream with it, so the drawn order never stays
  old. Each stream end is logged.
- Menu strings: the TUI catalog has English and Japanese only; kept.

The live proof attaches the TUI through a line proxy that ends its event
stream and every reopened one (fault injection at the protocol boundary),
and checks that a CLI sidebar.move still reaches the drawn order; it also
checks a lower-half drop under the last pinned row and a name-row drop on
the first machine row.

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

* debug.key palette reply: palette_loading too; doc fixes after review (cx-9ce.45 follow-up)

* fix(nightly-next): promote a green commit even when a newer push published its tree (#20574)

* test(nightly-next): promote a green commit older than its tree's publisher; never re-request a promoted one

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

* fix(nightly-next): bound promotion candidates by nightly-next, not the tree's publisher

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

* fix(nightly-next): skip the promoted commit without history; comment and help fixes

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

---------

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

* sidebar-v2 S6: the fallback check asserts the move changes the order

Review: the idle-tick check passed trivially if the CLI move did not change
the document order. A negative control (the tick only on idle timeouts, run
s6run9) left the order stale with four logged stream ends, so no tree refresh
carries the move.

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

* Browser record: a web address another client writes loads in the tab, not written back over (cx-ebm.59.6)

workspace.open_url (S3b, 9bb89aa8aeba) reuses a right-side frontend browser
tab by writing the new URL into its daemon record. BrowserRecordWriter.rebase
only re-based its copy, then wrote the page's old address back. Now a record
URL change that is not this writer's own echo (it keeps the last 8 addresses
it sent) and is not the page's address loads in the page (http/https only,
not a machine page), and the pending write-back is dropped: no write-back
loop, keyboard focus unchanged (BrowserTab.load).

BrowserRecordWriter.swift moved from CmuxNextApp to CmuxNextBrowserHistory
(same dependencies; types made public), so CmuxNextApp shrinks 226 lines.

* feat(cx-cmux-cua): nightly after-clip reel builder (#20593)

* feat(cx-cmux-cua): nightly after-clip reel builder

reel.py reads a build's signed notes, finds each PR's after clip on pr-media in one tree listing, and joins title cards and clips into reel.mp4 with reel.json and index.html.

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

* test(cx-cmux-cua): reel tests drive the CLI only

Every reel test runs reel.py as a command and checks its exit code, stderr, reel.json, index.html and reel.mp4; the internal-function tests are gone.

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

* fix(cx-cmux-cua): harden the reel builder's inputs

Unsigned or buildless notes, truncated listings, off-branch or redirected downloads and non-mov clips are refused; partial GraphQL heads are kept and non-https clips are not linked.

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

---------

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

* Fix release rails base red: staleness guard follows relative re-exports (#20594)

* test: the staleness guard follows a child module's re-exported capability

Red: 'cannot resolve capability open_url::CAPABILITY', as release rails
fails on feat-cmux-next since 45aa9fa07 (workspace.open_url).

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

* fix: the staleness guard reads a relative re-export from its own module

resource_router.rs re-exports its child's constant with
use open_url::CAPABILITY as OPEN_URL_CAPABILITY; the guard resolved
open_url from the crate root and found 18 CAPABILITY constants, so
release rails has been red on feat-cmux-next since 45aa9fa07.

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

* fix: follow a relative re-export only into a child module that exists

Review: the relative attempt ended in the unique-name lookup, so a
missing child could return a same-named constant from another module
instead of falling back to the crate-root path.

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

---------

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

* Browser record follow: review fixes (cx-ebm.59.6)

- An echo entry goes with its echo or its reply (cleared before the
  equality guard and when the send ends), so a later open_url to an address
  the tab visited before still loads.
- After a follow, page changes at the old address (a hibernated page loads
  on wake) and flushNow write nothing over the new record address.
- The followed address compares in URL form. Loading it uses BrowserTab.load,
  as cmux browser navigate already does (same automatic-download gesture).

* test(optchat-chief): the default Chief serves zoom and date only until it reads its record; chief profile in the launcher prompt (red, cx-ebm.60.5 follow-up)

* fix(optchat-chief): the default Chief gets zoom and date only until it reads its record; harness grant read live; chief profile in the launcher prompt (cx-ebm.60.5 follow-up)

* test(optchat-chief): a spawn on a harness outside the Chief's grant starts nothing (cx-ebm.60.5 gap 4)

* test: an SSH machine's chat folder trust is answered through agent-session-trust, then the start runs (cx-1bhjj)

Red: the daemon has no agent-session-trust verb, so the person cannot
answer a remote folder's trust question and the start stays refused.

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

* sidebar-v2 S6: workspace drops use the drawn rows; the marker shows the border side

hq-2d: a drop lands under the pointer at any rail scroll, row gap or
action placement. The rail stores its drawn geometry (area, visible body,
scroll offset, row height and stride) each frame, and the drop slot comes
from it: a row's upper half is the slot before it, its lower half and the
gap under it the slot after it; above the visible body is the first visible
row, below it the row after the last. Before, the slot assumed an unscrolled
rail with a one-row pad, two rows and one gap per workspace.

On a border of two sidebar sections the drop marker's right corner points
at the section the drop joins (up: the section above, down: the one below).

The live proof adds the border marker in both directions (released on its
own row: no change) and a drop in a rail scrolled to its end. Against the
previous head (792315d8bca9) these three checks fail: no corner, and the
scrolled drop landed in Pinned at the top.

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

* cmux-next: a chat in an SSH workspace asks to trust its folder on that machine; Cmd-I there starts in that workspace's folder (cx-1bhjj)

New daemon verb agent-session-trust-v1 {surface, cwd, level?}: reads acpmux's
acp.trust.get for a folder of this machine, or records the person's answer
(acp.trust.set), for an agent tab of this store; same trusted-local, machine
kind and acpmux-start rules as agent-session-start. The daemon never answers.

The remote chat wire serves acp.trust.get/set through it and lists
acp.trust.set, so the page's existing trust question (TrustAsk) shows for a
chat on another machine (a remote browser still cannot answer: acpmux never
lists it). The relay's fresh-gesture rule for a set that trusts is unchanged.
A start refused for an untrusted folder routes to the same question. A new
remote chat's handshake is newSession, so its seed folder is asked about
before the first send.

Cmd-I in an SSH workspace (new workspace, lawrence-call-1006 D, unchanged)
seeds the chat with a folder of the source workspace when the focused tab has
none, so it no longer fails with 'Open a terminal in a folder first'.
WorkspaceModel.firstTabFolder / DaemonStore.tabFolders replace three copies.

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

* sidebar mirror: rustfmt

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

* cmux-next: review fixes: Trust on a remote folder re-runs the start it held; at most 8 agent-session-trust calls at once (cx-1bhjj)

A start refused for an untrusted folder spent the send's gesture, and the
Trust click's gesture went to acp.trust.set, so the held prompt's start was
refused for want of a gesture. A trusting acp.trust.set on a remote wire now
also allows one session/new on that connection within a gesture's lifetime
(as a held prompt goes after Trust on this Mac). The daemon caps trust calls
(agent_session.limit); the spec says any trusted local unix client can set
trust there, as on acpmux's own socket. Drops a no-op newSession line.

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

* cmux-next: a remote chat's folder follows the Mac's rules: the home folder is asked about first and trusted only after that answer; an old remote cmux-tui shows how to update instead of a failing Trust (cx-1bhjj)

hq-a3 decisions (a) and (b).
(a) A new chat on another machine gets its folder from that machine's store
(workspace.agent_start.get, its own home folder): a home or above-home seed
never starts there by itself, and Use Home Folder asks first, as on this Mac.
agent-session-trust refuses / and the folders above the home folder
(agent_session.above_home) and trusts the home folder only with
home_confirmed (agent_session.home_unconfirmed); the relay adds that mark
only for a folder the person chose by Use Home Folder (a page cannot send it).
(b) The wire offers the trust question only when the machine's daemon serves
agent-session-trust-v1; otherwise an untrusted folder says 'Update cmux on
<machine> to trust folders from here.' (en, ja; 19 needs_review).

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

* spec: agent-session-trust home folder rule (cx-1bhjj)

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

* cmux-next: review fixes: home rule fails closed and also guards agent-session-start; remote home choices kept apart from this Mac's roots; typed home refusals (cx-1bhjj)

home_rule refuses / even with no home known, and with no home folder every
trusted answer needs home_confirmed. agent-session-start takes the same rule
(home_confirmed when the person chose the remote home by Use Home Folder), so
a fallback folder never starts a chat in the home folder by itself. The
remote Use Home Folder answer goes to transport.remoteHomeChosen, never to
addedRoots (which this Mac's file opens read), and is cleared on a local
connection. agent_session.home_unconfirmed / above_home get localized text.

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

* spec: agent-session-start takes the home folder rule (cx-1bhjj)

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

* cmux-next: AgentTabRemoteHost trust closure without a ternary (the type checker failed on it) (cx-1bhjj)

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

* test: only the agent page reads folder trust, and its answer needs the person's gesture, checked by the daemon (cx-1bhjj, hq-6d)

Red: pages have no route to the folder trust at all.

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

* cmux-tui-core: pages reach folder trust only as page agent's routed operations, the answer behind the person's gesture (cx-1bhjj, hq-6d)

hq-6d approved agent-session-trust-v1 with one change: the daemon itself
checks the person's gesture for a page's Trust answer, and only the pages
that ask the trust question may read it. page-router.json gets namespace
agent_session (owner agent_session.trust): agent_session.trust.get (read)
and agent_session.trust.set (mutation, gesture), listed only for page
agent (the agent pane and its New Tab page). Every other page gets
origin.forbidden, the read included; a page relay still cannot send the
legacy line. A plain unix client keeps acpmux's own rule (known limit in
commands.md). pageRouter.ts regenerated.

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

* settings: the free AI model row has its home in the Agents category (cx-ds09i; fixes categories.test.ts on the tip)

* cmux-tui-core: review fixes: a page never trusts a home folder; agent_session namespace is Unix only; legacy agent-session-trust stays local-Unix (cx-1bhjj, hq-6d)

A page's one confirmed click cannot prove the person chose the home folder
first, so the routed set refuses home_confirmed (the home rule then keeps the
home folder untrusted for pages). The namespace text says Unix socket only
and one relay connection per pane. commands.md says the legacy command stays
local-Unix only and the page path is the v2 names through the router
(hq-6d). The test also checks that forgetting an answer needs the gesture,
a get with a level is a bad request, and the relay refusal code.

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

* ci: the fast lane runner is an owned pool (no bead) (#20600)

* test: the fast lane runner is an owned pool (red)

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

* fix: the fast lane runner is an owned pool (no bead)

CI_FAST_LANE_RUNNER names a glaeda-fast-* runner. OWNED_LABEL did not match
it, so owned_pool_rescue would not see a fast-tier job stuck on it.

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

---------

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

* fix(optchat-chief): a pending default Chief also reads its record on each conversation change (review, cx-ebm.60.5 follow-up)

* sidebar-v2 S6: review fixes for drawn-geometry drops

- The dragged row's own slot draws no border corner (the drop is a no-op).
- Above a scrolled body there is no slot (it would be off screen); below
  the body the slot is after the last fully drawn row.
- The drop geometry is cleared each frame and set only when the workspace
  rows are drawn.

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

* ci: defer the IME guard workflow step (cmux-next.yml is reserved by another lane); the guard lands as a script first (cx-dd5b4)

* ci: cmux NIGHTLY-identity copies the newest cmux NEXT build, whichever run published it (cx-x7hwx)

#20518 hands a build still in Apple's queue to recovery, which publishes it
in a later (or continue-only) run. The legacy job needed publish-nightly ==
success in its own run and the build from that job, so a run that published
nothing never caught the old feed up. New Linux job legacy-identity-target
compares the cmux NEXT feed with the old feed
(scripts/ci/nightly-next-legacy-target.py) in every nightly-next run and
names the newest cmux NEXT build when it is newer; the macOS job starts only
then and copies that exact build. Kill switch, no-older-build re-check,
concurrency and the notary wait are kept.

Behavior test (tests/test_nightly_universal_build.sh): a recovery-published
build newer than the old feed starts the job; a build the old feed has, or
an older one, does not; a wrong enclosure stops it; the wiring does not
require a publication in the same run.

* ci: cmux NIGHTLY-identity shell checks without A && B || C (cx-x7hwx)

* cx-ebm.59.3: red test, open_url in an empty Home makes the screen and pane, then a frontend tab

* style(cmux-tui): rustfmt the agent-caller Chief test (cx-ebm.60.9)

* sidebar doc store: an open marks a held lease stale; drop the redundant position index

Re-review fixes: clearing the cache at open while another registry's
commit holds the document would hide a nested writer from that commit,
so an open marks a held lease stale instead. personal_workspaces_order
covers the older (position) index, so the open drops it (an older binary
makes it again at its own open).

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

* ci: cmux NIGHTLY-identity target never turns a run red and stops retrying a build that failed twice (cx-x7hwx)

* cx-ebm.59.3: open_url makes the pane, then a frontend tab, in an empty Home

With the app attached (frontend-browser-tabs-v1), a workspace with no
screen gets the screen and pane that tab.create_browser makes, holding a
frontend browser tab: its record is registered under the create step's
key first (the keyed new-frontend-browser-tab path, now a shared helper),
so a retry answers the same tab. The lone-dock column fallback does the
same with pane.split. No browser engine is needed.

* acpmux: behavior test, the person's latest mode pick is the start mode of every new chat on every harness (red) (cx-wx93i)

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

* acpmux: the person's latest mode pick is the start mode of every new chat, on every harness and model (cx-wx93i)

Lawrence 2026-10-14: "should just apply to the ones going forward, all
harnesses/models, should always use the latest user selected one".

- A mode pick from the person's app pane saves its level (fullAccess,
  standard or ask) in config.json lastPermissionMode; it wins over
  defaultPermissionMode and the cmux setting, maps onto each harness's own
  mode ids at the next new chat, and survives a restart. Chats that exist
  keep their mode. Plan stays a toggle and is not saved.
- The per-profile defaults.<profile>.mode (and its clear guard) is removed.
- Config reload picks up lastPermissionMode.

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

* cmux-next exports: regenerate after merging feat-cmux-next b8e30f7291b7 (section close/new actions beside sidebar.density comfortable)

Exporters' own output at 80666c4d8a0b: action exports from
scripts/measure/export-action-surfaces.sh (cmux-ci step
8632c96bc28b11972b0d46b3, aws-m4pro-8; links.json and
cmux-shortcuts.generated.json unchanged), settings schema and MDM from
run-swift-testing-suites.sh export-settings (cmux-ci step
bb959d3e5ba2c994b8d5a2ed, aws-m4pro-6, Xcode 26.6).

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

* Browser record follow: type the load closure (the compiler could not type the ternary closure) (cx-ebm.59.6)

* settings: schema page export lists the AI Models group in Agents (cx-ds09i; export-settings cmux-ci step 1282c157a3f8)

* Video wallpapers in the wallpaper picker (cx-14vdf) (#20477)

* test: video wallpapers loop while seen, hold still otherwise, and follow light and dark (cx-14vdf)

Behavior tests with a generated clip and a fake Desktop Pictures folder: a
video wallpaper loops muted behind the window while it is seen, holds its
frame when hidden, in Low Power Mode or under Reduce Motion, shares one
decoder across windows and lets it go when they close; Apple's moving
wallpaper and downloaded aerials are listed as Moving wallpapers, and
Apple's plays its light or dark cut with the window. The listing and the
playback are stubs here.

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

* feat: video wallpapers in the wallpaper picker (cx-14vdf)

A video is a normal wallpaper-picker choice next to the still ones; the
default stays a still background. Moving wallpapers lists Apple's moving
wallpaper and the aerials macOS downloaded, read where macOS keeps them,
and the person's own movies (Choose Image… takes movies now). The video
loops muted through AVPlayerLayer, one decoder per file shared by every
window, and holds its frame while the window is not seen, the screens
sleep, in Low Power Mode, at a serious thermal state or under Reduce
Motion; its first frame is the poster. Apple's moving wallpaper plays its
light or dark cut with the window.

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

* test: wallpaper tiles whose id is a file path reach the app as one thumbnail segment (cx-14vdf)

The Settings page asks for backdrop/<encodeURIComponent(id)>; the page scheme
decodes the path before splitting it on /, so a system: or file: id arrives as
several segments and the tile shows a broken image.

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

* fix: wallpaper tiles whose id is a file path show their thumbnail (cx-14vdf)

Encode the id twice in backdrop/<id>: the page scheme decodes the URL path
before splitting it on /, and the thumbnail source decodes its one segment.
macOS stills, moving wallpapers and the person's own images showed a broken
image in the picker.

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

* test: closing a window lets its video wallpaper's decoder go (cx-14vdf)

Review of #20477: the harness removed the backdrop before closing, which the app never does, so
a closed window's share of the shared player was never handed back. Red: the player outlives
both windows.

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

* fix: video wallpaper review fixes (cx-14vdf)

- A closing window hands its share of the shared player back (NSWindow.willCloseNotification):
  the app closes windows with their views in them, so the decoder lived until quit.
- Readiness from AVPlayerLayer's KVO reaches the main actor through MainDelivery instead of
  assuming it already runs there.
- Light and dark cuts swap only in the file name, never in a folder name.
- A wallpaper thumbnail for a choice that arrived after Settings opened (a newly downloaded
  aerial) re-reads the catalog instead of answering nothing.
- The gallery's Settings mock decodes thumbnail ids twice, as HostCards encodes them.
- The doc says a held video keeps the frame it is on.

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

---------

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

* fix(cmux-next): DEV daily waits for its cold cmux-tui tree instead of deferring forever (#20602)

* test(cmux-next): a DEV daily run waits for its own cmux-tui tree, then builds it

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

* fix(cmux-next): a DEV daily run waits for its cold cmux-tui tree instead of deferring forever

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

* fix(cmux-next): DEV tree wait looks again after a failed answer before giving up

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

---------

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

* cmux-tui: behavior test: ensure --replace-unresponsive replaces a frozen owner (red, cx-jniyz)

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

- A pick's level uses the merged asking table, and Codex read-only counts
  as ask, so a narrow pick never becomes the harness's wider own mode.
- Test: a unix socket client's set_mode is shown not to change the pick.

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

* test(cmux-tui): an agent caller cannot write any Chief (red, cx-ebm.60.9)

hq-6d decision 2026-10-14: layer 1 refuses every cmux chief new|edit|delete
write from an agent caller, any field, any Chief. Reads stay open.

* backend: Cloud Chiefs on Claude Opus 5.5 through the metered Bedrock route, Kimi as fallback

Lawrence 2026-10-14: Cloud Chiefs use the metered Claude route (the cr claude-david path) and bill
toward it. New model alias claude-opus-5-5: streamed Messages requests to
https://chatmux.dev/api/claude-david/v1/messages, signed by X-Stack-Auth from the staging-only
secret CHIEF_BRAIN_CLAUDE_STACK_AUTH. One holder object refreshes the access token before expiry
and keeps it in its own storage; a failed sign-in runs the turn on CHIEF_BRAIN_FALLBACK_MODEL
(staging: Workers AI Kimi) and logs only 'claude-david auth failed'. Staging defaults: turns on
Claude, compactor on Kimi. The daily turn cap stays.

Behavior test with the route and the refresh faked at their HTTP boundary: signed, streamed Opus
requests with the refreshed token and no API key, one refresh for two turns, the fallback on a
failed refresh, and no token bytes in replies, runs, the settings response or logs.

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

* backend: Claude Chiefs review fixes: owner allow-list and deployment cap, short refresh margin, no fake fallback

Review (REVISE, no token leak found) fixed:
- P1: the route bills one person's account. Claude only for owners in CHIEF_BRAIN_CLAUDE_OWNERS
  (staging: the dogfood owner), refused at PUT (chief_claude_unavailable) and at each turn; a
  daily cap for all Chiefs (CHIEF_BRAIN_CLAUDE_DAILY_TURNS, default 500) beside each Chief's 300.
- P2: refresh margin at most min(3 min, half the token's life), so a short-lived token is not
  refreshed on every request; test token lives 10 min.
- P2: with no fallback model a failed sign-in fails the turn (never the fake model); the compactor
  never runs on Claude (refused at PUT, mapped to the fallback).
- P3: a 401 from the route drops the stored access token; a rotated refresh token is kept in the
  holder's storage; refresh headers are asserted outside the fake.

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

* fix(cmux-tui): cmux chief refuses every write from an agent caller (cx-ebm.60.9)

Layer 1 stricter (hq-6d 2026-10-14): new, edit (any field, picture
included) and delete of any Chief exit 1 with origin.forbidden /
agent_caller for an agent caller. list stays open. A Chief changes its own
profile only through its profile tool.

* CmuxNext: sidebar rows badge their logo's corner instead of a count (#20492)

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

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

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

* CmuxNext: static status glyphs for the Static status mode

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

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

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

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

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

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

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

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

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

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

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

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

* feat: Codex-like status icon set

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

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

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

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

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

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

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

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

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

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

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

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

Red: the default is still symbols.

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

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

* feat: Codex-like status icons are the default

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

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

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

A literal color in a page stylesheet trips the theme-colors ratchet, so
the blue moves to STATUS_UNREAD in webviews/src/t…
teamleaderleo added a commit that referenced this pull request Oct 14, 2026
* test: a reported status shows its glyph and keeps a one-line row's pitch

cg-design-judge's sidebar Thunderdome (Tact #115): status set --state busy
drew no glyph, and a one-line status doubled the row (24 to 40 pt).

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

* fix: a one-line status keeps a one-line sidebar row's height

A one-line status on an otherwise one-line row (one reported entry, or
the hooks' status line) now sits dim on the right of the name's line,
truncated at 40% of the row, instead of a second line that grew the row
from 24 to 40 pt. A richer block (several entries, a log line or a
progress label) still draws under the name, as do rows with another
second-line element. The hover card lists every entry; VoiceOver reads
the text.

A status's state already reaches the row's badge on this base: busy
draws the grey spinner (the default row shows progress), and failed and
waiting draw their colored glyphs (#20492). The tests guard those.

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

---------

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

This branch was previously deployed

1 inactive deployment
cmux-tui-merge-tree — 442275e8 Deployed Oct 14, 2026 by teamleaderleo via publish the merge tree #646
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

show-leo Waiting for Leo to see captures before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants