Skip to content

cmux-tui: add familiar terminal shortcuts - #8698

Merged
lawrencecchen merged 205 commits into
mainfrom
feat-tui-terminal-shortcuts
Jul 27, 2026
Merged

lawrencecchen merged 205 commits into
mainfrom
feat-tui-terminal-shortcuts

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jul 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Adds shared modeless shortcut handling in cmux-tui for Cmd-K and Ctrl-L, plus familiar Cmd tab, split, close, and screen-navigation shortcuts.
  • Routes clear-history through one terminal-host action that clears scrollback and completed visible rows while preserving the OSC 133 prompt or the cursor soft-wrapped logical line. The action sends no input to the shell.
  • Preserves modified key identity across the host boundary, including Option-as-Alt, compact key events, associated text, and Kitty keyboard semantics.
  • Exposes the same capability-gated clear-history action through the control protocol, CLI, and generated bindings.
  • Includes three Swift 26.5 compatibility fixes required by the current main session-restore code.

Testing

  • cargo +1.97.1 fmt --all -- --check
  • ZIG=/opt/homebrew/opt/zig@0.15/bin/zig cargo +1.97.1 clippy --workspace --all-targets --all-features -- -D warnings
  • ZIG=/opt/homebrew/opt/zig@0.15/bin/zig cargo +1.97.1 test --workspace --all-targets -- --test-threads=1
  • ZIG=/opt/homebrew/opt/zig@0.15/bin/zig cargo +1.97.1 test -p cmux-tui config::tests::default_terminal_clear_shortcuts_clear_history -- --exact
  • Tagged macOS build: https://github.com/manaflow-ai/cmux/actions/runs/30084792568
  • Real Cmd-K and Ctrl-L keypresses in the tagged kclr3 app cleared completed history while preserving unsubmitted prompt text. The final head repeated the Ctrl-L action against the exact rebuilt cmux-tui binary.

Demo Video

  • No video attached. Verification used tagged-app screenshots plus terminal screen and scrollback assertions.

Review Trigger (Copy/Paste as PR comment)

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

Checklist

  • I tested the change locally
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed
  • I requested bot reviews after my latest commit
  • All code review bot comments are resolved
  • All human review comments are resolved

Summary by CodeRabbit

  • New Features

    • Added a clear-history command to remove retained terminal output and scrollback for the active PTY surface.
    • Added default Command/Super keyboard shortcuts, including Command-K, with a configuration option to disable them.
    • Added clearHistory support across CLI, protocol clients, and language bindings.
    • Added capability detection so unsupported servers reject the command safely.
  • Documentation

    • Documented the new command, keyboard shortcuts, configuration settings, and protocol capability requirements.
  • Bug Fixes

    • Terminal prompts and attached views now refresh correctly after history is cleared.
    • Alternate-screen content is preserved when clearing primary-screen history.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds a capability-gated clear-history command for PTY screen history and scrollback, exposes it through sessions, the UI, CLI, and language bindings, and adds configurable Super/Command shortcuts with tests and documentation.

Changes

Clear history feature

Layer / File(s) Summary
Protocol command and terminal clearing
cmux-tui/crates/cmux-tui-core/src/server.rs, cmux-tui/crates/cmux-tui-core/src/surface.rs
The server handles clear-history requests, while Surface::clear_history() clears PTY state, updates rendering, broadcasts VT output to mirrors, and emits relevant events.
Session and application integration
cmux-tui/crates/cmux-tui/src/session/..., cmux-tui/crates/cmux-tui/src/app.rs
Local and remote sessions invoke history clearing; remote sessions cache and enforce capabilities, while the application resets render and selection state and manages host keyboard protocol flags.
Super shortcut configuration
cmux-tui/crates/cmux-tui/src/config.rs
Adds the clear-history action, default Super bindings, Super chord parsing and matching, configurable disabling, and tests.
CLI and SDK command integration
cmux-tui/crates/cmux-tui/src/cli.rs, cmux-tui/bindings/*, cmux-tui/crates/cmux-tui/tests/cli.rs
Adds CLI and binding APIs, performs capability checks, wires conformance fixtures, and verifies screen, scrollback, and wire behavior.
Protocol and keyboard contracts
cmux-tui/docs/*, cmux-tui/spec/*
Documents the command contract, capability requirement, CLI usage, keybindings, Super shortcut configuration, binding expectations, and chord syntax.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant App
  participant Session
  participant Surface
  participant AttachMirror
  User->>App: Press Cmd/Super-k
  App->>Session: clear_history(surface)
  Session->>Surface: clear_history()
  Surface->>AttachMirror: Broadcast VT clear sequence
  Surface-->>Session: Clear terminal state
  Session-->>App: Complete ordered mutation
Loading
🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.10% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly reflects the main change: adding familiar terminal shortcuts in cmux-tui.
Description check ✅ Passed All required template sections are present and filled with concrete content, including testing, demo note, review trigger, and checklist.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed The Swift edits stay inside existing @MainActor UI types and add no new Sendable/shared-mutable isolation hazards or background store access.
Cmux Swift Blocking Runtime ✅ Passed Swift edits are formatting/type-annotation/import-only; no new waits, sleeps, locks, sync, or polling were introduced, and existing runModal() behavior was unchanged.
Cmux Browser Automation Off-Main ✅ Passed Diff only reformats ControlSurfaceResumeTarget.swift; no browser.* command routing, waits, or worker-lane automation code changed.
Cmux Expensive Synchronous Load ✅ Passed PASS: The Swift edits are limited to formatting/type-annotation/import changes; no new RestorableAgentSessionIndex.load() or similar heavy sync load was added to interactive/main-actor paths.
Cmux Cache Substitution Correctness ✅ Passed clear-history reads the live terminal state and only invalidates UI render caches; no persistence/history/snapshot path was switched to a stale cached value.
Cmux No Hacky Sleeps ✅ Passed No new fixed sleeps or polling were introduced in production TS/JS runtime code; the timing code found is existing timeout logic or test-only scaffolding.
Cmux Algorithmic Complexity ✅ Passed PASS: clear-history is linear over terminal rows, and shortcut handling scans only fixed-size binding tables; no new nested rescans on scalable collections.
Cmux Swift Concurrency ✅ Passed Touched Swift files only reformat/type-annotate session-restore code; no new DispatchQueue, Combine, completion-handler, or fire-and-forget Task patterns were introduced.
Cmux Swift @Concurrent ✅ Passed The only Swift change is a formatting-only switch in a synchronous NSAlert path; no async, @concurrent, or actor-isolation changes were introduced.
Cmux Swift Package Boundaries ✅ Passed The only Swift change is formatting in an AppKit/TerminalController helper; no independently testable domain logic was added or expanded in app Sources.
Cmux Swiftpm Lockfiles ✅ Passed No cmux-owned .gitignore, Package.swift, Package.resolved, or Xcode project lockfile changes appear in the PR diff; only vendored crossterm updates are present.
Cmux Swift Logging ✅ Passed Inspected the changed Swift runtime files; none add or change print/debugPrint/dump/NSLog usage or Logger constants, so the swift-logging rule isn’t violated.
Cmux User-Facing Error Privacy ✅ Passed PASS: the new user-facing error copy is generic (“Could not clear terminal history” / capability-gated unsupported) and does not expose vendor, provider, token, or raw upstream details.
Cmux Full Internationalization ✅ Passed The only visible diff is a Swift formatting-only change; no new user-facing text or localization entries were added or changed.
Cmux Swiftui State Layout ✅ Passed The only touched Swift file is an AppKit bridge helper; it adds no SwiftUI state, GeometryReader, lazy-row store refs, or render-time mutations.
Cmux Architecture Rethink ✅ Passed The only Swift change is a switch-body formatting tweak in ControlSurfaceResumeTarget.swift; no architectural, timing, or state-owner changes were introduced.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No standalone NSWindow/WindowGroup changes were introduced; touched Swift files are terminal/session logic, and cmuxAuxiliaryWindowIdentifiers remains unchanged.
Cmux Source Artifacts ✅ Passed The only changed path is a hand-written Swift source file, and no scratch, log, cache, or artifact paths appear in the diff.
Cmux No Test Or Debug Seam In Production Source ✅ Passed The PR’s Sources diff touches only 3 production files, and no added #if DEBUG, *ForTesting, TestHook, or debug-only members appear in the branch diff.
Cmux No Ambient Global State ✅ Passed The touched Swift code only adds methods inside existing types/extensions; no new file-scope funcs, mutable globals, or singleton state were introduced.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-tui-terminal-shortcuts

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 44f3819dd7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cmux-tui/crates/cmux-tui-core/src/surface.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmux-tui/crates/cmux-tui/src/app.rs`:
- Around line 5143-5149: Update the Action::ClearHistory handler to check the
active surface’s kind before calling session.clear_history, allowing the
mutation and related render-state/selection cleanup only for PTY surfaces. For
browser surfaces, no-op without enqueueing the unsupported mutation or altering
input/error state, matching the guards used by scroll_active and other
kind-specific actions.

In `@cmux-tui/crates/cmux-tui/src/config.rs`:
- Around line 723-726: Update the character-key handling around the TRACKED
modifier mask to compare the event’s non-Shift modifiers for exact equality
rather than using contains, while continuing to ignore Shift. Ensure Super
shortcuts do not match inputs with extra Ctrl or Alt modifiers, and add
regression coverage for those combinations in the related handling around lines
2219–2266.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 55a834b8-bfda-4b3c-bbdc-f513978d6e6b

📥 Commits

Reviewing files that changed from the base of the PR and between b17a87a and 44f3819.

📒 Files selected for processing (13)
  • cmux-tui/crates/cmux-tui-core/src/server.rs
  • cmux-tui/crates/cmux-tui-core/src/surface.rs
  • cmux-tui/crates/cmux-tui/src/app.rs
  • cmux-tui/crates/cmux-tui/src/cli.rs
  • cmux-tui/crates/cmux-tui/src/config.rs
  • cmux-tui/crates/cmux-tui/src/main.rs
  • cmux-tui/crates/cmux-tui/src/session/mod.rs
  • cmux-tui/crates/cmux-tui/tests/cli.rs
  • cmux-tui/docs/configuration.md
  • cmux-tui/docs/keyboard.md
  • cmux-tui/docs/protocol.md
  • cmux-tui/spec/cli.md
  • cmux-tui/spec/commands.md

Comment thread cmux-tui/crates/cmux-tui/src/app.rs
Comment thread cmux-tui/crates/cmux-tui/src/config.rs Outdated
@greptile-apps

greptile-apps Bot commented Jul 22, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds Cmd-K and Ctrl-L as modeless terminal shortcuts that clear scrollback history while preserving the active prompt, routing through a single clear_history_or_send_key action that falls back to encoding and forwarding the key when an alternate-screen application (e.g., vim, less) is active. A new Kitty keyboard protocol enhancement path normalises key events into a KeyboardInput value before routing, preserving Option-as-Alt semantics, base-layout identity, and associated text across overlays, bindings, browser input, and PTY forwarding.

  • Keyboard protocol lift: Crossterm Event::EnhancedKey events now flow through KeyboardInput::from_enhanced, carrying shifted_key, base_layout_key, and associated_text for lossless Kitty CSI-u forwarding; the host terminal is opted into PushKeyboardEnhancementFlags on startup and popped on all exit paths.
  • Clear-history action: The new ClearHistory action is wired into the config system (ctrl+l, cmd+k default), CLI, control-protocol server, remote client capability negotiation, and terminal-host binary protocol, with a clear-history-v1 / clear-history-key-v1 additive capability gate.
  • Swift 26.5 fixes: Three compatibility fixes in session-restore Swift files (explicit return in switch expressions, missing import CmuxWorkspaces, explicit type annotations) needed by current main.

Confidence Score: 5/5

Safe to merge. The clear-history action correctly handles primary-screen, alternate-screen, hosted, remote, and browser surface paths, releases the terminal lock before any PTY write, and returns RenderAction::None to let the SurfaceOutput event drive the repaint.

The three changed code paths (clear-history, Kitty protocol lift, Swift compat fixes) each have targeted unit tests. Lock ordering for the alternate-screen fallback is verified by a dedicated deadlock regression test. The additive capability gate ensures older server/client pairs cannot trigger the new command accidentally. No correctness-critical state is derived from heuristics, and no blocking synchronisation was introduced.

No files require special attention. The keys.rs behaviour change for HYPER/META (drop vs. silent strip) is worth a comment but does not affect any keyboard that a typical macOS Ghostty user would encounter.

Important Files Changed

Filename Overview
cmux-tui/crates/cmux-tui/src/app.rs Major input-handling refactor: Event normalised into TerminalInput/KeyboardInput before dispatch; clear-history shortcut special-cased in modeless path; host Kitty protocol pushed/popped on all exit paths including panic hook.
cmux-tui/crates/cmux-tui/src/keys.rs New KeyboardInput wrapper preserves Kitty-protocol fields (shifted_key, base_layout_key, associated_text, consumed_alt) across the routing pipeline; mods_from now returns None for HYPER/META events rather than silently stripping them.
cmux-tui/crates/cmux-tui/src/config.rs Adds ClearHistory action, super_shortcuts config flag, cmd/command/super modifier parser, and normalised Chord::matches that handles Shift+char equivalence; modeless_action_for special-cases Ctrl+ClearHistory bindings.
cmux-tui/crates/cmux-tui-core/src/surface.rs clear_history_or_encode_key correctly acquires terminal lock once for the screen decision and keyboard encoding, releases it before any PTY write, and uses the shared mark_output_dirty helper; three dedicated lock-ordering tests added.
cmux-tui/crates/cmux-tui-core/src/server.rs Adds ProtocolKeyInput serialisation (key/mods/consumed_mods/utf8/codepoint/action) with validation: text size cap, no control characters in text, consumed-mods subset of active mods, macos_option_as_alt invariant.
cmux-tui/crates/cmux-tui/src/session/remote.rs clear_history / clear_history_or_send_key check CLEAR_HISTORY_CAPABILITY and CLEAR_HISTORY_KEY_CAPABILITY respectively before sending, and map the unsupported error to the shared CLEAR_HISTORY_UNSUPPORTED_ERROR sentinel for the failure-status formatter.
cmux-tui/crates/cmux-tui/src/localization.rs New TerminalMessages struct adds clear_history_help, clear_history_failed, clear_history_unsupported, and keyboard_text_too_large; English and Japanese translations present.
cmux-tui/crates/cmux-tui/src/pty_input.rs Introduces failure_surface_id on PtyInputEvent and enqueue_coalescing_surface_operation/enqueue_surface_operation so clear-history failures carry the surface id to the failure formatter.
Sources/DockSplitStore+SessionSnapshot.swift Two Swift 26.5 compatibility fixes: explicit type annotations on sourceWorkspaceIdsByPanelId and relevantObservation to satisfy the stricter type checker.
Sources/ControlSurfaceResumeTarget.swift Swift 26.5 fix: implicit-return switch expression replaced with explicit return statements.

Sequence Diagram

sequenceDiagram
    participant Host as Host Terminal (Kitty)
    participant App as App (handle_keyboard)
    participant Config as Config/Keys
    participant Session as OrderedSession
    participant Surface as Surface (PTY)
    participant PtyQ as PtyInputSender

    Host->>App: Event::EnhancedKey (Cmd-K / Ctrl-L)
    App->>App: "KeyboardInput::from_enhanced()<br/>(preserves shifted_key, base_layout_key,<br/>associated_text, consumed_alt)"
    App->>Config: modeless_action_for(binding_key, fallback)
    Config-->>App: Some(ClearHistory)
    App->>App: run_clear_history_shortcut(input)

    alt Active surface is PTY (primary screen)
        App->>App: input.into_terminal_input() → KeyInput
        App->>Surface: scroll_to_bottom()
        App->>Session: clear_history_or_send_key(surface_id, key_input)
        Session->>PtyQ: enqueue_surface_operation(clear terminal history)
        PtyQ->>Surface: surface.clear_history_or_encode_key(Some(key_input))
        Surface->>Surface: "apply_clear_history_transition()<br/>→ ClearHistoryTransition::Cleared"
        Surface->>Surface: broadcast_attach_output(clear_bytes)
        Surface->>Surface: mark_output_dirty() → MuxEvent::SurfaceOutput
        App-->>App: "RenderAction::None<br/>(repaint driven by SurfaceOutput)"
    else Active surface is PTY (alternate screen, e.g. vim)
        App->>Session: clear_history_or_send_key(surface_id, key_input)
        Session->>Surface: clear_history_or_encode_key(Some(key_input))
        Surface->>Surface: "apply_clear_history_transition()<br/>→ ClearHistoryTransition::EncodedFallback(bytes)"
        Surface->>Surface: writer.write_all(encoded_key_bytes)
        Note over Surface: Key forwarded to shell/vim, terminal lock released before write
    else Active surface is Browser
        App->>App: forward_key(input) → forward_browser_key()
        Note over App: Key handled natively by WebView
    end
Loading

Reviews (13): Last reviewed commit: "fix(macOS): return resume policy explici..." | Re-trigger Greptile

Comment thread cmux-tui/crates/cmux-tui-core/src/surface.rs Outdated
Comment thread cmux-tui/crates/cmux-tui/src/app.rs
Comment thread cmux-tui/spec/commands.md
@cursor

cursor Bot commented Jul 23, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

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

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e91f5300cf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cmux-tui/crates/cmux-tui/src/app.rs Outdated
Comment thread cmux-tui/crates/cmux-tui/src/session/mod.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
cmux-tui/crates/cmux-tui/src/config.rs (1)

717-729: 🎯 Functional Correctness | 🟠 Major

Still require exact non-Shift modifier matches for character chords.

contains allows extra modifiers, so Ctrl+Cmd+K or Alt+Cmd+K can still trigger the new Cmd+K clear-history binding. Compare the CONTROL | ALT | SUPER mask for equality, continue ignoring Shift for character codes, and add regressions for these combinations.

Proposed fix
         let mods_match = if matches!(self.code, KeyCode::Char(_)) {
-            key.modifiers.contains(self.mods & !KeyModifiers::SHIFT)
+            const CHAR_TRACKED: KeyModifiers = KeyModifiers::CONTROL
+                .union(KeyModifiers::ALT)
+                .union(KeyModifiers::SUPER);
+            key.modifiers & CHAR_TRACKED == self.mods & CHAR_TRACKED
         } else {

Also applies to: 2213-2248

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmux-tui/crates/cmux-tui/src/config.rs` around lines 717 - 729, Update
KeyEvent matching in matches so character chords compare CONTROL | ALT | SUPER
modifiers for exact equality while continuing to ignore SHIFT; retain the
existing tracked-modifier equality behavior for non-character codes. Add
regression coverage for extra Cmd combinations such as Ctrl+Cmd+K and Alt+Cmd+K
not matching a Cmd+K binding.
cmux-tui/crates/cmux-tui/src/app.rs (1)

5158-5164: 🎯 Functional Correctness | 🟠 Major

Still guard Action::ClearHistory by surface kind.

Line [5159] only checks for an active surface ID. On a browser tab, this enqueues the unsupported history-clear mutation, causing a routine shortcut to enter the generic failure path and expose an internal error. No-op for browser surfaces, matching the other kind-specific actions, and add a browser regression test.

As per coding guidelines, user-facing errors should use product terms and keep implementation details in sanitized logs or telemetry.

Proposed fix
             Action::ClearHistory => {
-                if let Some(surface) = self.active_surface() {
+                if let Some((surface, handle)) = self.active_surface_with_handle()
+                    && handle.kind() != SurfaceKind::Browser
+                {
                     self.session.clear_history(surface);
                     self.render_states.remove(&surface);
                     self.selection = None;
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmux-tui/crates/cmux-tui/src/app.rs` around lines 5158 - 5164, Update the
Action::ClearHistory handler to verify that the active surface is a supported
non-browser kind before calling session.clear_history, removing render state, or
clearing selection; browser surfaces must no-op. Add a regression test covering
ClearHistory on a browser surface and confirming no unsupported mutation or
user-facing internal error occurs.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@cmux-tui/crates/cmux-tui/src/app.rs`:
- Around line 5158-5164: Update the Action::ClearHistory handler to verify that
the active surface is a supported non-browser kind before calling
session.clear_history, removing render state, or clearing selection; browser
surfaces must no-op. Add a regression test covering ClearHistory on a browser
surface and confirming no unsupported mutation or user-facing internal error
occurs.

In `@cmux-tui/crates/cmux-tui/src/config.rs`:
- Around line 717-729: Update KeyEvent matching in matches so character chords
compare CONTROL | ALT | SUPER modifiers for exact equality while continuing to
ignore SHIFT; retain the existing tracked-modifier equality behavior for
non-character codes. Add regression coverage for extra Cmd combinations such as
Ctrl+Cmd+K and Alt+Cmd+K not matching a Cmd+K binding.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 71fcc4d5-5cbd-4003-aedc-fb91513b19f3

📥 Commits

Reviewing files that changed from the base of the PR and between 44f3819 and e91f530.

📒 Files selected for processing (4)
  • cmux-tui/crates/cmux-tui/src/app.rs
  • cmux-tui/crates/cmux-tui/src/config.rs
  • cmux-tui/docs/configuration.md
  • cmux-tui/docs/keyboard.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cmux-tui/crates/cmux-tui/src/app.rs (1)

7999-8023: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make this regression test exercise the child redraw path.

test_mux launches sleep 30, and the test only mutates the in-memory terminal directly. The child therefore does not render a prompt or consume \x0c; this test would still pass if the redraw write were removed and does not verify prompt/input preservation. Use an interactive child or test double, wait for its redraw, and assert pending input survives while prior output is gone.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmux-tui/crates/cmux-tui/src/app.rs` around lines 7999 - 8023, Update
command_k_clears_prior_output_before_the_child_redraws to use an interactive
child or test double that renders a prompt, wait for the child redraw to
complete, and provide pending input before invoking Command-K. Assert that the
redraw path consumes the clear sequence, removes prior output, and preserves the
prompt and pending input; avoid relying only on direct terminal mutations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmux-tui/crates/cmux-tui-core/src/surface.rs`:
- Line 897: Remove the REDRAW_PROMPT form-feed injection from the clear_history
path in surface.rs. After clearing and mirroring the terminal state via
vt_write/broadcast, return Ok(()) without calling write_bytes on the PTY;
preserve the committed clear behavior and avoid reporting child-write errors.

---

Outside diff comments:
In `@cmux-tui/crates/cmux-tui/src/app.rs`:
- Around line 7999-8023: Update
command_k_clears_prior_output_before_the_child_redraws to use an interactive
child or test double that renders a prompt, wait for the child redraw to
complete, and provide pending input before invoking Command-K. Assert that the
redraw path consumes the clear sequence, removes prior output, and preserves the
prompt and pending input; avoid relying only on direct terminal mutations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 495199c4-d584-4978-a767-acf23b77c78a

📥 Commits

Reviewing files that changed from the base of the PR and between e91f530 and c63c43a.

📒 Files selected for processing (7)
  • cmux-tui/crates/cmux-tui-core/src/surface.rs
  • cmux-tui/crates/cmux-tui/src/app.rs
  • cmux-tui/crates/cmux-tui/src/cli.rs
  • cmux-tui/crates/cmux-tui/tests/cli.rs
  • cmux-tui/docs/configuration.md
  • cmux-tui/docs/keyboard.md
  • cmux-tui/spec/commands.md

Comment thread cmux-tui/crates/cmux-tui-core/src/surface.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cmux-tui/crates/cmux-tui/src/config.rs (1)

747-747: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add the promised default Super bindings.

The command helper is only used for SUPER+K; because modeless_action_for searches self.bindings, SUPER+T/W/D currently resolve to None, exactly as this test asserts. Add the intended Cmd+T, Cmd+W, Cmd+D, and Cmd+Shift+D bindings and update the test to expect their actions.

Proposed fix
                 command(KeyCode::Char('k'), Action::ClearHistory),
+                command(KeyCode::Char('t'), Action::NewTab),
+                command(KeyCode::Char('w'), Action::CloseTab),
+                command(KeyCode::Char('d'), Action::SplitRight),
+                command(KeyCode::Char('D'), Action::SplitDown),
-        assert_eq!(action(KeyCode::Char('t'), KeyModifiers::SUPER), None);
-        assert_eq!(action(KeyCode::Char('w'), KeyModifiers::SUPER), None);
-        assert_eq!(action(KeyCode::Char('d'), KeyModifiers::SUPER), None);
+        assert_eq!(action(KeyCode::Char('t'), KeyModifiers::SUPER), Some(Action::NewTab));
+        assert_eq!(action(KeyCode::Char('w'), KeyModifiers::SUPER), Some(Action::CloseTab));
+        assert_eq!(action(KeyCode::Char('d'), KeyModifiers::SUPER), Some(Action::SplitRight));
+        assert_eq!(
+            action(KeyCode::Char('D'), KeyModifiers::SUPER | KeyModifiers::SHIFT),
+            Some(Action::SplitDown)
+        );

Also applies to: 809-809, 2217-2226

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmux-tui/crates/cmux-tui/src/config.rs` at line 747, Extend the bindings
initialized near the command helper to include Super+T, Super+W, Super+D, and
Super+Shift+D with their intended actions, not just Super+K. Update the related
modeless_action_for test to expect those actions instead of None, preserving the
existing binding lookup behavior.
♻️ Duplicate comments (1)
cmux-tui/crates/cmux-tui-core/src/surface.rs (1)

892-923: 🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift

Unresolved: clear_history still injects \x0c into the PTY child, contradicting "without sending bytes to the child".

This diff only refactors dirty-flag/event emission into mark_output_dirty(); the underlying issue flagged in the previous review round is still present: self.write_bytes(REDRAW_PROMPT) sends a literal form-feed as input to the child process. write_bytes is documented as "Write input bytes to the PTY child" — so any foreground program (not just a readline shell) receives this byte, and the PR objective ("clears the active PTY screen and scrollback without sending bytes to the child") is not actually satisfied. It also means a transport error from this input write is reported as a clear_history failure even though the authoritative clear + mirror broadcast already succeeded.

Please either redesign so the prompt redraw doesn't rely on injecting input into the child, or update the objective/documentation to accurately describe that a redraw keystroke is sent, and decouple its error from the already-committed clear.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmux-tui/crates/cmux-tui-core/src/surface.rs` around lines 892 - 923, Update
clear_history so it no longer calls write_bytes with REDRAW_PROMPT or otherwise
injects input into the PTY child; preserve the terminal clear, attached-output
broadcast, render-generation update, and dirty/event handling, and ensure the
method’s result reflects only the clear operation rather than a prompt-input
transport error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@cmux-tui/crates/cmux-tui/src/config.rs`:
- Line 747: Extend the bindings initialized near the command helper to include
Super+T, Super+W, Super+D, and Super+Shift+D with their intended actions, not
just Super+K. Update the related modeless_action_for test to expect those
actions instead of None, preserving the existing binding lookup behavior.

---

Duplicate comments:
In `@cmux-tui/crates/cmux-tui-core/src/surface.rs`:
- Around line 892-923: Update clear_history so it no longer calls write_bytes
with REDRAW_PROMPT or otherwise injects input into the PTY child; preserve the
terminal clear, attached-output broadcast, render-generation update, and
dirty/event handling, and ensure the method’s result reflects only the clear
operation rather than a prompt-input transport error.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7157c5cb-bfb5-4336-ac13-7b211590653f

📥 Commits

Reviewing files that changed from the base of the PR and between c63c43a and b63c249.

📒 Files selected for processing (10)
  • cmux-tui/crates/cmux-tui-core/src/server.rs
  • cmux-tui/crates/cmux-tui-core/src/surface.rs
  • cmux-tui/crates/cmux-tui/src/app.rs
  • cmux-tui/crates/cmux-tui/src/cli.rs
  • cmux-tui/crates/cmux-tui/src/config.rs
  • cmux-tui/crates/cmux-tui/src/session/mod.rs
  • cmux-tui/crates/cmux-tui/src/session/remote.rs
  • cmux-tui/docs/protocol.md
  • cmux-tui/spec/README.md
  • cmux-tui/spec/commands.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cmux-tui/spec/commands.md (1)

660-666: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Align the documented browser error with the server response.

The spec documents browser surface does not support PTY/VT socket commands, but surface.rs:904 returns browser surface does not have a VT terminal. Because bindings preserve server error strings, choose one canonical message and update the implementation, specification, and tests consistently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmux-tui/spec/commands.md` around lines 660 - 666, Align the browser-surface
error message across the server implementation at surface.rs:904, the commands
specification, and all related tests. Choose one canonical string and update
every assertion and documented entry to use it, preserving the existing behavior
for other error conditions.
♻️ Duplicate comments (1)
cmux-tui/crates/cmux-tui-core/src/surface.rs (1)

893-898: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not inject form-feed into the PTY child.

write_bytes(REDRAW_PROMPT) sends \x0c to arbitrary foreground programs and can return an error after the clear is already committed. Return Ok(()) after updating mirrors/render state; add a regression test that verifies clearing does not write child input.

Also applies to: 925-926

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmux-tui/crates/cmux-tui-core/src/surface.rs` around lines 893 - 898, Remove
the REDRAW_PROMPT form-feed write from Surface::clear_history and return Ok(())
after updating the clear sequence, mirrors, and render state. Ensure clearing
never sends input to the PTY child or fails because of a child write, and add a
regression test verifying no child input is written.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@cmux-tui/spec/commands.md`:
- Around line 660-666: Align the browser-surface error message across the server
implementation at surface.rs:904, the commands specification, and all related
tests. Choose one canonical string and update every assertion and documented
entry to use it, preserving the existing behavior for other error conditions.

---

Duplicate comments:
In `@cmux-tui/crates/cmux-tui-core/src/surface.rs`:
- Around line 893-898: Remove the REDRAW_PROMPT form-feed write from
Surface::clear_history and return Ok(()) after updating the clear sequence,
mirrors, and render state. Ensure clearing never sends input to the PTY child or
fails because of a child write, and add a regression test verifying no child
input is written.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: dcebbdd7-edae-44d9-b0d2-5b549f398c1f

📥 Commits

Reviewing files that changed from the base of the PR and between b63c249 and 4810146.

📒 Files selected for processing (15)
  • cmux-tui/bindings/conformance/fixtures.json
  • cmux-tui/bindings/conformance/runner.py
  • cmux-tui/bindings/go/client.go
  • cmux-tui/bindings/go/client_test.go
  • cmux-tui/bindings/java/src/com/cmux/CmuxClient.java
  • cmux-tui/bindings/java/tests/com/cmux/WireCaptureTest.java
  • cmux-tui/bindings/python/cmux/client.py
  • cmux-tui/bindings/python/tests/test_protocol.py
  • cmux-tui/bindings/rust/src/lib.rs
  • cmux-tui/bindings/typescript/src/client.ts
  • cmux-tui/bindings/typescript/src/protocol/commands.ts
  • cmux-tui/bindings/typescript/test/client.test.ts
  • cmux-tui/crates/cmux-tui-core/src/surface.rs
  • cmux-tui/spec/bindings.md
  • cmux-tui/spec/commands.md

…rtcuts

# Conflicts:
#	cmux-tui/crates/cmux-tui-core/src/server.rs
#	cmux-tui/crates/cmux-tui/src/app.rs
#	cmux-tui/crates/cmux-tui/src/session/remote.rs
#	cmux-tui/docs/protocol.md
#	cmux-tui/spec/README.md
#	cmux-tui/spec/commands.md
@lawrencecchen
lawrencecchen merged commit f7a6f55 into main Jul 27, 2026
7 checks passed
@lawrencecchen
lawrencecchen deleted the feat-tui-terminal-shortcuts branch July 27, 2026 11:09
@coderabbitai coderabbitai Bot mentioned this pull request Jul 28, 2026
4 of 6 tasks
lawrencecchen added a commit that referenced this pull request Oct 10, 2026
… terminal lock

Root cause: build_frame_locked called mark_output_dirty (emit_terminal_output
takes Mux::state) with the terminal lock held. Mux code holds Mux::state while
it reads Surface::size (geometry), and a resize holds geometry while it takes
the terminal lock, so frame -> reader -> resize formed a cycle and every render
then queued behind the terminal lock. The cycle predates the surface.rs split:
the same call has been in build_frame_locked since f7a6f55 (#8698); the
move-only split did not change it.

Fix: one documented lock order on PtyTerminalRuntime (Mux::state -> geometry
-> term -> runtime -> leaf locks); no path calls an emit_terminal_* method
while it holds term or geometry.
- build_frame_locked no longer calls out. build_producer_frame takes the
  terminal guard, builds, drops the guard, then marks output dirty; the frame
  producer and publish_final_frame use it, finish_hosted_exit marks after
  unlocking.
- The local PTY reader publishes title changes after unlocking, as the hosted
  reader already did.
- Parser on_bell callbacks (local and hosted) only count bells (PendingBells);
  the reader emits them after it releases term and geometry. This also stops
  hosted replay bells being emitted under the geometry lock.
teamleaderleo added a commit that referenced this pull request Oct 10, 2026
* cmux-next sidebar: space marks paint the strip step token over their color, opaque

Root cause: ThemeTokens.derive set stripStep to black at alpha 0 (when #17241 made strips paint the window
ground), while design-tokens.json theme.stripStep is black at 0.22 dark / 0.05 light; the space marks
blended toward it with NSColor.blended, which also blends alpha, so inactive marks drew 20% transparent
and the softening never followed the token.

Fix: stripStep is the design token again (its only consumer is the marks; strips still paint
stripBackground == window). ProfileBarView.markColor paints the step over the mark color (full strength
for the other spaces, a quarter for the current one), so the result is opaque. Palette.stripStep is read
inside the bar's theme scope (performWithTheme), as before.

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

* refactor(mux): move the public option/result types and small state guards out of mux.rs

Move-only:
- mux/results.rs: run placements and results, terminal close/resolve/place/move results, spawn options and env validation, workspace placement and mutation results, sidebar plugin options and status, config reload errors, and daemon handoff requests.
- mux/guards.rs: pending terminal host binding/release guards, the pending workspace surface guard, the resource wait wake, config reload state, and the client focus record.
Public paths stay the same through re-exports. mux.rs 1301 -> 975.

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

* test(cmux-next): the real-click All chats test orders its window front (as the Home first-click test)

* test(cmux-next): the empty query's Recent holds any row kind the user ran (red)

A workspace, tab or setting the user ran never showed in Recent, because
Recent took only rows shown for an empty query (catalog actions). Bead
cx-9ce.27.

* feat(cmux-next): the palette's Recent holds any row kind the user ran

Recent on the empty query took only rows shown for an empty query, so a
workspace, tab or setting the user ran never appeared. Now any enabled
row with usage may be Recent; a row gated by a query prefix never is.
Eval learning cases 9/9. Bead cx-9ce.27.

* test(optchat): the probe warns when the harness does not know the compactor model (red)

Claude Code 2.1.287 on cmux-lawrence-2 does not know claude-haiku-5-5:
it prices the model at its default rates (20x) and checks it with one
more max_tokens=1 request in every session. Warn-only: one host.log line
and a compactor.model_unknown trace event; the probe still passes.

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

* test(acpmux): the Claude Code init reports its version in the session info (red)

OptChat's compactor probe needs it to warn when the session's Claude Code
is too old to know the compactor model.

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

* fix(optchat): the compactor probe warns when Claude Code does not know the model

acpmux's Claude Code adapter now puts the init's claude_code_version in
the session info. The compactor probe compares it with the first version
known to list the compactor model (claude-haiku-5-5: 2.1.293; 2.1.287
does not): an older one writes one host.log line and a
compactor.model_unknown trace event {harness, cc_version, model}. Never
a note, never a failure.

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

* optchat-chief: each started subagent's spawn line carries its link, [a1](cmux://chief/<home id>/session/<session id>), and the Chief names subagents with it

Lawrence 2026-10-09: "you need to be able to link to a subagent so I can
just click here to get to it". The link is the app deeplink of the
subagent's session in this Chief home; the app opens the tab that shows it.

* scripts/cmux-next: chief-subagents-live.py passes this host's subrouter route env to the app, so its Chief and compactor sign in

* test: a Chief subagent link (cmux://chief/<home id>/session/<id>) is a Home link, its click runs link.open, and link.open selects only this Chief's subagent tab (red)

* app: a Chief subagent's link in Home opens its workspace and chat tab

Lawrence 2026-10-09: "you need to be able to link to a subagent so I can
just click here to get to it". One deeplink form, cmux://chief/<home id>/
session/<session id> (DeepLink .chiefSession), written by the Chief and
rendered by Home: MarkdownLinkPolicy's extraRule allows exactly that form
(HomeAppLinks), a click goes to the host (never NSWorkspace), and the app
runs it as this build's link.open, only for its own Chief home. link.open
selects the tab that shows the session with host chief:<home id>; another
home, a session no subagent tab shows, or this Mac's own tab is refused,
and no tab is made.

* cmux-tui: failing test for browser history commands during attach

browser-back, browser-forward and browser-reload sent while a browser
surface has no CDP session are acked at enqueue and then fail in the
worker ("browser is still starting"). The new test expects a refusal
before the ack. Also covers the navigation retry when a provider lease
replacement detaches the session while Page.navigate is in flight
(passes already), and that history commands are accepted once attached.

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

* cmux-tui: refuse browser history commands before the surface attaches

browser-back, browser-forward and browser-reload were acked at enqueue
and then failed in the worker when the surface had no CDP session yet.
They now refuse before the ack ("browser is still starting; retry after
it attaches", or "browser failed: ..." after a permanent bootstrap
failure). They are not held: history belongs to the attached page, and
attach already loads the record's URL. The raw server commands also
refuse frontend-rendered surfaces, like browser-navigate.

back/forward/reload (browser/surface_navigation.rs) go through enqueue_history in
browser/navigation_hold.rs; the
accepted-once-attached test now checks the commands reach CDP.

Rebased by the unlanded-work audit from feat-cmux-next-browser-history-hold
(ad1a410012b4, PR 17098) onto the browser.rs module split.

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

* docs(cmux-next): mux-rs refactor log, final landing SHA

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

* test(cmux-tui-core): reproduce the frame/resize/render/mux-reader surface lock cycle (red)

The frame path calls mark_output_dirty (mux state lock) while it holds the
terminal lock; resize holds geometry and waits for the terminal; mux threads
hold state and read geometry. The test forces that interleaving through a
cfg(test) hook at the output event.

* fix(cmux-tui-core): never call into the mux while holding the surface terminal lock

Root cause: build_frame_locked called mark_output_dirty (emit_terminal_output
takes Mux::state) with the terminal lock held. Mux code holds Mux::state while
it reads Surface::size (geometry), and a resize holds geometry while it takes
the terminal lock, so frame -> reader -> resize formed a cycle and every render
then queued behind the terminal lock. The cycle predates the surface.rs split:
the same call has been in build_frame_locked since f7a6f559a4a4 (#8698); the
move-only split did not change it.

Fix: one documented lock order on PtyTerminalRuntime (Mux::state -> geometry
-> term -> runtime -> leaf locks); no path calls an emit_terminal_* method
while it holds term or geometry.
- build_frame_locked no longer calls out. build_producer_frame takes the
  terminal guard, builds, drops the guard, then marks output dirty; the frame
  producer and publish_final_frame use it, finish_hosted_exit marks after
  unlocking.
- The local PTY reader publishes title changes after unlocking, as the hosted
  reader already did.
- Parser on_bell callbacks (local and hosted) only count bells (PendingBells);
  the reader emits them after it releases term and geometry. This also stops
  hosted replay bells being emitted under the geometry lock.

* refactor(cmux-tui-core): move configure_agent_browser_session to surface/options.rs (move-only)

Keeps surface.rs under its godfile baseline (1690 -> 1678 lines) after the
lock-order fix.

* cmux-tui: TerminalReservationRequest.tab_id after the tip moved the type to mux/results.rs

* test(home): the subagent link passes the form check, the policy and the inline parser

* cmux-tui-core: move the terminal host's main loop to shared over a HostListener seam (cx-ko2e B16)

Move-only. serve_terminal_host_stdio (bootstrap, launch, endpoint bind,
record + lease publication, Ready, accept loop) leaves mod unix for
terminal_host_runtime/shared/host_serve/stdio.rs, so the Windows host
shares one accept loop. OS edges are sys seams with the old code behind
them:
- sys::HostListener (sys/unix/listener.rs): bind = UnixListener::bind +
  set_permissions 0600 + set_nonblocking, in that order; accept drops the
  peer address; wait(waker, timeout) is the old two-descriptor poll (None
  = -1, Some(remaining) = millis + 1 saturating) and returns whether the
  waker fired, or the raw poll error. The loop keeps its handling: a
  non-EINTR error backs off, success resets the backoff and drains a
  fired waker.
- sys::adopt_launch and sys::host_signals: the Unix modules
  (unix/adopt_launch.rs, unix/host_signals.rs, now pub(crate)).
Windows stubs (HostListener, publication lock, adopt_launch, host_signals)
fail closed until sys/windows.rs. Public re-export unchanged (cfg(unix)).

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

* test(cmux-tui-core): a place across a group publishes the group's change (red)

GPUI (cmux2-85) found that workspace.place renumbers a group's place in the
same transaction (personal-mixed-order-v1), so the group's top_index changes,
but the commit's session.events batch has no workspace_group change: mirrors
keep a stale top_index. The new test moves a workspace across a group and
expects a workspace_group state_upsert with the new top_index, and no group
change for a place that does not cross it.

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

* fix(cmux-tui-core): every personal commit publishes the groups whose place it moved

A workspace.place keeps each group at its place among the other rows, so a
workspace that crosses a group changes the group's top_index in the same
transaction. apply_personal now compares the group snapshots before and
after any personal state change and adds a workspace_group state_upsert for
each changed group the change did not already publish. The home creation
(place_home_first moves home to the front) queues the same group changes.
GPUI (cmux2-85) can drop its re-read of the group list after each change.

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

* test(home): the subagent link test uses its own message id (the measure cache keys by id)

* test(home): the subagent link sits among the rows the https link test uses

* test(cmux-tui): a journal writer commit holds no registry or state lock (red)

Terminal output batches take the workspace registry lock for their whole
commit, including the fsync, so every create and every registry user waits
behind them. Two tests pin the target lock order (registry -> connection ->
state; the writer takes only the connection lock).

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

* home: HomeMarkdown's link check is MarkdownLinkPolicy, so a Chief subagent link is a link in Home's renderer too

* fix(cmux-tui): the journal writer takes only the registry connection lock

The session journal writer held the workspace registry lock for every
terminal output batch, including its fsync, so every registry user waited
behind terminal output.

The registry's SQLite connection now sits behind its own reentrant lock
(workspace_registry/registry_connection.rs). Lock order: workspace registry
-> registry connection -> state. The writer takes only the connection lock
(never registry, never state; debug-asserted on the registry mutex). Request
threads that hold the registry and then lock state take the connection
first (Mux::lock_state_pinned), so state is never held across a writer
fsync. Transactions use unchecked_transaction on the shared guard; the
writer batch append moved to journal_extensions/writer_batch.rs on the
connection. Writer-blocking tests now hold the connection lock; a new test
proves a flow waiting behind a writer commit holds no state lock.

parking_lot (arc_lock) instead of std::sync::Mutex: std has no stable
reentrant mutex, registry methods nest connection access, and the pin must
be an owned guard so its holder can still call &mut WorkspaceRegistry
methods; the writer uses its try_lock_until deadline.

Moves for the godfile limit only: presentation snapshot, resource event
page, creation lookup, plugin generation, terminal snapshot reader, the
public projection store tests and three terminal journal tests.

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

* fix(optchat): the size loop retries fragments and keeps the try that keeps the most

Kept memory of the 9f851bd1dfb9 import: merge 288+32 of two 500-byte
lines became "Tests fail on pool.rs" (21 bytes): a size retry answered
with a fragment, it fit, and 32 messages' facts were lost. And when no
try fit, the loop kept the SHORTEST try, the one that dropped the most.

size_check_for (the host's size loop gives it the step's <input>):
- a reply under an eighth of the limit, for an input over the limit, is
  a fragment: it is retried ("Too short: ...") while tries are left, and
  never kept over a real try;
- of the real tries that fit, the longest is kept;
- when the tries run out with none fitting, the real try that keeps the
  most of the input's words (4+ characters) is kept, the shortest at a
  tie.
size_check_in (no input) keeps the old rules but the fragment one. Test
fakes that answered a 1,300-byte message with a 25-byte line now answer
with a fair-size line (DEPLOY_LINE).

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

* ci: style ratchets warn under a hard ceiling and file their debt (#18894)

* test(ci): style ratchets warn under a hard ceiling (red)

A god type, an l10n style error or a custom scrollbar under its ceiling
(GODFILES_WARN_SLACK, L10N_STYLE_WARN_MAX, SCROLLBARS_WARN_MAX) is a
ratchet debt warning; over it, it fails. Red: every hit still fails.

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

* ci: style ratchets warn under a hard ceiling and file their debt

God files and types, l10n style errors and custom scrollbars now have a
warning tier: under its ceiling a hit is a ratchet debt annotation and
passes; over it, it fails as before. The checks job sets the ceilings
(GODFILES_WARN_SLACK=20: allowance + 20% of the budget, so a new type
fails past 1200 lines; L10N_STYLE_WARN_MAX=100; SCROLLBARS_WARN_MAX=10).
Placeholder and table errors, concurrency, notification centers and
crash safety keep no warning tier.

A new ratchet-debt job (issues: write, base code only, feat-cmux-next
pushes) keeps one open issue listing the debt at the tip and closes it
when it is paid.

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

* test(ci): ratchet ceilings never reach the checks job's script tests (red)

Job-wide, GODFILES_WARN_SLACK and SCROLLBARS_WARN_MAX reached the script
tests in the same job, which expect every hit to fail: #18894's run had
check-scrollbars.test.sh and the god-file scope tests red. Red: the
ceilings are job env.

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

* ci: set each ratchet ceiling on its own step

The ceilings were job env, so the checks job's script tests (which
expect every hit to fail) saw them too. Each now lives on the step of
the check it relaxes.

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

---------

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

* style(webviews): format the agent pane automation and direct tests (vp fmt; react-apps-check red)

0d93d66e8593 and 91f9f45ff124 landed lines that vp check refuses. The run on
1f33d128993c skipped react-apps-check, so the first push that ran it went red.

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

* cmux-tui: split-client-keys-v1 proven over the daemon socket (terminal_host_recovery/split_client_keys.rs) instead of an in-process unit test (NO UNIT TESTS rule); poison-safe registry lock in the prelaunch skip

* test(cmux-next): palette learning cases for hidden rows and Reset Ranking (red)

Behavior at the palette eval boundary (replay harness): a hidden row
leaves the results, a whole-title query still finds it, showing it again
brings it back, Reset Ranking drops a learned lift. Bead cx-9ce.27.

* backend: red behavior test, a Stack member with no permission entries answers 503 forever (cx-6wc8)

Drives the real POST /v1/hooks/stack route and the real Stack client; only
Stack's HTTP API is fake. A member with zero permissions and an owner the
team-wide list leaves out must not make the team sync answer 503.

* feat(cmux-next): hide a palette row or reset its ranking (palette-usage-v1 row controls)

Raycast bar row controls, owned by the daemon's usage history:
palette_usage.hide {key, hidden} and palette_usage.forget {key} (Reset
Ranking: forgets the row's uses and learned picks); the snapshot carries
the hidden keys (catalog 210 -> 212 ops, SDK descriptors, app-host
scopes, docs; MCP and the CLI exclude them). Both return only {revision}
and hash the key in the replay fingerprint.

App: a row's Actions offer Hide from Palette (Show in Palette when
hidden) and Reset Ranking; the store sends them in order with the
recorded runs. Ranker: a hidden row is gone from Recent and every match
except a query that is its whole title. Strings in all palette locales.

Learning cases 13/13. Bead cx-9ce.27.

* backend: red behavior test reads team.members.list by its real answer shape (cx-6wc8)

* backend: a Stack member missing from the team-wide permission list is read alone (cx-6wc8)

The team sync threw (503, Svix retried forever) when a listed member had no
entry in GET /team-permissions?team_id&recursive=true, because a truncated list
and a member with zero permissions look the same there. A project whose default
member permission set is empty made every team sync fail.

Now each such member's own permissions are read once (user_id, recursive). A
successful answer with zero permissions gives role member; an owner a truncated
list left out keeps $delete_team and stays owner; a failed direct read still
throws, so the delivery answers 503 and changes nothing. getTeamMember treats
an empty direct answer the same way. Two unit cases that asserted the old
throw are deleted; the behavior test team-stack-empty-perms covers both paths.

* cx-m0p7 adapters: red tests for the VS Code family and Zed sources and the chat index relay

* cx-m0p7 adapters: VS Code family and Zed project sources on project.sync; app relays the chat index as project.observe

* refactor(cmux-tui): image paste tests keep their #[path] files (no test moves)

NO UNIT TESTS rule (2026-10-10): a move-only crate move keeps the tests where they are. image_paste_tests.rs and image_paste_file_tests.rs stay next to their modules with the same #[path] attributes as in cmux-tui-core.

* docs(cmux-next): crate-split lane 2 step 1 line names the unchanged test files

* test(cmux-next): CEFLifecycleRaceTests runs the turn that applies a conceal

Since f688bed646c7 a conceal hides the CEF host view in a block queued for
the end of the run-loop turn (same-pane switch keeps the page window on
screen). enteringAWindowDoesNotShowAPageTheLifecycleHid still read isHidden
in the same turn and failed in 8 of 8 full fleet runs at 2b4a85dfcd52. It
now runs one zero-wait run-loop pass after the conceal and after re-entry.

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

* fix(cmux-next): the local remote browser host's start deadline never stops a host that listened

After its sleep the deadline task returns when it was cancelled. start and
the task both run on the main actor, so a cancelled task means the host
listened or exited first.

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

* test(cmux-next): DiffSidecarProcessTests deadlines run on a ManualClock

The startup, request and reply-deadline tests used the real clock with 300 ms
to 1 s limits; on a host running 32 suite processes the marker-to-reply path
took longer than 1 s (theRequestDeadlineStartsAtTheReadyMarker) and the
suite hit its time limit. They now step a ManualClock after the deadlines
are armed (sleepers(atLeast:)), and the child's go signal is a file the test
creates. The cancel test's pid file appears whole (write, then rename): the
test could read the empty file that '>' creates before echo writes.

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

* test(cmux-next): LaunchMarkStaggerTests waits for the fired deadline's effect

A fired DemandTimer wakes on the global pool and hops to the main actor; 20
Task.yield calls did not cover that on a loaded host (isTitleShown false,
aws-m4pro-6 with 4 shards on one host). The test now waits (bounded) for
the mark and the status line with waitForCondition.

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

* fix(cmux-next): the cloud progress view reads its typing-note color inside performWithTheme

check-theme-scope.sh was red on the tip (CloudMachineProgressView.swift:107,
rule 1) since 85007b45672e.

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

* test(cmux-next): the live-daemon harness uses 30 s test deadlines

BranchDaemonHarness connected with the product's 2 s request and 5 s
terminal start deadlines. With 4 shards on one host, create-terminal missed
5 s in BranchDaemonTests, BranchTerminalLifetimeTests and QuitSessionsTests
of one shard at once. These suites do not test deadlines (DeadlineTests
does); per the TEST GLOBAL STATE rule they now use 30 s.

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

* test(cmux-next): HomeSourceRouterRecoveryTests waits for both owners online, not 300 ms

The test slept 300 ms for both owners' first events, then dropped and
restored the local owner. On a loaded host the drop landed before the
cloud's online, so the return was a merged connection change instead of
ownerRecovered and the test hit its 60 s limit. HomeSourceRouter exposes
ownerConnections (each owner's applied connection); the test waits until
both are online.

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

* fix(cmux-next): TabModel.resourceID is an observed stored property

Set from each snapshot in init and update, so an id that arrives with a
later snapshot of the same surface notifies observers (the store's waits,
views that find a tab by its public id).

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

* fix(cmux-next): an agent action's page wait wakes at the page install

AppBrowserPage.awaitPage waited in Observations on
TabContentCache.existingBrowser, but the page table is not observable: a
page installed after the first look was never seen and the action waited
out its whole bound (AgentActionWaitTests.anActionWaitsForThePageThatIsStill
Starting, 1 of 8 full fleet runs at 2b4a85dfcd52, 30 s). The wait now also
reads pageInstalls.revision, which every install bumps. Verified red-then-
green on the fleet with a throwaway test (steps bc8ef772b57b1516c6d0b1c5
red, 0f4e22e36764bd848eef2fcb green against the earlier registrar fix); no
unit test lands (NO UNIT TESTS rule).

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

* test(cmux-next): RecoveryNoticeTests and the DiffSidecar exit tests stop racing the clock

RecoveryNoticeTests waited 500 Task.yield calls for the debounced draft
write, which runs off the main actor; on a loaded host (aws-m4pro-7, shared
with PR runners) it ran out first. It now waits, bounded at 30 s, for the
file. aNonZeroExitIsAFailure timed out at the real 10 s request deadline on
the same shard; the exit and empty-reply tests now use a ManualClock like
the other non-deadline tests.

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

* cmux-tui test: scoped capture reassert tests use a quiet surface

Race: scoped_focus_gained_reasserts_host_mouse_capture_after_invisible_host_reset
and scoped_resize_reasserts_host_mouse_capture_after_invisible_host_reset ran a
live shell. The tests assumed the terminal was idle between their own
vt_write and the reassert. The shell's startup output is parsed on the PTY
reader thread while it holds the terminal lock, so try_pointer_semantics
reported Contended. reassert_scoped_host_terminal_state then kept the applied
state (left: Some(true), right: None), or desired_host_mouse_capture fell
back to false. Both are the designed contention behavior. The tests raced
the shell.

Fix: the surface writes nothing until it reads a line
(crate::test_wait::quiet_surface, the same fix as the sibling
desired_host_mouse_capture_follows_scoped_inner_terminal). No product change.

Red under load (Linux Testbox, cargo nextest run --workspace in parallel):
pty_mouse::scoped_* 3 of 60 runs failed at pty_mouse.rs:509, :543, :552.

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

* cmux-tui test: initial route fallback tests stop racing a 20 ms handshake budget

Race: initial_provider_timeout_falls_back_to_next_route and
initial_link_timeout_closes_group_and_falls_back_to_next_route gave every
route one 20 ms attempt budget and the whole selection 500 ms. The tests
assumed the budget expires only for the stalled route. The same budget also
covers the real Unix route's connect plus the client/daemon handshake. That
handshake runs CPU-bound crypto on the test's current-thread runtime. Under
full-suite load it can take more than 20 ms (the Unix route then fails) or
the whole run more than 500 ms (remote_runtime.rs:5574 and :5620).

Fix: the tests drive select_initial_route with the runtime attempt
(RuntimeInitialRouteAttempt) and one budget per route index. The stalled
route keeps 20 ms, so only the runtime's attempt timer moves selection on.
The Unix route gets a 30 s hang guard, so the handshake never races the short
timer. Same product code path, same assertions (one stalled call, one group
close, the Unix route selected). No product change. The tests move to
remote_runtime/tests/initial_route_budget.rs (test count unchanged, 2072);
remote_runtime.rs goes from 6677 to 6590 lines.

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

* cmux-tui tests: diagnostics for two unreproduced full-suite timeouts

deferred_input.rs:894 (right_button_capture_cannot_cross_a_new_pairing_dialog)
and remote_entry_tests.rs:88 (READ_HANG_GUARD) did not fail in 120 runs each
on a Linux Testbox: 60 under a parallel cargo nextest run --workspace, 60
under 64 CPU hogs, plus 6 concurrent full in-process runs of both test
binaries. Code reading found no lost-event or shared-state race: every
pending-mutation decrement without an event (supersede) happens on the
enqueuing thread before the wait, and each entry test has its own temp dir
and Mux.

So the waits now say why they failed:
- settle_pending_mutations (app/tests.rs) replaces the bare
  recv_timeout(...).unwrap() loops in deferred_input.rs. A timeout prints
  pending and pointer counts, elapsed time, and the settlements already
  handled, so a lost settlement can be told apart from a slow one.
- connect_as_link and response in remote_entry_tests.rs print the error
  kind, elapsed time, active connections, registered remote clients and
  whether the socket still exists.

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

* cmux-tui-core: move start_host_runtime to shared over a HostChild seam (cx-ko2e B17)

Move-only. start_host_runtime (the PTY reader, parser, child watcher and
exit publisher of a host) leaves unix/host_start.rs for
terminal_host_runtime/shared/host_start.rs. Its OS edges are sys seams:
- sys::HostChild: the Unix enum (Spawned | Adopted) stays in
  unix/host_start.rs with wait_for_child_exit_without_reaping (table C);
  its methods are pub(crate).
- sys::pty_poll_handle(&dyn MasterPty) -> PtyPollHandle: Unix is
  master.as_raw_fd() with the same context message.
- The drain waker pair is HostStream::pair() (std UnixStream::pair on
  Unix).
Windows stubs fail closed until sys/windows.rs. The re-export of the
host_refusal types in shared/unadoptable.rs gets
cfg_attr(not(unix), allow(unused_imports)): it warned on the Windows check
since d5137c4a1619.

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

* cmux-tui: split client keys socket test matches the wire error; poison-safe lock without a path

* refactor(cmux-next): palette row controls as their own type; RequestId out of resource.rs (god-file limits)

* cx-m0p7 adapters: review fixes (no partial complete lists from a busy vscdb, a failed Zed row or a scanning chat index; rank-only times; three.js folders)

* refactor(cmux-tui-core): lift the hosted reader loop out of spawn_hosted into HostedReader (move-only)

The 760-line reader-thread closure of Surface::spawn_hosted becomes the named
type surface/hosted_reader.rs: HostedReader holds the loop state, run() is the
connection loop, stream() the host stream loop, after_stream() the exit/loss
handling; hosted_reader/{output,resize,reconnect}.rs hold the transition
handlers and the reconnect retry loop. Labeled break/continue became Flow and
Option returns; the stream's frame demultiplexer, journal target and journal
reservation still live until the connection iteration ends. Clippy-only edits:
let-else to `?`, needless borrows.

surface.rs 1678 -> 942 lines (godfile baseline row removed); new files 279,
140, 144 and 280 lines. Production code only; no test moved or added.

* cmux-tui: client id lookup reads through the registry connection guard (tip changed the connection type)

* cmux-tui-core: move the standby/seeded launch to shared over a StandbyTerminalHost seam (cx-ko2e B18)

Move-only. launch_terminal_host_from, HostPresentation and
launch_terminal_host_seeded leave unix/standby.rs for
shared/attachment/launch.rs, so the daemon side launches a host the same
way on every platform. Edges are sys seams:
- sys::StandbyTerminalHost: the Unix struct stays in unix/standby.rs
  (spawn uses pre_exec setsid, table C); its fields are pub(crate) and the
  launch code uses stdin/stdout only through Write/Read (GPUI lane R2;
  the Windows stub carries io::PipeWriter / io::PipeReader).
- sys::endpoint_dir(file_owner(root)) replaces
  PathBuf::from("/tmp").join("cmux-th-<uid>") after fs::metadata(root).uid():
  the same path.
- sys::reserve_terminal_host_publication gains a Windows stub.
The sys::launch_terminal_host_from seam from B12 is removed (the function
is shared now). Re-exports keep names and cfg(unix) gating.

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

* S3: drop the unit tests and the reducer op (NO UNIT TESTS rule); verification moves to the tagged app via the debug socket

* cmux-tui: failing CLI behavior test for closed.delete (R102 S1b, red)

A real daemon, driven through the CLI: `closed <id> delete --members 0`
keeps the other member of a closed two-tab group, `closed <id> delete` removes a
group so `closed <id> reopen` answers resource.not_found, `closed clear
--since-ms <future>` deletes nothing, and `closed clear` empties the
history. A malformed --since-ms is a usage error.

The state CLI helpers move to tests/cli/state_helpers.rs so tests/cli.rs
stays under its size budget.

Re-applied from feat-cmux-next-reopen-closed (orphan audit lane A, bead
cx-gzh.7) as a behavior test in place of the in-process unit tests.

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

* cx-m0p7 store: socket-level behavior test on a real daemon replaces the in-process unit tests (NO UNIT TESTS)

* cx-m0p7 adapters: socket-level daemon test for the editor import replaces the unit tests (NO UNIT TESTS)

* Announce omnibox selection changes (#18904)

* fix(cmux-next): announce omnibox selection changes

* fix(cmux-next): expose selected omnibox rows on the AX list

* fix(cmux-next): keep the AX list type visible to tests

* fix: satisfy package namespace lint

* fix(history): needsOrigin stays on the main actor

The app build (Xcode 27) refused the nonisolated helper: it calls the
main-actor HistoryLocation.isSameSidebarItem. Fix-forward for 588d7fb6a7d5.

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

* cmux-tui-core: terminal_host_runtime launch.rs doc names the standby seam (B18 review nit)

Comment only.

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

* cmux-tui: closed.delete: delete a group or members, clear all or since (R102 S1b)

Delete Permanently and Clear Recently Closed. `closed.delete` takes
exactly one of `closed` (with optional member indexes) or `all` (with
optional `since_ms`). Deleted groups cannot be reopened and their terminal
archives go with them. A zero-member group of a deleted space keeps its
restore record. The request's idempotency key records the result, so a
retry replays it; reopen and delete serialize on one lock.

CLI: `cmux closed <id> delete [--members 0,2]` and `cmux closed clear
[--since-ms T]`. Page access needs closed:write. Spec, bindings
descriptors, app-host generated files and the CLI contract are updated.

RequestId moves to resource/request_id.rs so resource.rs stays under its
size budget.

Re-applied from feat-cmux-next-reopen-closed d2cde3b51efc (orphan audit
lane A, bead cx-gzh.7).

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

* cx-m0p7 store: the socket test picks a non-temporary folder

* cx-m0p7 adapters: the socket test picks a non-temporary folder

* cloud: declare the G8 approval codes on the gated Cloud ops, type them in cmux-cloud, allow the agent test team on development (cx-t2rz)

cloud.machine.create/resize/delete and cloud.snapshot.create/delete/restore
(CLOUD_APPROVAL_OPS, cx-wb5.65) declare approval.pending, approval.denied,
approval.expired and approval.too_many_pending, with the docs of the
install path. cmux-cloud declares the same codes (ops/declared.rs, checked
against the catalog by tests/manifest.rs) and maps them to
cmux.cloud.approval_pending/_denied/_expired/_too_many_pending with
details (the request id) kept, instead of protocol_error.

development only: CLOUD_ALLOWED_TEAMS adds team_6774f37212cb1152d2a9, the
personal team of the agent test account (lead decision); staging and
production are unchanged.

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

* cmux-next: the Mac app serves the credential relay with its install token; Cloud create and list through cmux/cloud behind CMUX_CLOUD_LINK=app (cx-wb5.63, cx-t2rz)

Credential relay (cx-wb5.63, slice B of cx-wb5.57): after each handshake
of the verified app connection (client-hello user_origin_allowed) the app
registers the provider family credential on its local daemon and answers
credential.relay with POST /v1/read (no key) or /v1/ops (key, origin)
using the install token only; a 401 mints once more; the Worker's answer
goes back unchanged. credential.session answers signed_in and team. A
non-verified connection never registers.

Cloud create and list through the Cloud app server (cx-t2rz), for builds
with CMUX_CLOUD_LINK=app; the default stays /api/vm until the first-party
app packages ship in the bundle. CloudMachineCreateFlow: a create no
person started shows the native confirmation first and sends nothing until
a click; one key, origin user; on approval.pending it shows the
confirmation if no click happened in this flow, answers that exact apr_
request as the person's session (feed.answer allow/once), then retries the
same key at most 8 times with a 1-8 s backoff through an injected clock.
The confirmation is user-only in debug.dialog (automation may only
dismiss it). Strings in the 21 Cloud languages.

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

* chore(cmux-next): sync the AppPlatform scopes copy after the merge

* Fix composer option selection semantics (#18910)

* test(webviews): delete the webviews unit tests (Lawrence 2026-10-10, "Delete all now")

Unit-test deletion lane, slice 1. Pure deletion: no product code change.
webviews tests before: 352 files, about 2,693 cases; after: 51 files, about
172 cases (23 of the files are the frozen settings page).

Deleted: 301 test files, about 2,521 cases (function, hook, component,
store and CSS-string tests under jsdom/happy-dom), and the fixtures and
helpers only they used (test/fixtures/markdown-roundtrip,
acpmux/conversation/fixtures, test/viewer-empty-dom.ts,
test/markdown-viewer-shell.harness.ts, popoverTriggerTesting.ts,
checkpoints/testFixture.ts). Mixed files keep only their boundary case.

Kept (not unit tests):
- real-browser lanes (requireBrowserLane): agent-pane-locale,
  composer-menu-width, desktop-feel, diff-virtualization, editor-csp,
  editor-page, files-panel-coverage, gallery-browse-layout,
  latency-harness, ui-a11y;
- palette eval replay (palette-eval);
- guards and ratchets over the tree: ui-rules, check-layers,
  check-type-scale, gallery-coverage, pane-english (scan only),
  diff-catalog-owner, desktopLayer (first-import scan only),
  cloud strings catalog check;
- generated-file and bundle checks: icons/iconPack (build_pack --check),
  protocol codegen (generated files current), app-theme (Swift vectors
  current), diff-worker-bundle, diff-label-bundle;
- shared contract vectors: protocol vectors, acpmux handoff schema pin,
  question fixtures (shared with the Swift port), gallery env vectors.
- webviews/src/pages/settings/ (FREEZE, settings slot): 23 files wait
  for a later slice.

Stale comments and docs that named deleted tests are updated; the
oxfmt ignore list drops the two deleted corpus folders.

* cloud: regenerate the catalog, the TS client and the Rust wire client for the approval codes (cx-t2rz)

Generated by bun run catalog:export, catalog:client and catalog:rust on a
Testbox; the app-host global is unchanged.

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

* cmux-next: one thin Base UI scrollbar (ui/ScrollArea) for the model picker's rail and list

Lawrence 2026-10-09: "i dont like the scrollbar can we use thinner one from baseui that looks very clean?"
(the rail showed the system scrollbar again; the earlier hide-classes were lost in a later landing).
ui/ScrollArea wraps Base UI ScrollArea: the viewport scrolls natively (wheel, trackpad, keys, scrollIntoView)
and hides the system scrollbar; a 6px rounded thumb with no track fades in on hover or while scrolling (no
fade with Reduce Motion), colored from the surrounding text (--ui-scroll-thumb overrides). Tailwind only, so the
pane and the pages compile the same rules. The rail and the model list use it.

test/preload.ts: jsdom lacks Element.getAnimations, which Base UI ScrollArea calls; the preload gives every
jsdom window the method (no running animations) and sets Base UI's BASE_UI_ANIMATIONS_DISABLED so popups
still close at once in jsdom. Full bun test: no new failures against the tip (784 pre-existing).

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

* S3: tagged-app proof harness (Cmd+D through debug.key, read-screen) and its results

* docs(cmux-next): crate-split lane 2 landing line (fdc55cb2fb50)

* cmux-next: the Cloud create retry loop is bounded by retries (check-concurrency) (cx-t2rz)

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

* feat(cmux-tui-core): debug-build lock-rank checker for the mux and surface locks

crate::lock_rank gives each lock of the documented order a rank: mux.state
(StateMutex) > pty.geometry > pty.term > pty.runtime > pty.taps > leaf
(pty.render, pty.title, pty.mouse_encoders, pty.last_attach_colors,
mux.default_colors). The surface locks become RankedMutex (same API as
std::sync::Mutex); a thread-local stack records held ranks, and a blocking
acquisition at a rank equal to or before a held one panics in debug and test
builds with both lock names and the acquisition site. try_lock is recorded,
not checked (it cannot deadlock). Release builds compile the rank, name and
stack out, so RankedMutex is a plain Mutex there.

The first run found one nested order the docs did not name: taps then
last_attach_colors, so attach taps get their own rank before leaf. It found
one wrong-order acquisition, in a test double: the clear-history probe locked
the terminal from inside the PTY write (runtime held); it now probes with
try_lock, which keeps its assertion.

Also: spawn_hosted moves `mux` into HostedReader and the debug-only disconnect
seam reads the surface's own mux (release clippy flagged a redundant clone).

* docs(cmux-tui-core): explain why a broken host stream keeps its journal reservation until the reconnect (cx-fr5l)

An active reservation keeps the capture epoch odd, so checkpoint capture
refuses the diverged mirror until the reconnect replaces it and records the
host_reconnect gap; releasing it at the break would publish an even epoch over
unjournaled output. An inactive reservation delays nothing (shutdown waits for
the reader first, then revokes it at once). No behavior change, so no test.

* S3: the provisional split maps columns and rows instead of indexing them (crash ratchet)

* cx-m0p7 store: keep LocalOperation in resource.rs (boundary check reads it); move encode_hex out for the god-file limit

* test(cmux-next): delete the CmuxNext unit test suites, pass 2 (Lawrence 2026-10-10, "Delete all now")

Unit-test deletion lane, slice 2. Pass 1 (dcc8cad9829b) kept 1,306 suites
under the old rule. This pass applies the chief's ruling: delete every
in-process suite (calls on internal types, in-process AppKit/WebKit/CEF,
fake daemons, red/crash/property suites); keep real-boundary suites,
guards/generators and benches.

CmuxNext tests before: 1,375 Swift files, 6,535 @Test; after: 63 files,
179 @Test. 1,312 files and 1,305 suites deleted, plus fixtures only they
read; 39 of 50 test targets are empty and leave Package.swift and
ci-target-graph.json (PKG slot f31e0a2ac07f).

Kept:
- real daemon or binary over the wire: the .liveDaemon suites
  (Branch*, BatchClose, DaemonVersionHandoff, Integration, QuitSessions,
  BranchStateResources), Mobile LiveDaemon + PhoneClientOverDaemonLane,
  FirstLaunch (same-tree cmux-tui), RemoteLocalhostLive, ChildSignalDefaults
  (posix_spawn children, named by check-crash-safety.sh);
- the control socket (chief ruling): all of CmuxNextControlTests;
- guards and generators run by scripts: ActionSurfaceParity,
  ActionContract, ActionCatalog, CLISurfaceParity, DaemonCapabilityExport,
  LinkExport, ManagedPreferencesManifest, SettingsSchemaExport, CEFShimABI;
- benches: AgentPaneTransportBench(+Parse), CmuxDialogLatencyBench,
  PageHostPoolClaimReadyBenchmark, PaletteBenchmark, OmniboxPhaseABenchmark.
Their helpers that lived in deleted suites move to helper files
(OmniboxFixtures.swift, RecordingPageProvider.swift); FirstLaunchTests
binds AppServices itself instead of calling ActionBindingCoverageTests.

The sidebar-layout-cases.json fixture stays: cmux-tui-core reads it.

CI: cmux-next.yml swift-test drops the WebKit driver and attach stress
steps (their suites are gone) and the deleted suites from the skip and
filter lists. scripts/measure/hotkey-agent-feed.sh, hotkey-resize.sh and
modifier-hints.sh only ran deleted unit suites and go too.
tests/test_cmux_next_route.py follows the smaller target graph; a test
folder with no Swift sources (a fixture another language reads) is not a
target.

* S3: SpawnOptions drops unserved fields itself (DaemonConnection stays within its size limit)

* ci(cmux-tui artifacts): cmux_next_ daemon tests build only the members that hold them

The macOS daemon test job spent 11m31s of its 14 min compiling: `cargo test
--workspace cmux_next_` builds every member's test binaries (acpmux alone has
about 45) to run tests from four members. The filter matches the test path, so
a cmux_next_ test can only live in a member whose sources name cmux_next_;
scripts/ci/cmux-next-daemon-test-packages.sh selects those (today cmux-motion,
cmux-tui, cmux-tui-core, cmux-tui-platform) and falls back to --workspace.
publish-tree and nightly-next promotion wait on this job.

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

* fix(cmux-next): WebKit right and middle buttons: right presses go in as AppKit right-button events, a middle press carries button number 2 on its own Quartz form (DOM button 1), wheel events take the Quartz location AppKit gives the window point, and the web view empties the context menu of a right-click the app did not dispatch, so an agent's right-click opens no native menu (cx-ollo)

* cmux-next: harden the credential relay and the Cloud create approval (cx-t2rz security review)

P2-1: the relay serves only the app cmux/cloud and only cloud.* ops
(credential.app_refused / credential.op_refused otherwise): an install
token with origin user counts as the person on the Worker, so no other app
or op family may borrow it.
P2-2: before feed.answer the app reads integration.approval.get as the
person and requires op cloud.machine.create, state pending and exactly the
params the person confirmed; the feed item must carry the same request
and digest, tool cloud.machine.create, and poster system:cloud:<team>.
Any mismatch answers nothing and shows why. The confirmation shows the
machine size and name.
P3-1: the relay registers only on the app server link path (never with
the legacy default or in release).
P3-2: a create from the progress view's Retry (no gesture origin) asks
the person first; only an action with origin user skips the sheet.
P3-3: the session call for the approval refuses redirects and keeps no
cookies or cache.

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

* fix(cmux-next): short update titles; the test feed and announcements are notice cards

Chief 2026-10-09 (round 2 of Lawrence's update card ask).

- Update titles are short and the version moves to the detail line:
  "Update Available" / "Version 1.0.0-nightly.…", "Update Check Failed",
  "Needs macOS 27.0 for …". Before, "cmux 1.0.0-nightly.376…" and
  "Couldn't Check for Up…" truncated in a 260 pt sidebar. UpdateNote's
  needsNewerMacOS carries the found version. 4 new strings (en + ja,
  19 languages needs_review).
- The test-feed notice (Use Real Feed, no x) and the announcements (their
  allow-listed Try It, an x that dismisses for good, the next one shows)
  are the shared notice card. Order: update status, test feed,
  announcement, tip; the "cmux Updated!" card waits while an update
  status or a test feed shows.
- The plain R114 card stack is gone (SidebarCard, SidebarCardView,
  SidebarCardStackView, SidebarCardStackLayout, SidebarModel.cards and
  onCardAction); its hover test goes with it. The footerCards slot stays.
  Announcements now show whether or not the sidebar chrome is revealed.
- NO UNIT TESTS (Lawrence 2026-10-10): the unit tests this change breaks are
  deleted in this diff (UpdateNoticeTests and its ManualClock copy, from
  31d888016eed / d344241b7a88; the stack hover test; two stack-card
  asserts). Proof is the tagged app on cmux-lawrence-2 through the debug
  socket with screenshots.

Bead: cx-t34p

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

* fix(cmux-next): a check while an update waits shows the waiting update

Lawrence's NIGHTLY (2026-10-09/10): 3797671859401 was downloaded and
waited for a restart; Sparkle skips every check while an update waits
("update check ignored while install is progressing"), so his manual
Check for Updates showed nothing and never saw 3800566557701.

- Check for Updates while an update is staged does not ask Sparkle (it
  would ignore the check); it logs and answers with the staged update.
- The staged update card ("cmux X is ready", Restart to Update) shows
  after such a check even under updates.notify silent, which otherwise
  hides it. Before, a silent install had no answer to a check at all.

Proof: tagged app on cmux-lawrence-2 through the debug socket (staged
update + notify silent: no card; Check for Updates: the card), with
screenshots. No unit tests (Lawrence 2026-10-10).

Bead: cx-t34p

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

* ci(nightly-next): keep the acpmux and OptChat Chief dependency builds between nightlies (cx-g34i)

build-nightly-app compiled every Rust dependency of acpmux (53 s + 46 s, two
arches) and the OptChat Chief (24 s + 24 s) from scratch on each nightly-next
run, on the serial path before the Release build. Swatinem rust-cache on their
target dirs (cmux-tui/target/acpmux-app, optchat-chief/target/app), nightly-next
track only; cargo rebuilds what changed.

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

* test(cmux-tui): drop the hand-written operation and command counts (cx-034r)

Unit-test deletion lane, slice 3 (chief order): every lane that added an
op had to edit 4+ files with a hand-written count (palette hit 211, 212,
213). The generated-from-spec checks (spec inventory, SDK sync, the set
equality of generated commands and the IR) already prove coverage.

Removed, no other assertion changed:
- cmux-tui-core resource_router tests: operations.len() == 213 (2x);
- cmux-tui transport path coverage: cases.len() == 188, catalog 213;
- check-resource-api-boundary test: operations 213;
- conformance runner: TRANSPORTED_OPERATION_COUNT (now: a non-empty
  catalog), and its test;
- SDK raw coverage tests: command count 236 and event count 69 in C++,
  Go, Java, Python, TypeScript, Zig (src and the watch example);
- spec/resource-operations-v2.md names the JSON catalog, not a number.

Pure assertion deletion: no red test first. 0 tests deleted, 0 added
(test count before == after).

* cmux-next: S3 optimistic Cmd+D off by default (cx-wb5.72)

Keys typed right after Cmd+D lost the tail of the line in 1 of 8 proof
runs. Every daemon now serves split-client-keys-v1, so the capability gate
protects no one. PaneSplitCommand.optimisticSplitEnabled = false sends every
split through the old path; the landing with the ordering fix sets it back.
split-optimistic-proof.py records the path each Cmd+D took and takes an
expected path (optimistic|old) as a gate argument.

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

* test(scheduler): failing tests for automations, runs and bodies in rows (g1)

Head under 100 KB with 100 automations of 20,000-character instructions
and 450 kept runs; bodies stored once with reference counts; keyset
paging for automation.list and run.list stable under inserts; run.get
joins the body; migration of an old JSON head is idempotent on a live
DO and after a rollback refill; disable, delete and a policy deny
cancel every queued run. Deterministic: reads answer without an ok
field, the cancelled-run check uses a sleeping first run, the migration
check binds the object before it reads the head.

Re-applied from orphaned fe31fa1f0ed8 + 467e890d7cb1 (cx-3bi.38).

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

* feat(scheduler): automations, runs and bodies in rows, keyset paging, live migration (g1)

SchedulerDO moves to row mode (plans/cmux-next/backend.md (g), DO audit
F-1). automation/<id> and run/<id> rows hold the records without their
bodies; body/<sha256> rows hold each distinct body once with a reference
count (automations plus kept runs) adjusted in the same commit; a
finished/<run> index lets prune drop the oldest finished runs past 200
without reading the open ones. A started run keeps its body hash. The
head keeps owner, settings, counters, chains and the counts
automation_count, open_runs, finished_count, row_seq. All four tables
are private, so events and snapshots carry the head only.

One lookup module (domains/scheduler-rows.ts) and one per-op store
(domains/scheduler-store.ts) replace every read of state.automations and
state.runs. New reads run.list and run.get; automation.list pages with
cursor and limit and its first page is unchanged. System op
scheduler.rows_migrate (key carries the head seq) rebuilds the rows from
an old head's maps on bind and drops the maps in one commit; the alarm
path migrates first too.

Re-applied from orphaned 7ab757b458e5 onto feat-cmux-next (cx-3bi.38).
run-policy.test.ts keeps the tip's createWithTeamDown helper (cx-nfts)
and reads next_at from the automation row. Catalog, TS client, Rust
cloud-wire client, app-host ops/scopes/d.ts and the bundled AppPlatform
scopes.json are regenerated with the repo generators, not hand-merged.

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

* test(scheduler): failing tests for the g1 review findings

A run pruned while the dispatch loop awaits a create is skipped and
onWake goes on; a refilled head migrates by merge (maps win per id, rows
the maps lack stay with their bodies); reads on a legacy head answer the
retryable owner.migrating; run.list with a sparse filter over more than
200 kept runs scans every step and ends without an empty last page.

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

* fix(scheduler): g1 review: stale dispatch rows, merge migration, legacy reads, paging

- onWake re-reads each run before it builds the dispatch params: a
  disable, delete or deny during an earlier create may have cancelled the
  run and prune may have dropped it with its body, which made requireBody
  throw and stop the wake. A missing, started or terminal run is skipped.
- scheduler.rows_migrate merges a refilled head instead of replacing the
  rows: the maps win per id, rows the maps lack stay with their stored
  bodies (dropped only when that body row is gone), and every body
  reference count is recounted.
- Reads on a legacy head answer the retryable owner.migrating, the same
  as the reducer.
- run.list looks one match ahead, so a page that ends the list has no
  cursor to an empty page.
- automation.list keeps automation_count and next_cursor optional while
  an old Worker can answer (deploy skew); catalog, TS client, Rust
  cloud-wire client and app-host outputs regenerated.
- plans/cmux-next/backend.md (g): roll-forward only, merge semantics,
  and the operator recovery when a pre-g1 build meets a migrated head.

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

* test(scheduler): drop the engine-level g1 unit tests (NO UNIT TESTS rule); the SchedulerDO op tests stay as the behavior proof

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

* scheduler: regenerate the cloud-wire Rust client and the bundled AppPlatform scopes.json after the rebase

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

* cmux-next: a Cloud create approval must be for the create's own team; the mismatch text stays localized (cx-t2rz review)

The approve step takes the backend team the relay's create bills (the
install token's team claim, captured when the create starts) and requires
the feed item's approval.team to equal it, so an identical create pending
in another team the person belongs to is never approved. The mismatch
reason goes to the log only; the person sees the localized text.

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

* scheduler (cx-3bi.38): regenerate the cloud-wire Rust client on the tip

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

* test(rd-host): bench sends synthetic upstream frames; upstream loopback check (red: no host sink) (cx-9zn)

cmux-rd bench --upstream mic|camera|screen opens an upstream stream, sends
synthetic frames through cmux-rd-core's UpstreamSender with the host's
feedback, and reports frames sent/acked and an FNV-1a hash.
scripts/upstream-loopback.sh expects a host with --upstream-record DIR to
record every sent frame; red until the host has the recording sink.

* feat(rd-host): --upstream-record DIR development sink for viewer upstream media (cx-9zn)

The desktop host offers up_media only with a sink; it had none (NoSink),
so no viewer could send its microphone, camera or screen anywhere. The new
RecordSink (development only, off by default) writes each upstream stream
to an owner-only file (Annex-B .h264, length-prefixed .opus-packets) and
logs frames, bytes and an FNV-1a hash on close. The sink is chosen per
session; without the flag the host still offers no upstream caps.

* fix(cmux-next): the diff sidecar keeps its pipe readers until end of file

DiffSidecarProcess released the Process at the child's exit, and with it
the Pipes and their read FileHandles. When the exit was handled before the
stdout end-of-file event, the FileHandle closed its descriptor and the EOF
never came: the reply never finished and the caller waited for the request
deadline (130 s; a 60 s hang of writesTheRequestAfterTheReadyMarker... on
the ManualClock, 7 times on 2026-10-09). The invocation now holds both read
ends until each reads EOF, and releases them when the launch fails.

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

* fix(cmux-next): a cloud edit marks the source degraded before it subscribes

requireEditable started the conversation's subscribe and only then set
degraded. A subscribe that went live in between called recover() on a
source that was not degraded yet, so no .ownerRecovered went out and the
edit waited for a recovery nothing would send (CloudHomeSourceTests
withoutAnEchoTheEditSubscriptionEndsAtTheDeadline, 60 s, once under load).
The flag is now set in the same lock as the target read.

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

* test(cmux-next): live-daemon suites wait for the daemon's events, not deadlines

The harness connection (and QuitSessionsTests' relaunch connections) send
no client deadline: each request waits for the daemon's reply, so
create-terminal returns when its host is up. The 30 s bound of 57a5a8129382
only moved the line a loaded host crossed at 5 s. TerminalHosts.awaitExit
waits for kernel exit events (one process source per pid) instead of a
50 ms kill(0) poll; its 30 s bound and the suites' .timeLimit are only hang
guards.

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

* test(cmux-next): the palette benchmark measures the thread's CPU time

The budget read wall time, which counts time the thread waited for a core:
with 32 suite processes on one host the ranking median read 123 ms (54.8 ms
in another run) against 50 ms. The ranking runs synchronously on the calling
thread, so its CPU time is the search cost.

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

* fix(rd-host): review fixes for the upstream record sink (cx-9zn)

Review subagent should-fix items: the record dir must be a real 0700 dir
owned by this user; a per-session byte limit (--upstream-record-max-mb,
default 1024) and 64-file limit; a sink that cannot start falls back to
NoSink instead of dropping every viewer; unique file names; Drop logs
open streams. Bench uses the welcome's max_datagram. upstream-loopback.sh
counts host lines per run (a failed run no longer shifts later checks),
accepts paced drops, and takes PORT/PORT_NOSINK.

* test(cmux-tui): delete the unit tests of 18 leaf crates (cx-034r)

Unit-test deletion lane, slice 4a (Lawrence 2026-10-10, "Delete all now").
In-src #[cfg(test)] tests and the tests/ files that call library code are
deleted in cmux-motion, cmux-history, cmux-conversation, cmux-chat-index,
cmux-terminal-iface, cmux-terminal-sizing, cmux-tui-cloud-conversations,
cmux-theme-tokens, cmux-agent-pane-policy, cmux-tasks-core, cmux-tasks,
cmux-app-manifest, cmux-chief, cmux-terminal-client, cmux-mobile-core,
cmux-mobile-ffi, cmux-unix-socket and ghostty-vt.

Kept (boundary or cross-language contract):
- ghostty-vt tests/ (bytes in, terminal state out; chief ruling);
- cmux-terminal-client tests/route_e2e.rs and cmux-tasks tests/server.rs
  (real sockets end to end);
- shared corpora and fixtures another language replays:
  cmux-conversation conformance, cmux-chief corpus, cmux-agent-pane-policy
  parity + typescript, cmux-history fixtures, cmux-theme-tokens load,
  cmux-mobile-core terminal sizing fixtures, cmux-app-manifest schema
  fixtures + samples, cmux-tasks-core catalog (catalog <-> Op guard).
cfg(any(test, feature = "test-support")) narrows to the feature.
cmux-tui.yml: the Windows step drops cmux-chat-index, which has no tests.

Deferred to slice 4b (security and malformed-input cases need a boundary
test first): cmux-local-auth, cmux-relay, cmux-tui-machine-protocol,
cmux-tui-machine-agent-protocol, cmux-transport, cmux-encode, cmux-wg,
cmux-link.

Counts: 327 in-src tests and 189 tests/ tests deleted (516); 55 files deleted.

* cmux-tui: two terminal_host_recovery load flakes made deterministic (cx-2dc4)

pipelined_new_tabs_start_hosts_in_parallel_in_request_order asserted a
wall-clock bound (8 hosts x 400 ms ready delay < 3.2 s), which IO and CPU
load broke (40/40 red on a loaded Testbox). A debug-only host seam,
CMUX_TUI_TEST_HOST_READY_GATE, now holds every host before Ready at a FIFO
the test opens later; all records on disk while the gate is closed prove
the launches overlap, and identify must reply while the creates wait.

host_crash_is_named_with_its_panic_message: the abort seam fired 300 ms
after install, racing the owner's PTY custody (no custody, no replacement,
no host_replaced line), and systemd-coredump of the aborted 1 GB debug host
kept it alive 3-6 s under load. The seam now crashes when the test opens
<marker>.trigger, after wait_for_daemon_custody saw the daemon hold the
master and its custody thread (renamed pty-custody, visible in /proc) end,
and it disables core dumps before abort.

Loaded Testbox runs (32 vCPU; P=8+32 hogs, P=24+96 hogs+8 fsync dd,
P=16+32 hogs+workspace build): before 40/40 and 3/40 red; after 0/120 each.

* cx-m0p7 adapters: daemon-capabilities.json lists project-list-v1

* test(cmux-tui): server-stats write_path counters and the effect intent socket check (red)

Add the optional server-stats section write_path (include-gated, like
resource_projection, so older SDK decoders stay safe): effect receipt
commits carried by journal writer batches, their savepoint failures,
the batches that carried them, effect receipt commits that ran their own
transaction on a request thread, and registry lock acquisitions on a
journal writer thread (counted in release builds, process-wide).

The new socket test drives a headless daemon: terminal creates under
terminal output must commit their effect receipts as writer intents,
with no request-side effect commit and no writer registry lock. Red
today: effect_store still commits each receipt in its own request-side
transaction and fsync (effect_intents stays 0).

Spec: commands.md and sdk-schema.json gain the section; bindings
regenerated.

Bead: cx-g1fa.1

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

* fix(cmux-tui): effect receipt commits ride the journal writer batch

effect_store commit_resource_effect and commit_resource_effect_patch
become typed writer intents (journal ingress Effect events on the
durable lane). The request thread prepares the intent under the
workspace registry (validation, stored fingerprint, public-fold pruning),
the journal writer applies it inside its batch transaction under a
SAVEPOINT per intent (one shared fsync per batch), and the request
thread finishes it (projection stats, public fold) under the same
registry hold. An intent that fails its checks rolls back to its
savepoint and only its request gets the error; a SQLite failure still
fails the batch, which the writer retries or splits as before.

Lock order is unchanged: workspace registry -> registry connection ->
state. commit_resource_effect_projection drops state and the connection
pin before the receipt wait, keeps the registry (topology writers stay
serialized), and re-locks state to install the revision. The receipt
wait uses the producer deadline and commit fence: a writer error, a
dropped receipt or an expired deadline returns an error and releases
the registry. Prepare and executing transitions stay request-side.

The writer marks its whole loop iteration (batch, commit, retries,
receipt delivery) as the journal writer, so a registry lock there is
asserted in debug and counted in release (server-stats
write_path.writer_registry_locks, checked over the socket). Local commit
fallback (counted as request_effect_commits): writer disabled, stopped,
failed, not running yet, or the calling thread still holds the
connection.

The registry lock, the journal writer iteration and the registry
connection take lock ranks in the lock-rank checker (crate::lock_rank:
workspace.registry > journal.writer > registry.connection > mux.state),
so debug and test builds panic on a registry lock on the writer thread
on every pa…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant