Skip to content

Remote tmux mirrors: exact feed-forward sizing, verified pane geometry, faithful live pane headers, active-pane indicator, and drag-stable rendering - #7315

Merged
austinywang merged 29 commits into
manaflow-ai:mainfrom
ejc3:remote-tmux-feed-forward
Jul 9, 2026

Conversation

@ejc3

@ejc3 ejc3 commented Jul 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Multi-pane mirrored tmux windows can render panes a column narrower than the width tmux assigned them. Repro on main: mirror a session with a 3-pane split (cmux ssh-tmux <host>), run a program that paints full-width lines, and resize the cmux window through a few widths — at many widths one pane's full-width lines wrap by one character, and some resizes leave a pane permanently mismatched until the next resize.

Root cause: the client size reported to tmux is derived by dividing the mirror's outer pixel area by the cell size, which counts local divider and padding pixels as terminal columns, so tmux lays out more columns than the panes have pixels. Nothing then constrains a pane's rendered grid to the width tmux assigned, so where the extra columns land is layout-dependent.

Symptom of the bug

This shows how at certain window sizes, tmux believes a that the pane is one column bigger than it actually is. That results in the incorrect wrap you see on the left side pane. The pull request's purpose is to fix that and make the whole thing simpler and testable.

old_master_broken_trimmed.mov

The same defect frozen in a frame — main rendering a mirrored session with the left pane's full-width lines wrapping by one column:

broken_master

Demo Video

A tour over an eight-shape layout zoo (every pane running a width probe that paints a full-width ruler, a bottom-row sentinel, and a live PTY-vs-tmux size check):

  1. Every layout shape, first with tmux headers off — hairline dividers and the active-pane dot, nothing else, matching what a stock tmux displays — then with pane-border-status top, where each strip carries tmux's own header text.
  2. Live titles: panes are retitled programmatically and the headers update the moment tmux's own border would redraw.
  3. pane-border-status toggled off and back on mid-session — panes reclaim and yield the title rows, re-settling exactly each time.
cmux-ultimate-demo-trimmed.mov

Design

One authority per quantity, feed-forward in both directions:

  • Window pixels own the client size. The pushed size is a pure function of the container's device pixels, the layout tree's structure, and measured render constants (cell size and surface padding, sampled from live surfaces per backing scale). It never reads tmux-assigned geometry or rendered grids, so tmux's %layout-change echo of our own push recomputes to the identical value and dedups to silence — there is no feedback loop to manage.
  • tmux owns pane division. The render imposes tmux's assigned cells verbatim as integer device-pixel edge rails: exact on each split's axis (with +1 device pixel into the divider gap, so downstream point-conversion rounding can never shave a column off a pane sitting exactly on a cell boundary), filling the cross axis. Pane ratios are user state and are never written.
  • Sizes are per window (refresh-client -C '@id:WxH'), deduped per window, reseeded after reconnect, with a session-wide fallback for servers that reject the @id: form. Hidden tabs claim their size once at attach (the first per-window pin drops unclaimed windows to tmux's 80×24 default) and re-own it when selected. Zoom renders the visible tree without touching the pushed size or panel lifecycle.

Alternative considered: reconciling the render after the fact — measure what each surface actually renders and bring tmux to it (report the summed rendered grid as the client size, and resize-pane whenever tmux assigns a column a pane's pixels can't render). That direction loses on two grounds. (1) It creates a cycle with two independent rounding schemes inside it — the view's pixel division and tmux's integer cell division; at some pixel widths the two have no common fixed point, so any policy that re-reads renders after a reflow either oscillates by a column (SIGWINCH-storming every pane) or must be rate-limited into eventual silence at a wrong answer. (2) Per-pane corrections write tmux's layout ratios, which are user state shared with every client of the session; grids read mid-resize feed transient geometry back as permanent ratio changes. Sizing from pixels + structure only, and rendering tmux's layout verbatim, removes the cycle instead of managing it.

How the system works

Data flow on main (before)

One loop, three writers, and measurements feeding back into inputs:

              NSWindow resize
                    │ outer pixels
                    ▼
        cols,rows = outer px ÷ cell px          ← counts divider/padding
                    │                             pixels as terminal columns
                    ▼
            refresh-client -C WxH ────────────► tmux
                    ▲                             │
                    │                             │ %layout-change
      rendered grids read back                    │ (layout STRING published
      and re-fed into sizing                      ▼  to the render as-is)
                    │              SwiftUI splits panes proportionally;
                    └───────────── each pane renders whatever width the
                                   proportional division lands on

Three problems live in that picture. The pushed size counts non-terminal
pixels, so tmux lays out more columns than the panes can render. The render
divides space proportionally instead of using tmux's assignment, so where the
lost column lands is layout-dependent. And rendered grids feed back into
sizing, so the system can oscillate: push → tmux re-lays-out → render moves →
push again.

Data flow now

Two one-way paths that never read each other's outputs, plus a quarantine
that keeps unverified geometry away from the render:

 SIZING (window pixels are the only authority for the client size)

   NSWindow resize
        │ container device pixels
        ▼
   f(pixels, tree STRUCTURE, measured cell/pad constants)
        │            ▲ constants sampled once per backing scale
        ▼              from live surfaces — never per frame
   refresh-client -C '@id:WxH'  ───────────────────────────► tmux


 GEOMETRY (tmux is the only authority for pane division)

   tmux %layout-change / list-windows
        │ layout string (STRUCTURE is truth; its pane
        │ rects are NOT — see the header-mode trap below)
        ▼
   pendingLayouts quarantine ── one generation-tagged
        │                        list-panes fetch per window
        ▼
   real pane rects (pane_left/top/width/height + title + active flag)
        │ patch leaf rects onto the parsed tree
        ▼
   windowsByID published + observers notified   ← the ONLY place window
        │                                          geometry becomes visible
        ▼
   render imposes tmux's rects verbatim as integer device-pixel rails

Who drives what:

  • The cmux window's pixels drive tmux's client size. Nothing else writes it —
    not tmux events, not rendered grids, not reconcile.
  • tmux drives pane division, pane titles, and the active pane. The render
    draws exactly the rectangles tmux reports; pane ratios are user state and
    are never written back.
  • A %layout-change echo of our own push recomputes to the identical size and
    dedups to silence. No feedback loop exists to manage.

A window resize, end to end: pixels change → f recomputes cols×rows from
pixels + structure → one deduped refresh-client -C '@id:WxH' per window →
tmux re-divides its panes → %layout-change arrives, is parsed and
quarantined → one list-panes fetch returns the real rects → the patched
tree is published atomically and the render moves once, to verified geometry.
If layouts arrive faster than fetches return, newer layouts coalesce onto the
pending entry (generation-tagged; stale replies are discarded) and observers
keep the last verified tree until the next verified one — the render never
shows an intermediate guess, so there is nothing to bounce.

Geometry: the two header modes

tmux may or may not draw a title row above each pane (pane-border-status).
Both modes must render faithfully and settle, so the vertical chrome is
tmux's own rows plus at most one synthetic band:

Mode 1 — tmux gives us the header (pane-border-status top is set).
tmux carves a real title row above every pane, inside the window's own cell
budget. cmux adds nothing: every pane sits at y ≥ 1, so no synthetic band is
reserved and the full row budget is pushed. The strips render tmux's title
rows as a hairline carrying each pane's index "title" label and the
active-pane dot:

   ●─0 "left"──────────┬──1 "right"─────────  ← tmux's title rows (real cells,
   │ pane %0           │ pane %1                y=0), drawn as hairline strips
   │                   │                        with the labels + active dot
   ├──2 "build"────────┴────────────────────  ← tmux's title row for pane %2
   │ pane %2
   └────────────────────────────────────────

The trap in this mode: tmux's layout STRING still reports the pre-title tree
(a pane claimed at y=0 with 62 rows actually displays at y=1 with 61 rows).
That is why placement never trusts the string's rects and waits for
list-panes — placing panes off the string is visibly one row wrong, and
rendering the string first and correcting after made panes bob. The
quarantine makes that impossible by construction rather than by timing.

Mode 2 — no tmux headers (default; pane-border-status off).
tmux's window is just panes plus separator rows — no title rows, and a stock
tmux displays no titles anywhere. cmux matches that: strips are bare
hairlines, and the ONE cell-high band it reserves across the top (subtracting
exactly one row from the pushed budget) exists so the active-pane dot always
has a home that can never overlap pane text:

   ●─────────────────┬───────────────  ← synthetic band, 1 cell high,
   │ pane %0           │ pane %1                cmux-only (subtracted from the
   │                   │                        pushed rows so tmux and the
   ├─────────────────┴────────────────────    render agree on the budget)
   │ pane %2                                  ← tmux separator row: bare
   └────────────────────────────────────────    hairline, no text

The band is uniform across every branch of the split tree, so each branch
loses the same single row and the bottoms of adjacent columns stay aligned
regardless of how many panes stack in each — the row budget is a function of
the tree's structure only, not its depth (test-pinned).

In both modes the strips are the only chrome: nothing ever draws over pane
text and the dividers are one device pixel like tmux's own borders. Header
text is tmux's, verbatim: each label is the pane's EXPANDED
pane-border-format (custom formats included), seeded by the same
list-panes fetch that publishes the geometry and kept live by a per-pane
control-mode subscription — a program retitling its pane updates the strip
at the same moment a native tmux client's border would redraw. When headers
are off, no text renders at all, because that is what tmux shows. During a
resize the transient render reserves the same strip rows with the last-known
labels pinned, so the chrome never blinks while panes re-divide.

Testing

The defect class here lives in the interaction between stages (pixels → pushed size → tmux's assignment → imposed frames → rendered grid), so the tests are layered: pure property tests on the math, contract tests on the state machine around it, and an end-to-end suite that drives the whole loop against a real tmux server and asserts on the app's own introspection — never on screenshots.

Unit and property suites (cmux-unit scheme):

  • RemoteTmuxMirrorGeometryTests — property tests over randomized layout trees. The client-size function is invariant under re-assigning different sizes onto the same structure (it may depend on structure only); computed frames sit on integer device-pixel rails with each pane's split-axis extent in [needed, needed+1] px (the +1 boundary bias); the chrome fold matches per-shape expectations; minimum floors hold.
  • RemoteTmuxMirrorFeedForwardTests — the mirror's sizing contract: the push is a pure function of container pixels + structure (re-applying tmux layouts never changes it); hidden mirrors write exactly one initial claim; reconcile never pushes (tmux events are not push triggers — the invariant that keeps the system echo-silent); frames are imposed only when tmux's layout matches the computed size for the current pixels, otherwise the render falls back to the proportional transient; zoom changes neither panel lifecycle nor the pushed size.
  • RemoteTmuxConnectionWindowSizingTests — per-window dedup on the connection, the %error → session-wide fallback for servers without the @id:WxH form, and the reconnect reseed table.
xcodebuild test -scheme cmux-unit -destination 'platform=macOS' \
  -only-testing:cmuxTests/RemoteTmuxMirrorGeometryTests \
  -only-testing:cmuxTests/RemoteTmuxMirrorFeedForwardTests \
  -only-testing:cmuxTests/RemoteTmuxConnectionWindowSizingTests

End-to-end suite (cmuxUITests/RemoteTmuxSizingUITests) — a real tmux server, the real app, the real ssh transport, hermetically: the app builds its own throwaway tmux on an isolated TMUX_TMPDIR through a DEBUG-only socket verb (the XCUITest runner is sandboxed and cannot touch /tmp itself), and ssh is replaced by the checked-in scripts/remote-tmux-e2e-ssh-shim.sh via CMUX_REMOTE_TMUX_SSH_FOR_TESTING — which reproduces the three ssh behaviors the transport depends on (the remote shell re-splits the quoted command, a pty exists only under -t/-tt, and stderr stays separate so probe-failure classification works). Window sizes and tab selection are driven over the control socket (NSWindow.setFrame with read-back, surface.focus) rather than AX mouse gestures, so the suite is deterministic on any desktop, including headless CI.

xcodebuild test -scheme cmux -destination 'platform=macOS' \
  -only-testing:cmuxUITests/RemoteTmuxSizingUITests

Scenarios: attach settles stable and coherent; a shape sweep (even/nested/rows/grid/deep/six-column/main-horizontal) must render every pane per the contract — exact on the immediate split's axis, ≥ assigned on the fill axis — at every window size; a window-size sweep re-converges at each width with pane ratios preserved; a co-attached client's resize-pane heals. The oracle reads the remote.tmux.pane_grids debug verb (per-pane assigned vs rendered grids plus the sizing inputs), on top of tmux-side stability and coherence checks. The suite also guards its own blind spots: every resize asserts the read-back window frame matched the request, the width sweep asserts pushed columns strictly grow with the window, and a settle check fails if the selected window has no mirror entry — so a regression that stops mirrors from existing cannot pass on tmux-side checks alone.

bash scripts/remote-tmux-e2e-ssh-shim-check.sh exercises the shim against every ssh invocation shape the transport makes (ControlMaster ops, one-shot probes with stderr classification, the -tt control stream with a live stdin dialogue, SIGTERM cleanup) in seconds.

On CI, dispatch test-e2e.yml with test_filter=RemoteTmuxSizingUITests (records video; each lab pane in the first window runs scripts/remote-tmux-width-probe.sh, which paints a PTY-wide ruler, a bottom-row sentinel, and a two-axis size check — a human-readable narration of the machine oracle).

Manual verification: scripts/remote-tmux-shape-zoo.sh <ssh-host> builds the same shape zoo on a real remote server's tmux over a single interactive ssh connection (probes running in every pane), ready to mirror with cmux ssh-tmux <ssh-host>. Or run scripts/remote-tmux-width-probe.sh inside any mirrored pane: a wrapped ruler (surface narrower than the PTY), a clipped bottom sentinel, or a ✗ at rest is a sizing bug, and the probe's resize log gives the transition history for a report.

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 (copy/paste block above or equivalent)
  • All code review bot comments are resolved
  • All human review comments are resolved

Summary by CodeRabbit

Summary

  • New Features
    • Added remote tmux “pane grids” diagnostics with per-window sizing snapshots and exact assigned-vs-rendered validation.
    • Improved remote tmux mirror sizing with per-window support, richer layout/zoom state, and automatic fallback when per-window sizing isn’t supported.
    • Added DEBUG-only remote commands to drive sizing UI tests (including deterministic window frame resizing).
    • Expanded remote tmux pane action localization to additional languages.
  • Bug Fixes
    • Improved manual resizing reporting behavior and ensured deferred samples flush on re-attach.
    • Reduced unnecessary portal redraw work for non-visible terminal surfaces; prevented main-window self-resizing from SwiftUI content.
  • Tests
    • Expanded unit, integration, and end-to-end mirror sizing labs and protocol coverage.

Note

High Risk
Large changes to remote tmux control connection, mirror sizing/rendering, and terminal surface resize reporting—core session mirroring behavior with complex timing and tmux protocol edge cases.

Overview
Fixes multi-pane remote tmux mirrors reporting too many columns to tmux (divider/padding counted as grid) and rendering panes with proportional splits instead of tmux’s assigned cells.

Sizing is now feed-forward: RemoteTmuxMirrorGeometry derives the pushed refresh-client -C size from container device pixels, the base layout structure, and measured ghostty cell/padding constants—never from rendered grids or tmux-assigned geometry—so layout echoes dedupe instead of looping. Per-window refresh-client -C '@id:WxH' (with session-wide fallback on %error) replaces a single session size; hidden tabs get a one-time size claim at attach.

Geometry no longer trusts layout strings: %layout-change / list-windows trees sit in pendingLayouts until a generation-tagged list-panes reply patches real rects (including zoom/visible layout and pane-border-status offsets). Initial attach batches all windows into one publish so tab order is deterministic.

Rendering switches to exact imposed frames (RemoteTmuxImposedFrameLayout) when layout matches the computed size, with a proportional transient mode during settle; chrome is tmux-like hairline strips with optional live pane-border-format labels and an active-pane dot (pane header toolbar removed). Manual-I/O surfaces report TerminalSurfaceRawSizingSample on every applied resize, with attach-time flush for off-window applies.

Also adds remote.tmux.pane_grids introspection, DEBUG sizing test socket verbs + optional SSH shim for hermetic e2e, main-window guard against NSHostingView content-driven resize, and CI tmux install for e2e.

Reviewed by Cursor Bugbot for commit 0f6ac5a. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

@ejc3 is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 4, 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

This PR adds remote tmux per-window sizing, feed-forward mirror geometry, debug sizing verbs, a real-tmux UI test harness, and supporting unit/docs updates. It also includes small workflow, reload, and watchdog script changes.

Changes

Remote tmux per-window sizing feature

Layer / File(s) Summary
Contracts and wire formats
Sources/RemoteTmuxControlCommandKind.swift, Sources/RemoteTmuxControlMessage.swift, Sources/RemoteTmuxWindow.swift, Sources/RemoteTmuxHost.swift, Sources/RemoteTmuxSSHTransport.swift, Sources/RemoteTmuxControlStreamParser.swift, Sources/RemoteTmuxController.swift, Packages/macOS/CmuxControlSocket/.../ControlCommandExecutionPolicy.swift, Sources/TerminalController+DebugMethodNames.swift
Extends the tmux command/message/window contracts, SSH path selection, parser routing, session-mirror lookup, and socket-worker/debug method lists used by the remote tmux flow.
Control connection and socket routing
Sources/RemoteTmuxControlConnection.swift, Sources/TerminalController+RemoteTmux.swift, Sources/TerminalController.swift
Adds per-window sizing sends and fallback, reconnect reseeding, header subscriptions, richer window parsing, layout preservation, and the pane-grid/test socket verbs with capability routing.
Mirror geometry and layout rendering
Sources/RemoteTmuxMirrorFrames.swift, Sources/RemoteTmuxMirrorGeometry.swift, Sources/RemoteTmuxLayoutContainer.swift, Sources/RemoteTmuxLayoutNode.swift, Sources/RemoteTmuxPaneHeader.swift, Sources/App/CmuxMainWindow.swift
Introduces exact mirror frames, feed-forward geometry, imposed/proportional layout rendering, layout-tree patching, header height constants, and the main-window sizing hook that stops content-driven resizing.
Window mirror state and surface plumbing
Sources/RemoteTmuxWindowMirror.swift, Sources/RemoteTmuxWindowMirrorView.swift, Sources/RemoteTmuxSessionMirror.swift, Packages/macOS/CmuxTerminal/.../TerminalSurface*.swift, Sources/TerminalWindowPortal.swift, Sources/Workspace.swift
Adds mirror sizing state, calibration and snapshotting, view-driven size pushes, manual surface size reporting, portal visibility gating, and workspace wiring for the remote-tmux display path.
Unit tests and project wiring
cmuxTests/RemoteTmuxAuthTests.swift, cmuxTests/RemoteTmuxControlParserTests.swift, cmuxTests/RemoteTmuxMirrorFeedForwardTests.swift, cmuxTests/RemoteTmuxMirrorGeometryTests.swift, cmuxTests/TerminalAndGhosttyTests.swift, cmux.xcodeproj/project.pbxproj
Updates parser/auth coverage, adds feed-forward and geometry suites, adds terminal and main-window contract tests, and registers the new sources in the Xcode project.
Localization
Resources/Localizable.xcstrings
Expands the remoteTmux.pane.close, remoteTmux.pane.splitDown, and remoteTmux.pane.splitRight translations to additional locales.

End-to-end UI tests, scripts, and docs

Layer / File(s) Summary
UI harness and helper scripts
cmuxUITests/RemoteTmuxSizingUITests.swift, scripts/remote-tmux-e2e-ssh-shim*.sh, scripts/remote-tmux-shape-zoo.sh, scripts/remote-tmux-width-probe.sh
Adds the real-tmux UI suite, SSH shim and check script, shape-zoo and width-probe helpers, and the socket/PTY plumbing used by the harness.
Reference docs
skills/cmux-testing/SKILL.md, skills/cmux-testing/references/remote-tmux-sizing-e2e.md
Documents the new UI harness, debug verbs, prerequisites, oracle checks, and local reproduction guidance.

Infra tweaks

Layer / File(s) Summary
Workflow and utility scripts
.github/workflows/test-e2e.yml, scripts/reload.sh, scripts/cmux-spin-watchdog.sh
Adds tmux to the E2E workflow install step, closes duplicate stdout/stderr file descriptors in the tagged-app launch path, and adds a watchdog for sustained main-thread CPU spin.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RemoteTmuxWindowMirrorView
  participant RemoteTmuxWindowMirror
  participant RemoteTmuxControlConnection
  participant TmuxServer

  RemoteTmuxWindowMirrorView->>RemoteTmuxWindowMirror: noteContainerSize(pointSize, scale)
  RemoteTmuxWindowMirror->>RemoteTmuxWindowMirror: currentGeometry().clientCells(...)
  RemoteTmuxWindowMirror->>RemoteTmuxControlConnection: setWindowSize(windowId, columns, rows)
  RemoteTmuxControlConnection->>TmuxServer: refresh-client -C '`@id`:WxH'
  TmuxServer-->>RemoteTmuxControlConnection: success or %error
  RemoteTmuxControlConnection-->>RemoteTmuxWindowMirror: size recorded or fallback applied
  RemoteTmuxControlConnection-->>RemoteTmuxWindowMirror: notifyTopologyChanged() after layout/header updates
Loading

Possibly related issues

Possibly related PRs

  • manaflow-ai/cmux#7063: Both PRs modify Sources/RemoteTmuxHost.swift SSH invocation behavior and default executable selection for remote tmux auth/transport.
  • manaflow-ai/cmux#7239: This PR builds on pane-border and active-pane chrome plumbing that is consumed by the remote tmux layout and mirror UI code here.

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (6 errors)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error Production RemoteTmuxControlConnection adds ContinuousClock sleeps for client-size debounce and redraw kicks; this is timing-based sync outside tests. Replace the sleeps/debounces with real state transitions or callbacks owned by an actor/timer abstraction; keep timing scaffolding test-only.
Cmux Algorithmic Complexity ❌ Error RemoteTmuxLayoutContainer.swift:69-74 filters the full segments array inside every divider strip body, making imposed render O(dividers×segments) per UI refresh. Precompute header segments per strip once (or carry a strip→segments map) so each divider doesn't rescan the entire segment list during render.
Cmux Swift File And Package Boundaries ❌ Error FAIL: RemoteTmuxControlConnection.swift is 2042 lines and got a huge +659-line expansion of networking/parsing/reconnect/sizing logic; core tmux types also stay in app Sources. Extract remote-tmux core (connection, parser, geometry, message/window models) into a small SwiftPM target; keep only UI/AppKit glue in the app target.
Cmux Swiftui State Layout ❌ Error RemoteTmuxWindowMirrorView reintroduces a GeometryReader to measure and drive sizing/layout, which the SwiftUI rule explicitly forbids. Restore onGeometryChange or a localized background measurement, and keep the sizing signal out of the view’s layout tree.
Cmux Architecture Rethink ❌ Error FAIL: RemoteTmuxControlConnection adds production Task.sleep debounce/attach-kick timers and extra state caches, reintroducing the timing-side repair paths the rule forbids. Move sizing to one event-driven owner; replace sleep-based debounce/kick with deterministic attach/resize acknowledgements, and keep timing helpers test-only.
Cmux No Test Or Debug Seam In Production Source ❌ Error Sources/TerminalController+RemoteTmuxTestSupport.swift adds remote.tmux.test_exec/test_set_frame DEBUG-only handlers with no production caller; this is a test seam in shipping source. Move those handlers and their dispatch entries out of Sources into the test target or a dedicated debug-only target/file, and use @testable import for any needed internal state.
✅ Passed checks (19 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 New UI/stateful code stays on @MainActor, and the new socket verbs hop through MainActor.run before touching app stores.
Cmux Browser Automation Off-Main ✅ Passed The PR stays in remote-tmux sizing/docs code; no browser.* routing changes were introduced, so the off-main browser automation rule isn’t implicated.
Cmux Expensive Synchronous Load ✅ Passed The PR adds tmux sizing/socket logic and DEBUG test support only; no new RestorableAgentSessionIndex.load(), agent-history scans, or large sync file/JSON loads on main or interactive paths.
Cmux Cache Substitution Correctness ✅ Passed Snapshot/reporting uses read-only cached state with nil guards and stale pruning; dead windows are dropped and rejected per-window sizes fall back deterministically.
Cmux No Hacky Sleeps ✅ Passed Added sleeps/polls are confined to test/manual scaffolding or a watchdog; no new production sync delay was introduced or worsened.
Cmux Swift Concurrency ✅ Passed No new forbidden async pattern: the only DispatchGroup is in a DEBUG-only test verb, and runtime Tasks are stored/cancelled, not fire-and-forget.
Cmux Swift @Concurrent ✅ Passed No violation: the diff adds no @concurrent or nonisolated async signatures, and the new heavy remote-tmux verbs run on the socket worker with explicit MainActor hops where needed.
Cmux Swiftpm Lockfiles ✅ Passed PR diff only changes a source file and docs; no Package.swift, Package.resolved, .gitignore, or Xcode project/package-reference files are touched.
Cmux Swift Logging ✅ Passed No added or changed production Swift logging: diff search found no new print/debugPrint/dump/NSLog/Logger lines in touched Swift files.
Cmux User-Facing Error Privacy ✅ Passed Changed production RPCs use generic host/session errors; raw tmux/SSH text stays in DEBUG logs/tests, not user-visible error bodies or copy.
Cmux Full Internationalization ✅ Passed PASS: The only new user-facing Swift strings are the tmux context-menu labels, and their catalog entries cover all supported locales; other changes are tests/docs/debug-only.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR changes are remote-tmux sizing and test/support code; no new standalone auxiliary windows or cmux.* close-shortcut identifier changes were introduced.
Cmux Source Artifacts ✅ Passed Changed paths are only Sources/RemoteTmuxControlConnection.swift and docs/remote-tmux-sizing-timers.md; both are intentional source/doc changes, not artifacts or scratch output.
Cmux No Ambient Global State ✅ Passed Added remote-tmux APIs live on owning types/extensions; no new file-scope API, mutable global state, or singleton/runtime state was introduced.
Title check ✅ Passed The title clearly summarizes the main change: feed-forward sizing and mirrored tmux rendering behavior.
Description check ✅ Passed The description covers summary, testing, demo video, review trigger, and checklist, with the required sections mostly complete.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@ejc3
ejc3 marked this pull request as ready for review July 4, 2026 15:00
@ejc3
ejc3 requested a review from lawrencecchen as a code owner July 4, 2026 15:00
@ejc3
ejc3 force-pushed the remote-tmux-feed-forward branch from a13b26c to 8a1212e Compare July 4, 2026 15:04
@greptile-apps

greptile-apps Bot commented Jul 4, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces a feedback-loop-prone proportional SwiftUI split layout with a feed-forward sizing model for remote tmux mirrors: the client size pushed to tmux is derived purely from container device pixels, the layout tree's structure, and measured ghostty cell/padding constants — never from tmux-assigned geometry or rendered grids — eliminating the one-column-short wrapping defect that appeared on multi-pane mirrored windows.

  • Feed-forward sizing (RemoteTmuxMirrorGeometry): clientCells(pixelWidth:pixelHeight:structure:) computes columns×rows without reading any tmux-assigned sizes, so the %layout-change echo of cmux's own push recomputes to the identical value and deduplicates silently, removing the feedback loop entirely.
  • Quarantined geometry publication: %layout-change / list-windows trees are held in pendingLayouts until a generation-tagged list-panes reply patches real pane rects (including pane-border-status offsets and zoom state); the render never sees raw layout-string geometry.
  • Per-window client sizing via refresh-client -C '@id:WxH' with session-wide fallback for older tmux servers, per-window dedup, and reconnect reseed; hidden tabs claim a size exactly once at attach to prevent other windows from collapsing to 80×24.

Confidence Score: 5/5

The feed-forward sizing model is architecturally sound and thoroughly tested; no new defects were identified in this review pass.

The core change — replacing proportional SwiftUI splits and grid-feedback sizing with a pure-function pixel→cells→frames pipeline — is correct by construction: clientCells never reads tmux-assigned geometry, so echo events are silently deduped and there is no feedback loop to manage. The quarantine + generation-tagged list-panes fetch prevents raw layout-string rects from ever reaching the render. Per-window sizing dedup and the hidden-mirror write-once gate handle the multi-window attach ordering correctly. The localization additions cover all 20 supported locales. The test suite exercises the key invariants. The one pre-existing concern the team is tracking — TerminalController+RemoteTmuxTestSupport.swift remaining in production Sources/ — was already flagged in a prior review round.

No files require special attention beyond the pre-existing discussion of Sources/TerminalController+RemoteTmuxTestSupport.swift.

Important Files Changed

Filename Overview
Sources/RemoteTmuxMirrorGeometry.swift New pure-value type implementing the feed-forward sizing math (clientCells and frames); uses cumulative edge-rail device-pixel placement, ChromeTree pre-pass for linear complexity, and paneTouchesTop for pane-border-status header mode detection. Well-tested by property tests.
Sources/RemoteTmuxWindowMirror.swift Major refactor: adds feed-forward sizing (noteContainerSize, updateClientSize, framesForRender), quarantined geometry consumption (apply(window:)), structure-signature-gated layoutStructureVersion, and sizingSnapshot for socket introspection. Proportional transient mode preserved as fallback.
Sources/RemoteTmuxControlConnection+Sizing.swift New extension: setWindowSize (per-window with debounce), setClientSize (session-wide with debounce), notePerWindowSizeRejected (fallback on %error), scheduleAttachRedrawKickIfNeeded (350ms gap documented in sizing-timers.md). Per-window dedup ledger guards the hidden-mirror write-once invariant.
Sources/RemoteTmuxControlConnection+LayoutPublication.swift New extension: quarantine + generation-tagged list-panes fetch pipeline. handlePaneRectsReply validates every pane rect before publishing (guards raw layout-string geometry from ever reaching observers), with one retry on partial/garbled replies and atomic first-attach batch staging.
Sources/TerminalController+RemoteTmuxTestSupport.swift New file in production Sources/ containing the #if DEBUG-guarded v2RemoteTmuxTestExec and v2RemoteTmuxTestSetFrame verbs. Dispatch and policy routing are correctly #if DEBUG-gated and absent from socketWorkerMethods/allowedSocketMethods in release builds; however the file remains in Sources/ rather than a dedicated debug folder.
Sources/RemoteTmuxWindowMirrorView.swift GeometryReader replaced with onGeometryChange; sizing retry loop removed; imposed-vs-transient mode selected by framesForRender returning nil when layout does not yet match computed size. isVisibleForSizing gate enforced at both onAppear and onGeometryChange sites.
Sources/RemoteTmuxControlConnection.swift Properties widened from private/private(set) to internal to allow extension-file access across +Sizing, +LayoutPublication, +PaneSubscriptions, and +CommandResults — necessary for the split-file architecture, not for test access.
Resources/Localizable.xcstrings Adds translations for remoteTmux.pane.close, splitDown, and splitRight across all 20 supported locales. Complete coverage; no gaps detected.
cmuxTests/RemoteTmuxMirrorGeometryTests.swift New property-test suite covering clientCells structure-only invariant, frame edge-rail placement (+1 device-px bias), chrome fold per shape, paneTouchesTop detection, and minimum floor behavior.
cmuxTests/RemoteTmuxMirrorFeedForwardTests.swift Contract tests for the mirror's sizing invariants: pure-function re-apply, hidden-mirror one-shot write, reconcile-never-pushes (echo-silence guarantee), transient fallback on size mismatch, and zoom's panel-lifecycle neutrality.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant View as RemoteTmuxWindowMirrorView
    participant Mirror as RemoteTmuxWindowMirror
    participant Geometry as RemoteTmuxMirrorGeometry
    participant Conn as RemoteTmuxControlConnection
    participant tmux as tmux server

    View->>Mirror: noteContainerSize(pointSize, scale)
    View->>Mirror: updateClientSize()
    Mirror->>Geometry: clientCells(pixelWidth, pixelHeight, structure)
    Note over Geometry: f(pixels, structure, constants) - never reads tmux geometry
    Geometry-->>Mirror: (cols, rows)
    Mirror->>Conn: setWindowSize(windowId, cols, rows)
    Conn->>Conn: debounce 180ms
    Conn->>tmux: "refresh-client -C '@id:WxH'"
    tmux-->>Conn: %layout-change (echo)
    Conn->>Conn: stagePendingLayout (quarantine)
    Conn->>tmux: list-panes (generation-tagged)
    tmux-->>Conn: real pane rects + titles + active
    Conn->>Conn: handlePaneRectsReply - publish windowsByID
    Conn-->>Mirror: reconcile(layout:) / apply(window:)
    Mirror->>View: framesForRender(containerPt:)
    Note over Mirror,View: imposed frames: exact device-pixel rails or proportional transient if layout mismatch
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant View as RemoteTmuxWindowMirrorView
    participant Mirror as RemoteTmuxWindowMirror
    participant Geometry as RemoteTmuxMirrorGeometry
    participant Conn as RemoteTmuxControlConnection
    participant tmux as tmux server

    View->>Mirror: noteContainerSize(pointSize, scale)
    View->>Mirror: updateClientSize()
    Mirror->>Geometry: clientCells(pixelWidth, pixelHeight, structure)
    Note over Geometry: f(pixels, structure, constants) - never reads tmux geometry
    Geometry-->>Mirror: (cols, rows)
    Mirror->>Conn: setWindowSize(windowId, cols, rows)
    Conn->>Conn: debounce 180ms
    Conn->>tmux: "refresh-client -C '@id:WxH'"
    tmux-->>Conn: %layout-change (echo)
    Conn->>Conn: stagePendingLayout (quarantine)
    Conn->>tmux: list-panes (generation-tagged)
    tmux-->>Conn: real pane rects + titles + active
    Conn->>Conn: handlePaneRectsReply - publish windowsByID
    Conn-->>Mirror: reconcile(layout:) / apply(window:)
    Mirror->>View: framesForRender(containerPt:)
    Note over Mirror,View: imposed frames: exact device-pixel rails or proportional transient if layout mismatch
Loading

Reviews (16): Last reviewed commit: "Handle remote tmux mirror runtime-ready ..." | Re-trigger Greptile

Comment thread Sources/RemoteTmuxHost.swift
@ejc3
ejc3 force-pushed the remote-tmux-feed-forward branch from 8a1212e to 6b04066 Compare July 4, 2026 15:26

@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: 5

🤖 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 `@cmuxUITests/RemoteTmuxSizingUITests.swift`:
- Line 325: `startWidthProbes` is using a fixed sleep before sending keys, which
violates the test time-inversion policy; replace that delay with a real
readiness check for the target panes. Update the `startWidthProbes` flow used by
`buildLabSession` and `buildShapeZoo` to poll a concrete signal such as
`#{pane_current_command}` (or the same readiness condition used by
`remote-tmux-shape-zoo.sh`) before typing, so the test proceeds only when the
panes are actually ready. Remove the “let the shell finish starting” style wait
and keep the waiting logic localized to the pane-startup path.
- Line 47: The socket path in RemoteTmuxSizingUITests is being built with
NSHomeDirectory() and can exceed sun_path in long sandbox paths, causing the
guard to fail before lastSocketFailure is recorded. Update the socketPath setup
in the test’s socket creation flow to either preflight the full path length and
set lastSocketFailure with an explicit overflow message, or switch to a shorter
guaranteed socket root so the failure is surfaced clearly.

In `@Sources/RemoteTmuxControlConnection.swift`:
- Around line 508-577: Stale per-window sizing state in
RemoteTmuxControlConnection is leaking across closed windows: clear the closed
window’s entries from lastWindowSizes and windowSizeDebounceTasks when handling
.windowClose so reconnect/replay can’t use a dead `@id` target. Also update
notePerWindowSizeRejected() and the .perWindowSize error handling path to
distinguish “unsupported on old tmux” from “window not found,” so only the
former flips supportsPerWindowSize to false and falls back to
setClientSize(columns:rows:).

In `@Sources/RemoteTmuxWindowMirror.swift`:
- Around line 416-428: Remove the test-only observability seams from
RemoteTmuxWindowMirror: `lastWindowSizeForTesting` should be dropped and tests
should read `connection.lastWindowSizes[windowId]` directly via `@testable
import`, and `geometryOverrideForTesting` should not live in the production
type. Move the geometry stub behind a dedicated debug-only helper/file or
replace it with a real injectable dependency, and update `currentGeometry()` so
production code no longer branches on the test override.

In `@Sources/TerminalController.swift`:
- Line 2041: `system.capabilities` is advertising DEBUG-only tmux verbs in the
always-on methods list, causing Release builds to claim support for
`remote.tmux.test_exec` and `remote.tmux.test_set_frame` even though
`socketWorkerV2Response` only handles them under `#if DEBUG`. Update
`TerminalController` so these two tokens are removed from the unconditional
`methods` array and are added via `Self.v2DebugMethodNames` alongside the other
debug-only capability names, keeping the advertised capabilities aligned with
actual dispatch behavior.
🪄 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: 8857db97-f31a-4da2-9c53-0b47f6e83fad

📥 Commits

Reviewing files that changed from the base of the PR and between f48922a and 8a1212e.

📒 Files selected for processing (33)
  • .github/workflows/test-e2e.yml
  • Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Wire/ControlCommandExecutionPolicy.swift
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+Sizing.swift
  • Sources/RemoteTmuxControlCommandKind.swift
  • Sources/RemoteTmuxControlConnection.swift
  • Sources/RemoteTmuxControlMessage.swift
  • Sources/RemoteTmuxControlStreamParser.swift
  • Sources/RemoteTmuxController.swift
  • Sources/RemoteTmuxHost.swift
  • Sources/RemoteTmuxLayoutContainer.swift
  • Sources/RemoteTmuxMirrorFrames.swift
  • Sources/RemoteTmuxMirrorGeometry.swift
  • Sources/RemoteTmuxPaneHeader.swift
  • Sources/RemoteTmuxSSHTransport.swift
  • Sources/RemoteTmuxSessionMirror.swift
  • Sources/RemoteTmuxWindow.swift
  • Sources/RemoteTmuxWindowMirror.swift
  • Sources/RemoteTmuxWindowMirrorView.swift
  • Sources/TerminalController+RemoteTmux.swift
  • Sources/TerminalController.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/RemoteTmuxAuthTests.swift
  • cmuxTests/RemoteTmuxControlParserTests.swift
  • cmuxTests/RemoteTmuxMirrorFeedForwardTests.swift
  • cmuxTests/RemoteTmuxMirrorGeometryTests.swift
  • cmuxUITests/RemoteTmuxSizingUITests.swift
  • scripts/reload.sh
  • scripts/remote-tmux-e2e-ssh-shim-check.sh
  • scripts/remote-tmux-e2e-ssh-shim.sh
  • scripts/remote-tmux-shape-zoo.sh
  • scripts/remote-tmux-width-probe.sh
  • skills/cmux-testing/SKILL.md
  • skills/cmux-testing/references/remote-tmux-sizing-e2e.md

Comment thread cmuxUITests/RemoteTmuxSizingUITests.swift
Comment thread cmuxUITests/RemoteTmuxSizingUITests.swift Outdated
Comment thread Sources/RemoteTmuxControlConnection.swift Outdated
Comment thread Sources/RemoteTmuxWindowMirror.swift Outdated
Comment thread Sources/TerminalController.swift Outdated
@ejc3
ejc3 marked this pull request as draft July 4, 2026 15:56

@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: 7

♻️ Duplicate comments (1)
Sources/TerminalController.swift (1)

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

system.capabilities still unconditionally advertises DEBUG-only verbs.

remote.tmux.test_exec / remote.tmux.test_set_frame are appended to the always-on methods array here, but the dispatch cases in socketWorkerV2Response (Lines 1140-1143) are #if DEBUG-gated. In Release, system.capabilities claims support for these two verbs but calling them returns method_not_found. This is the same gap flagged on a prior revision of this PR and remains unresolved.

🐛 Proposed fix: move the DEBUG-only tokens into the DEBUG-gated append
-            "workspace.remote.terminal_session_end", "remote.tmux.sessions", "remote.tmux.attach", "remote.tmux.detach", "remote.tmux.state", "remote.tmux.mirror", "remote.tmux.window", "remote.tmux.pane_grids", "remote.tmux.test_exec", "remote.tmux.test_set_frame",
+            "workspace.remote.terminal_session_end", "remote.tmux.sessions", "remote.tmux.attach", "remote.tmux.detach", "remote.tmux.state", "remote.tmux.mirror", "remote.tmux.window", "remote.tmux.pane_grids",
`#if` DEBUG
        methods.append(contentsOf: Self.v2DebugMethodNames)
        methods.append(contentsOf: ["remote.tmux.test_exec", "remote.tmux.test_set_frame"])
`#endif`
🤖 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 `@Sources/TerminalController.swift` at line 2041, `system.capabilities` is
still advertising DEBUG-only tmux verbs in release builds; move
`remote.tmux.test_exec` and `remote.tmux.test_set_frame` out of the always-on
`methods` list in `TerminalController` and append them only inside the existing
`#if DEBUG` block alongside `Self.v2DebugMethodNames`, so the advertised
capabilities match the `socketWorkerV2Response` dispatch cases.
🤖 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 `@cmuxUITests/RemoteTmuxSizingUITests.swift`:
- Around line 70-71: The test cleanup order in the app-launching scenarios is
wrong: app.terminate() runs before tearDown() can use the app socket to call
tmux(["kill-server"]). Update the deferred cleanup in RemoteTmuxSizingUITests so
the tmux server is killed first and the app is terminated afterward, and apply
the same ordering to each launchApp() scenario referenced in the test file to
ensure the remote.tmux.test_exec path remains reachable during teardown.

In `@scripts/remote-tmux-e2e-ssh-shim-check.sh`:
- Around line 60-87: The shim check script is writing stderr to a predictable
shared path, which can collide across runs or be abused via symlink precreation.
Update the stderr capture in the remote tmux e2e shim checks to use the existing
private mktemp-based temp directory/lab directory instead of a global /tmp
filename, and make sure the checks around tmux_remote, check, and the temp
cleanup all reference that per-run private path.

In `@scripts/remote-tmux-shape-zoo.sh`:
- Around line 38-39: The probe payload path in the remote tmux script is
predictable, so update the PROBE assignment and its write/cleanup flow to use an
unpredictable temporary file created with mktemp instead of the current fixed
/tmp/remote-tmux-width-probe-$(id -un).sh pattern. Keep the logic centered
around the PROBE variable in scripts/remote-tmux-shape-zoo.sh, and ensure the
temporary file is removed on exit using the script’s cleanup/trap path.
- Around line 84-87: The tmux setup for the mainh window is missing part of the
e2e shape: update the remote-tmux-shape-zoo.sh window construction around the
mainh setup so it matches the UI test’s mainh layout, including the second
horizontal split and the main-horizontal arrangement. Use the existing tmux
commands in the mainh block as the place to mirror the e2e suite’s shape
exactly, preserving the intended window structure for the shape zoo.
- Around line 92-109: The probe startup in the tmux pane loop relies on fixed
sleeps and a pane_current_command check that can misclassify valid bash panes,
so replace the shell-typing path with a deterministic tmux-driven launch for the
probe. Update the logic around the pane iteration and probe launch in the
remote-tmux-shape-zoo.sh flow so the probe starts directly through tmux commands
rather than sending keystrokes into shells, and remove the retry/sleep-based
nudge loop entirely.

In `@Sources/RemoteTmuxLayoutContainer.swift`:
- Around line 62-71: Remove the direct RemoteTmuxWindowMirror dependency from
pane rows in RemoteTmuxLayoutContainer and RemoteTmuxProportionalSplit so each
RemoteTmuxPaneLeaf is driven by immutable pane snapshot data plus explicit
action closures. Update the ForEach content to pass only the pane state needed
for rendering and route focus/requestSplit through callbacks from the parent
instead of reading mirror.activePaneId inside the leaf. Keep the mirror owned at
a higher level and prevent it from being captured by every pane subtree.

In `@Sources/RemoteTmuxWindowMirrorView.swift`:
- Around line 72-91: The sizing retry in pushClientSize(pointSize:) is still
time-based via ContinuousClock.sleep, which should be replaced by the actual
cell-size update signal. Use the existing GhosttyTerminalView
ghosttyDidUpdateCellSize notification, or the sizing-snapshot update path, to
trigger mirror.updateClientSize() when cell metrics change instead of looping on
a fixed delay. Keep the retry cancellation in sizingRetryTask, and wire the
update through the mirror/client-size flow so the render path becomes
event-driven rather than timer-driven.

---

Duplicate comments:
In `@Sources/TerminalController.swift`:
- Line 2041: `system.capabilities` is still advertising DEBUG-only tmux verbs in
release builds; move `remote.tmux.test_exec` and `remote.tmux.test_set_frame`
out of the always-on `methods` list in `TerminalController` and append them only
inside the existing `#if DEBUG` block alongside `Self.v2DebugMethodNames`, so
the advertised capabilities match the `socketWorkerV2Response` dispatch cases.
🪄 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: c7eca02f-cf86-42a6-918a-24851f594bb0

📥 Commits

Reviewing files that changed from the base of the PR and between 8a1212e and cc19721.

📒 Files selected for processing (33)
  • .github/workflows/test-e2e.yml
  • Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Wire/ControlCommandExecutionPolicy.swift
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+Sizing.swift
  • Sources/RemoteTmuxControlCommandKind.swift
  • Sources/RemoteTmuxControlConnection.swift
  • Sources/RemoteTmuxControlMessage.swift
  • Sources/RemoteTmuxControlStreamParser.swift
  • Sources/RemoteTmuxController.swift
  • Sources/RemoteTmuxHost.swift
  • Sources/RemoteTmuxLayoutContainer.swift
  • Sources/RemoteTmuxMirrorFrames.swift
  • Sources/RemoteTmuxMirrorGeometry.swift
  • Sources/RemoteTmuxPaneHeader.swift
  • Sources/RemoteTmuxSSHTransport.swift
  • Sources/RemoteTmuxSessionMirror.swift
  • Sources/RemoteTmuxWindow.swift
  • Sources/RemoteTmuxWindowMirror.swift
  • Sources/RemoteTmuxWindowMirrorView.swift
  • Sources/TerminalController+RemoteTmux.swift
  • Sources/TerminalController.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/RemoteTmuxAuthTests.swift
  • cmuxTests/RemoteTmuxControlParserTests.swift
  • cmuxTests/RemoteTmuxMirrorFeedForwardTests.swift
  • cmuxTests/RemoteTmuxMirrorGeometryTests.swift
  • cmuxUITests/RemoteTmuxSizingUITests.swift
  • scripts/reload.sh
  • scripts/remote-tmux-e2e-ssh-shim-check.sh
  • scripts/remote-tmux-e2e-ssh-shim.sh
  • scripts/remote-tmux-shape-zoo.sh
  • scripts/remote-tmux-width-probe.sh
  • skills/cmux-testing/SKILL.md
  • skills/cmux-testing/references/remote-tmux-sizing-e2e.md

Comment thread cmuxUITests/RemoteTmuxSizingUITests.swift
Comment thread scripts/remote-tmux-e2e-ssh-shim-check.sh Outdated
Comment thread scripts/remote-tmux-shape-zoo.sh Outdated
Comment thread scripts/remote-tmux-shape-zoo.sh
Comment thread scripts/remote-tmux-shape-zoo.sh Outdated
Comment thread Sources/RemoteTmuxLayoutContainer.swift Outdated
Comment thread Sources/RemoteTmuxWindowMirrorView.swift Outdated
@ejc3 ejc3 changed the title Mirrored tmux panes can render narrower than their assigned width; size mirrors feed-forward Remote tmux mirrors: exact feed-forward sizing, verified pane geometry, faithful live pane headers, active-pane indicator, and drag-stable rendering Jul 5, 2026
@ejc3
ejc3 marked this pull request as ready for review July 5, 2026 01:53
Comment thread Sources/TerminalController+RemoteTmux.swift 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: 1

🤖 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 `@cmuxUITests/RemoteTmuxSizingUITests.swift`:
- Around line 483-489: The readiness check in RemoteTmuxSizingUITests should not
rely only on perPane.allSatisfy because tmux(_) trims trailing empty output,
letting a trailing unset `@probe_alive` pane slip through. Update the gate around
the tmux("list-panes"... ) loop to verify the expected pane count as well as all
values being "1", using the existing tmux(_:) helper and the sessionName:`@0`
probe output so a missing final probe cannot pass early.
🪄 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: 1ce1d226-3a90-4caf-8762-e6c562e6821c

📥 Commits

Reviewing files that changed from the base of the PR and between cc19721 and 2bc26b1.

📒 Files selected for processing (27)
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+RuntimeLifecycle.swift
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+Sizing.swift
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface.swift
  • Resources/Localizable.xcstrings
  • Sources/App/CmuxMainWindow.swift
  • Sources/RemoteTmuxControlCommandKind.swift
  • Sources/RemoteTmuxControlConnection.swift
  • Sources/RemoteTmuxLayoutContainer.swift
  • Sources/RemoteTmuxLayoutNode.swift
  • Sources/RemoteTmuxMirrorGeometry.swift
  • Sources/RemoteTmuxPaneHeader.swift
  • Sources/RemoteTmuxSessionMirror.swift
  • Sources/RemoteTmuxWindowMirror.swift
  • Sources/RemoteTmuxWindowMirrorView.swift
  • Sources/TerminalController+DebugMethodNames.swift
  • Sources/TerminalController+RemoteTmux.swift
  • Sources/TerminalController.swift
  • Sources/TerminalWindowPortal.swift
  • Sources/Workspace.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/RemoteTmuxMirrorFeedForwardTests.swift
  • cmuxTests/RemoteTmuxMirrorGeometryTests.swift
  • cmuxTests/TerminalAndGhosttyTests.swift
  • cmuxUITests/RemoteTmuxSizingUITests.swift
  • scripts/cmux-spin-watchdog.sh
  • scripts/remote-tmux-shape-zoo.sh
  • scripts/remote-tmux-width-probe.sh
💤 Files with no reviewable changes (2)
  • Sources/RemoteTmuxPaneHeader.swift
  • cmux.xcodeproj/project.pbxproj

Comment thread cmuxUITests/RemoteTmuxSizingUITests.swift Outdated
@ejc3

ejc3 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Pre-merge check fixes in 53238f1: placement chrome is now a one-pass fold threaded through place() (linear in pane count); the single-pane initial-sizing retry loop and its NotificationCenter observers are deleted in favor of a direct TerminalSurface.onRuntimeReady callback plus the applied-size report (event-driven, verified 5/5 on the sizing e2e suite after an A/B run against the previous head isolated the retry removal); the DEBUG-only test verbs moved to a dedicated debug-only file — they stay app-side because the sandboxed XCUITest runner cannot reach /tmp or spawn tmux, and @testable import cannot cross the process boundary. The description now follows the PR template.

On the package-boundaries check: extracting the remote-tmux connection/geometry into a SwiftPM target is the right end state, but doing it inside this PR would mean loosening access control across a ~2000-line type immediately after end-to-end validation. Planned as the first follow-up PR.

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

@cubic-dev-ai

cubic-dev-ai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Pre-merge check fixes in 53238f1: placement chrome is now a one-pass fold threaded through place() (linear in pane count); the single-pane initial-sizing retry loop and its NotificationCenter observers are deleted in favor of a direct TerminalSurface.onRuntimeReady callback plus the applied-size report (event-driven, verified 5/5 on the sizing e2e suite after an A/B run against the previous head isolated the retry removal); the DEBUG-only test verbs moved to a dedicated debug-only file — they stay app-side because the sandboxed XCUITest runner cannot reach /tmp or spawn tmux, and @testable import cannot cross the process boundary. The description now follows the PR template.

On the package-boundaries check: extracting the remote-tmux connection/geometry into a SwiftPM target is the right end state, but doing it inside this PR would mean loosening access control across a ~2000-line type immediately after end-to-end validation. Planned as the first follow-up PR.

@codex review
...

@ejc3 I can't start this review because your workspace has reached its free monthly review limit. cubic has reviewed 241,631 of the 240,000 allowed lines of code this month. Reviews resume on 1 August 2026 (in 27 days). Paid plans include much higher monthly review limits. Upgrade now to resume reviews.

To help optimise your usage, you can tune cubic to get the most out of your usage limits:

Learn more →

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026 •

Copy link
Copy Markdown

@ejc3 Thanks for the detailed context on the pre-merge fixes. Noted the following:

  • Placement chrome refactored to a one-pass fold in place() — good improvement, linear in pane count.
  • Retry-loop/NotificationCenter removal in favor of TerminalSurface.onRuntimeReady + applied-size reporting is event-driven and was validated via A/B isolation against the previous head (5/5 on the sizing e2e suite) — solid verification approach for a timing-sensitive change.
  • DEBUG-only test verbs (remote.tmux.test_exec, remote.tmux.test_set_frame) staying app-side makes sense given the XCUITest runner's sandbox restrictions (no /tmp access, no tmux spawning) and the process-boundary limitation on @testable import.
  • Deferring the SwiftPM extraction of the remote-tmux connection/geometry code to a follow-up PR is reasonable — loosening access control across a ~2000-line type right after e2e validation would add unnecessary risk to this PR.

I'll kick off a fresh review of the latest commit now.


✏️ Learnings added
Learnt from: ejc3
URL: https://github.com/manaflow-ai/cmux/pull/7315

Timestamp: 2026-07-05T07:11:17.680Z
Learning: In this repository's remote-tmux mirror sizing feature (Sources/TerminalController+RemoteTmux.swift), the DEBUG-only test verbs `remote.tmux.test_exec` and `remote.tmux.test_set_frame` intentionally live app-side (not in the XCUITest target) because the sandboxed XCUITest runner cannot reach `/tmp` or spawn `tmux` processes, and `testable import` cannot cross the process boundary between the UI test runner and the app under test.

Learnt from: ejc3
URL: https://github.com/manaflow-ai/cmux/pull/7315

Timestamp: 2026-07-05T07:11:17.680Z
Learning: Extracting the remote-tmux connection/geometry logic (currently a ~2000-line type spanning files like Sources/RemoteTmuxControlConnection.swift, Sources/RemoteTmuxWindowMirror.swift, Sources/RemoteTmuxMirrorGeometry.swift) into a separate SwiftPM package/target is the planned end state, but is deferred to a first follow-up PR after the sizing feed-forward model PR (cmux repo, PR `#7315`), to avoid loosening access control across the large type immediately after end-to-end validation.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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)
cmuxUITests/RemoteTmuxSizingUITests.swift (1)

794-808: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Chunk-level UTF-8 decode can drop response bytes.

The read loop decodes each 8192-byte chunk independently via String(bytes:encoding:.utf8). If a response ever crosses the buffer boundary mid multi-byte sequence, that chunk decodes to nil and is silently discarded, corrupting accumulator and yielding a spurious nil from a socket call (a flaky-test source that's hard to trace). Accumulating Data and decoding once past the newline avoids it.

🐛 Proposed fix: accumulate bytes, decode after framing
-        var buffer = [UInt8](repeating: 0, count: 8192)
-        var accumulator = ""
-        let deadline = Date().addingTimeInterval(65)
-        while Date() < deadline {
-            let count = Darwin.read(fd, &buffer, buffer.count)
-            guard count > 0 else { break }
-            if let chunk = String(bytes: buffer[0..<count], encoding: .utf8) {
-                accumulator.append(chunk)
-                if let newline = accumulator.firstIndex(of: "\n") {
-                    return String(accumulator[..<newline])
-                }
-            }
-        }
-        return accumulator.isEmpty ? nil : accumulator.trimmingCharacters(in: .whitespacesAndNewlines)
+        var buffer = [UInt8](repeating: 0, count: 8192)
+        var accumulator = Data()
+        let deadline = Date().addingTimeInterval(65)
+        while Date() < deadline {
+            let count = Darwin.read(fd, &buffer, buffer.count)
+            guard count > 0 else { break }
+            accumulator.append(contentsOf: buffer[0..<count])
+            if let newline = accumulator.firstIndex(of: UInt8(ascii: "\n")) {
+                return String(decoding: accumulator[..<newline], as: UTF8.self)
+            }
+        }
+        return accumulator.isEmpty ? nil : String(decoding: accumulator, as: UTF8.self).trimmingCharacters(in: .whitespacesAndNewlines)
🤖 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 `@cmuxUITests/RemoteTmuxSizingUITests.swift` around lines 794 - 808, The read
loop in the helper that returns the first line from the socket is decoding each
buffer chunk independently, which can drop bytes when a UTF-8 sequence spans a
boundary. Update the logic in this read helper to accumulate raw bytes first,
detect the newline framing on the byte buffer, and only then decode the complete
response once; this will prevent silent chunk loss and flaky nil results from
the socket read path.
🤖 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 `@cmuxUITests/RemoteTmuxSizingUITests.swift`:
- Around line 794-808: The read loop in the helper that returns the first line
from the socket is decoding each buffer chunk independently, which can drop
bytes when a UTF-8 sequence spans a boundary. Update the logic in this read
helper to accumulate raw bytes first, detect the newline framing on the byte
buffer, and only then decode the complete response once; this will prevent
silent chunk loss and flaky nil results from the socket read path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f969b95a-5e5a-4a76-85d0-33347b927121

📥 Commits

Reviewing files that changed from the base of the PR and between 9e7e9df and 53238f1.

📒 Files selected for processing (8)
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+RuntimeLifecycle.swift
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface.swift
  • Sources/RemoteTmuxMirrorGeometry.swift
  • Sources/RemoteTmuxSessionMirror.swift
  • Sources/TerminalController+RemoteTmux.swift
  • Sources/TerminalController+RemoteTmuxTestSupport.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxUITests/RemoteTmuxSizingUITests.swift
💤 Files with no reviewable changes (1)
  • Sources/TerminalController+RemoteTmux.swift

@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: 53238f18da

ℹ️ 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 Sources/RemoteTmuxSessionMirror.swift
Comment thread Sources/RemoteTmuxMirrorGeometry.swift
@ejc3

ejc3 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Architecture Rethink disposition (2a3088c):

Retry loop + readiness observers: removed (earlier commits). Initial sizing rides two direct surface events — onRuntimeReady (fires once when the runtime surface comes alive, covering surfaces created already at their final grid) and the applied-size report. Validated by A/B: the sizing e2e suite goes 1/5 → 5/5 with the readiness callback in place.

Redraw-kick gap timer: event-gated redesign was built, validated green end to end, and withdrawn under adversarial review. The SIGWINCH the kick forces is delivered by the pane PTY ioctl, which tmux defers behind internal resize coalescing that emits nothing observable to control clients — layout publications confirm the wrong fact (layout recomputation is immediate), land inside the coalescing window on fast links so the shrink/restore pair collapses to net-zero, and per-window confirmation predicates admit spurious matches from unrelated windows already at the shrunken height. The full analysis now lives on the constant so the timer can't be "cleaned up" into that trap later.

Size-send debounces: rate limiters, not correctness dependencies — the ledger is written synchronously before any deferral, dedup makes late sends idempotent, and the reconnect reseed replays the ledger. Reply-gated coalescing self-clocks to the control channel RTT and would reinstate the SIGWINCH storm the debounce absorbs; the layout-settle oscillation has no terminating event to gate on. Also documented in place.

Reconnect backoff: deliberate network retry policy, unchanged.

Every timer that correctness rode on is gone; the two that remain are rate limiters with their non-substitutability documented at the declaration.

@ejc3

ejc3 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Added docs/remote-tmux-sizing-timers.md (f92a5b5) documenting why the two remaining sizing timers are load-bearing — the redraw-kick gap (SIGWINCH is a pane PTY ioctl deferred behind tmux's own resize coalescing, which emits nothing observable, so no control event can gate the restore) and the size-send debounce (a rate limiter the ledger + dedup + reconnect reseed make correctness-neutral). Includes a by-hand exploration with its confounds spelled out.

On the question of a regression test: I attempted one and concluded against it, corroborated by an independent review. The kick's effect is a SIGWINCH below cmux's observable control surface — an e2e that observes it reliably requires the recorder to be the foreground process in the exact displayed mirrored pane and is inherently flaky around OS signal delivery, and a cheap command-shape unit test would pass for the broken event-gated design too (shrink-then-restore is identical; only the sub-coalescing-window timing differs). Per the repo's test-determinism and no-test-seam policies, a flaky or false-confidence test is worse than the documented invariant. The protection is the doc + the constant comment that points at it.

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

@cubic-dev-ai

cubic-dev-ai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Added docs/remote-tmux-sizing-timers.md (f92a5b5) documenting why the two remaining sizing timers are load-bearing — the redraw-kick gap (SIGWINCH is a pane PTY ioctl deferred behind tmux's own resize coalescing, which emits nothing observable, so no control event can gate the restore) and the size-send debounce (a rate limiter the ledger + dedup + reconnect reseed make correctness-neutral). Includes a by-hand exploration with its confounds spelled out.

On the question of a regression test: I attempted one and concluded against it, corroborated by an independent review. The kick's effect is a SIGWINCH below cmux's observable control surface — an e2e that observes it reliably requires the recorder to be the foreground process in the exact displayed mirrored pane and is inherently flaky around OS signal delivery, and a cheap command-shape unit test would pass for the broken event-gated design too (shrink-then-restore is identical; only the sub-coalescing-window timing differs). Per the repo's test-determinism and no-test-seam policies, a flaky or false-confidence test is worse than the documented invariant. The protection is the doc + the constant comment that points at it.

@codex review
...

@ejc3 I can't start this review because your workspace has reached its free monthly review limit. cubic has reviewed 241,631 of the 240,000 allowed lines of code this month. Reviews resume on 1 August 2026 (in 27 days). Paid plans include much higher monthly review limits. Upgrade now to resume reviews.

To help optimise your usage, you can tune cubic to get the most out of your usage limits:

Learn more →

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026 •

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd remote-tmux-sizing-timers.md
echo "---"
fd remote-tmux-sizing-timers.md --exec cat {}

Length of output: 5690


The image attached (labeled "PR description image: broken_master") shows the same tmux "sizing" mirrored-window debug UI as in the PR description: a dark terminal window titled "sizing" with tabs even3, nes... (nested, selected), row..., and deep. Three panes are visible on the right:

  • Top-left pane: PTY: 49x38, tmux: pane %3=49w win @1=98w client=98w, check: PTY = tmux pane (both axes) ✓, a ruler line, wrap/short legend, and a resize log with two timestamped entries (17:58:02 PTY 90x45... → 17:58:03 PTY 49x38...), ending with bottom row 38.
  • Top-right pane: PTY: 48x18, tmux: pane %4=48w win @1=98w client=98w, same check/ruler/legend, resize log (17:58:02 PTY 89x22... → 17:58:03 PTY 48x18...), ending with bottom row 18.
  • Bottom-right pane: PTY: 48x19, tmux: pane %5=48w win @1=98w client=98w, same structure, resize log ending 17:58:03 PTY 48x19..., ending with bottom row 19.

All three checks show ✓, and a red "THIS IS A DEV BUILD" badge is visible bottom-left outside the terminal content. This confirms PTY dimensions match tmux's pane/window/client width calculations across all three panes in this "nested" shape.

@ejc3, thanks for writing up docs/remote-tmux-sizing-timers.md — I read through it. The reasoning holds up:

  • Redraw-kick gap: the core argument (SIGWINCH is delivered via a pane PTY ioctl behind tmux's own resize-coalescing timer, which emits nothing on the control channel) is a sound explanation for why no control-visible event can safely gate the restore. Your point about a layout-publication-gated restore landing inside the coalescing window and collapsing to a net-zero change is a legitimate failure mode, not a hypothetical.
  • Size-send debounce: framing it as a rate limiter rather than a correctness dependency is well-supported by the ledger-write-before-defer + dedup + reconnect-reseed design — that combination genuinely does make the timer's firing (or non-firing) correctness-neutral.
  • No regression test decision: I agree with the tradeoff here. A test that reliably observes SIGWINCH delivery to the exact foreground process in the mirrored pane would be inherently flaky (OS signal coalescing, timing-sensitive), and a command-shape assertion (checking that shrink-then-restore calls are made) would pass identically for the broken event-gated design you tried and discarded — so it'd provide false confidence rather than real protection. Documenting the invariant at the point of maximum future risk (the constant declaration) is the more honest tradeoff, especially since you validated the current design's behavior via the earlier A/B run.

I'll factor this into review of the current diff.


✏️ Learnings added
Learnt from: ejc3
URL: https://github.com/manaflow-ai/cmux/pull/7315

Timestamp: 2026-07-05T18:58:38.073Z
Learning: In the cmux repo's remote-tmux mirror sizing feature (Sources/RemoteTmuxControlConnection.swift), two timers remain intentionally: `attachRedrawKickGapMs` (~350ms) and `clientSizeDebounceMs` (180ms). Neither has a regression test by design, documented in docs/remote-tmux-sizing-timers.md (added in commit f92a5b5e18). Rationale: the redraw-kick's effect (SIGWINCH from a pane PTY ioctl) is delivered below cmux's observable control surface and deferred behind tmux's own internal resize-coalescing timer which emits nothing on the control channel, so no control-visible event (e.g. `%layout-change`) can safely gate the restore — gating on one would land inside tmux's coalescing window and cause the shrink/restore pair to collapse to a net-zero change, silently reintroducing the bug the kick fixes. An e2e test observing the SIGWINCH reliably would require the recorder to be the foreground process in the exact displayed mirrored pane and would be flaky around OS signal delivery/coalescing; a cheap command-shape unit test would also pass identically for a previously-tried-and-discarded broken event-gated design (shrink-then-restore commands are identical; only sub-coalescing-window timing differs), so per the repo's test-determinism policy, only documentation-at-the-declaration is used instead of a test. The size-send debounce is a rate limiter, not a correctness dependency, because the size ledger (`lastClientSize`/`lastWindowSizes`) is written synchronously before any deferral, dedup makes late/duplicate sends idempotent, and reconnect reseed replays the ledger.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@ejc3

ejc3 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

The pre-merge panel re-expanded to 6 on this pass. Nothing in the code changed since it last showed 2 — f92a5b5e18 adds only docs/remote-tmux-sizing-timers.md plus a one-line comment pointing at it — so these are standing dispositions resurfacing rather than new issues. Mapping each:

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to set up container
ℹ️ 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".

@vercel

vercel Bot commented Jul 6, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jul 9, 2026 6:25am

ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 19, 2026
…anaflow-ai#7315)

manaflow-ai#7315 (exact feed-forward sizing / verified pane geometry) changed a mirror
window to publish only when its own paneRects reply lands, and those fetches are
enqueued incrementally — window @2's fetch appears after @1 resolves. The test
replied to a single snapshot of pending paneRects, so @2 never published, the
mirror built one tab instead of two, and the reorder + windowOrder assertions
failed (panelIds.count == 1, not 2).

The product is correct — the sibling mirror suites and the multiplex fuzzer build
multi-window mirrors green. This is a stale test setup: drain every paneRects
fetch (bounded loop) so both windows publish, then the two-tab reorder holds.

Red/green: on clean main the test fails with panelIds.count → 1 == 2; with the
drain it passes (1 test). Test-only change; no product code touched.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 19, 2026
…ndowReorderTests

manaflow-ai#7315 (verified pane geometry) and the pane-border-status work changed the
control-command stream the reorder/close state machine emits: a window-list
publish now also enqueues a per-window paneRects refetch, and closing a window
issues a border-status unsubscribe (a plain send(), kind .other). The suite
drives the connection with positional commandNumber:0 replies, so an undrained
follow-up sits at the FIFO head and swallows the reply meant for the reorder/
close list-windows recovery — the batch never recovers, the connection never
reconnects, and retained panes never release. All 33 assertions across 9 tests
failed on clean main for this one reason.

Fix is test-only: publish helpers drain every follow-up (paneRects + .other), a
drainLeadingOther helper clears them ahead of each correlated reply, and the
exact-pending assertions compare with those incidental follow-ups filtered out.
The product is correct — the multiplex fuzzer and the sibling mirror suites build
multi-window mirrors and reorder/close them green.

Red/green: clean main fails the suite with 33 issues; with this it passes 14/14.
No product code changed. Broke in manaflow-ai#7315.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 19, 2026
…anaflow-ai#7315)

manaflow-ai#7315 (exact feed-forward sizing / verified pane geometry) changed a mirror
window to publish only when its own paneRects reply lands, and those fetches are
enqueued incrementally — window @2's fetch appears after @1 resolves. The test
replied to a single snapshot of pending paneRects, so @2 never published, the
mirror built one tab instead of two, and the reorder + windowOrder assertions
failed (panelIds.count == 1, not 2).

The product is correct — the sibling mirror suites and the multiplex fuzzer build
multi-window mirrors green. This is a stale test setup: drain every paneRects
fetch (bounded loop) so both windows publish, then the two-tab reorder holds.

Red/green: on clean main the test fails with panelIds.count → 1 == 2; with the
drain it passes (1 test). Test-only change; no product code touched.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 19, 2026
…ndowReorderTests

manaflow-ai#7315 (verified pane geometry) and the pane-border-status work changed the
control-command stream the reorder/close state machine emits: a window-list
publish now also enqueues a per-window paneRects refetch, and closing a window
issues a border-status unsubscribe (a plain send(), kind .other). The suite
drives the connection with positional commandNumber:0 replies, so an undrained
follow-up sits at the FIFO head and swallows the reply meant for the reorder/
close list-windows recovery — the batch never recovers, the connection never
reconnects, and retained panes never release. All 33 assertions across 9 tests
failed on clean main for this one reason.

Fix is test-only: publish helpers drain every follow-up (paneRects + .other), a
drainLeadingOther helper clears them ahead of each correlated reply, and the
exact-pending assertions compare with those incidental follow-ups filtered out.
The product is correct — the multiplex fuzzer and the sibling mirror suites build
multi-window mirrors and reorder/close them green.

Red/green: clean main fails the suite with 33 issues; with this it passes 14/14.
No product code changed. Broke in manaflow-ai#7315.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 20, 2026
…anaflow-ai#7315)

manaflow-ai#7315 (exact feed-forward sizing / verified pane geometry) changed a mirror
window to publish only when its own paneRects reply lands, and those fetches are
enqueued incrementally — window @2's fetch appears after @1 resolves. The test
replied to a single snapshot of pending paneRects, so @2 never published, the
mirror built one tab instead of two, and the reorder + windowOrder assertions
failed (panelIds.count == 1, not 2).

The product is correct — the sibling mirror suites and the multiplex fuzzer build
multi-window mirrors green. This is a stale test setup: drain every paneRects
fetch (bounded loop) so both windows publish, then the two-tab reorder holds.

Red/green: on clean main the test fails with panelIds.count → 1 == 2; with the
drain it passes (1 test). Test-only change; no product code touched.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 20, 2026
…ndowReorderTests

manaflow-ai#7315 (verified pane geometry) and the pane-border-status work changed the
control-command stream the reorder/close state machine emits: a window-list
publish now also enqueues a per-window paneRects refetch, and closing a window
issues a border-status unsubscribe (a plain send(), kind .other). The suite
drives the connection with positional commandNumber:0 replies, so an undrained
follow-up sits at the FIFO head and swallows the reply meant for the reorder/
close list-windows recovery — the batch never recovers, the connection never
reconnects, and retained panes never release. All 33 assertions across 9 tests
failed on clean main for this one reason.

Fix is test-only: publish helpers drain every follow-up (paneRects + .other), a
drainLeadingOther helper clears them ahead of each correlated reply, and the
exact-pending assertions compare with those incidental follow-ups filtered out.
The product is correct — the multiplex fuzzer and the sibling mirror suites build
multi-window mirrors and reorder/close them green.

Red/green: clean main fails the suite with 33 issues; with this it passes 14/14.
No product code changed. Broke in manaflow-ai#7315.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 20, 2026
…anaflow-ai#7315)

manaflow-ai#7315 (exact feed-forward sizing / verified pane geometry) changed a mirror
window to publish only when its own paneRects reply lands, and those fetches are
enqueued incrementally — window @2's fetch appears after @1 resolves. The test
replied to a single snapshot of pending paneRects, so @2 never published, the
mirror built one tab instead of two, and the reorder + windowOrder assertions
failed (panelIds.count == 1, not 2).

The product is correct — the sibling mirror suites and the multiplex fuzzer build
multi-window mirrors green. This is a stale test setup: drain every paneRects
fetch (bounded loop) so both windows publish, then the two-tab reorder holds.

Red/green: on clean main the test fails with panelIds.count → 1 == 2; with the
drain it passes (1 test). Test-only change; no product code touched.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 20, 2026
…ndowReorderTests

manaflow-ai#7315 (verified pane geometry) and the pane-border-status work changed the
control-command stream the reorder/close state machine emits: a window-list
publish now also enqueues a per-window paneRects refetch, and closing a window
issues a border-status unsubscribe (a plain send(), kind .other). The suite
drives the connection with positional commandNumber:0 replies, so an undrained
follow-up sits at the FIFO head and swallows the reply meant for the reorder/
close list-windows recovery — the batch never recovers, the connection never
reconnects, and retained panes never release. All 33 assertions across 9 tests
failed on clean main for this one reason.

Fix is test-only: publish helpers drain every follow-up (paneRects + .other), a
drainLeadingOther helper clears them ahead of each correlated reply, and the
exact-pending assertions compare with those incidental follow-ups filtered out.
The product is correct — the multiplex fuzzer and the sibling mirror suites build
multi-window mirrors and reorder/close them green.

Red/green: clean main fails the suite with 33 issues; with this it passes 14/14.
No product code changed. Broke in manaflow-ai#7315.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 21, 2026
…anaflow-ai#7315)

manaflow-ai#7315 (exact feed-forward sizing / verified pane geometry) changed a mirror
window to publish only when its own paneRects reply lands, and those fetches are
enqueued incrementally — window @2's fetch appears after @1 resolves. The test
replied to a single snapshot of pending paneRects, so @2 never published, the
mirror built one tab instead of two, and the reorder + windowOrder assertions
failed (panelIds.count == 1, not 2).

The product is correct — the sibling mirror suites and the multiplex fuzzer build
multi-window mirrors green. This is a stale test setup: drain every paneRects
fetch (bounded loop) so both windows publish, then the two-tab reorder holds.

Red/green: on clean main the test fails with panelIds.count → 1 == 2; with the
drain it passes (1 test). Test-only change; no product code touched.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 21, 2026
…ndowReorderTests

manaflow-ai#7315 (verified pane geometry) and the pane-border-status work changed the
control-command stream the reorder/close state machine emits: a window-list
publish now also enqueues a per-window paneRects refetch, and closing a window
issues a border-status unsubscribe (a plain send(), kind .other). The suite
drives the connection with positional commandNumber:0 replies, so an undrained
follow-up sits at the FIFO head and swallows the reply meant for the reorder/
close list-windows recovery — the batch never recovers, the connection never
reconnects, and retained panes never release. All 33 assertions across 9 tests
failed on clean main for this one reason.

Fix is test-only: publish helpers drain every follow-up (paneRects + .other), a
drainLeadingOther helper clears them ahead of each correlated reply, and the
exact-pending assertions compare with those incidental follow-ups filtered out.
The product is correct — the multiplex fuzzer and the sibling mirror suites build
multi-window mirrors and reorder/close them green.

Red/green: clean main fails the suite with 33 issues; with this it passes 14/14.
No product code changed. Broke in manaflow-ai#7315.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 21, 2026
…anaflow-ai#7315)

manaflow-ai#7315 (exact feed-forward sizing / verified pane geometry) changed a mirror
window to publish only when its own paneRects reply lands, and those fetches are
enqueued incrementally — window @2's fetch appears after @1 resolves. The test
replied to a single snapshot of pending paneRects, so @2 never published, the
mirror built one tab instead of two, and the reorder + windowOrder assertions
failed (panelIds.count == 1, not 2).

The product is correct — the sibling mirror suites and the multiplex fuzzer build
multi-window mirrors green. This is a stale test setup: drain every paneRects
fetch (bounded loop) so both windows publish, then the two-tab reorder holds.

Red/green: on clean main the test fails with panelIds.count → 1 == 2; with the
drain it passes (1 test). Test-only change; no product code touched.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 21, 2026
…ndowReorderTests

manaflow-ai#7315 (verified pane geometry) and the pane-border-status work changed the
control-command stream the reorder/close state machine emits: a window-list
publish now also enqueues a per-window paneRects refetch, and closing a window
issues a border-status unsubscribe (a plain send(), kind .other). The suite
drives the connection with positional commandNumber:0 replies, so an undrained
follow-up sits at the FIFO head and swallows the reply meant for the reorder/
close list-windows recovery — the batch never recovers, the connection never
reconnects, and retained panes never release. All 33 assertions across 9 tests
failed on clean main for this one reason.

Fix is test-only: publish helpers drain every follow-up (paneRects + .other), a
drainLeadingOther helper clears them ahead of each correlated reply, and the
exact-pending assertions compare with those incidental follow-ups filtered out.
The product is correct — the multiplex fuzzer and the sibling mirror suites build
multi-window mirrors and reorder/close them green.

Red/green: clean main fails the suite with 33 issues; with this it passes 14/14.
No product code changed. Broke in manaflow-ai#7315.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 22, 2026
…anaflow-ai#7315)

manaflow-ai#7315 (exact feed-forward sizing / verified pane geometry) changed a mirror
window to publish only when its own paneRects reply lands, and those fetches are
enqueued incrementally — window @2's fetch appears after @1 resolves. The test
replied to a single snapshot of pending paneRects, so @2 never published, the
mirror built one tab instead of two, and the reorder + windowOrder assertions
failed (panelIds.count == 1, not 2).

The product is correct — the sibling mirror suites and the multiplex fuzzer build
multi-window mirrors green. This is a stale test setup: drain every paneRects
fetch (bounded loop) so both windows publish, then the two-tab reorder holds.

Red/green: on clean main the test fails with panelIds.count → 1 == 2; with the
drain it passes (1 test). Test-only change; no product code touched.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 22, 2026
…ndowReorderTests

manaflow-ai#7315 (verified pane geometry) and the pane-border-status work changed the
control-command stream the reorder/close state machine emits: a window-list
publish now also enqueues a per-window paneRects refetch, and closing a window
issues a border-status unsubscribe (a plain send(), kind .other). The suite
drives the connection with positional commandNumber:0 replies, so an undrained
follow-up sits at the FIFO head and swallows the reply meant for the reorder/
close list-windows recovery — the batch never recovers, the connection never
reconnects, and retained panes never release. All 33 assertions across 9 tests
failed on clean main for this one reason.

Fix is test-only: publish helpers drain every follow-up (paneRects + .other), a
drainLeadingOther helper clears them ahead of each correlated reply, and the
exact-pending assertions compare with those incidental follow-ups filtered out.
The product is correct — the multiplex fuzzer and the sibling mirror suites build
multi-window mirrors and reorder/close them green.

Red/green: clean main fails the suite with 33 issues; with this it passes 14/14.
No product code changed. Broke in manaflow-ai#7315.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 22, 2026
…anaflow-ai#7315)

manaflow-ai#7315 (exact feed-forward sizing / verified pane geometry) changed a mirror
window to publish only when its own paneRects reply lands, and those fetches are
enqueued incrementally — window @2's fetch appears after @1 resolves. The test
replied to a single snapshot of pending paneRects, so @2 never published, the
mirror built one tab instead of two, and the reorder + windowOrder assertions
failed (panelIds.count == 1, not 2).

The product is correct — the sibling mirror suites and the multiplex fuzzer build
multi-window mirrors green. This is a stale test setup: drain every paneRects
fetch (bounded loop) so both windows publish, then the two-tab reorder holds.

Red/green: on clean main the test fails with panelIds.count → 1 == 2; with the
drain it passes (1 test). Test-only change; no product code touched.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 22, 2026
…ndowReorderTests

manaflow-ai#7315 (verified pane geometry) and the pane-border-status work changed the
control-command stream the reorder/close state machine emits: a window-list
publish now also enqueues a per-window paneRects refetch, and closing a window
issues a border-status unsubscribe (a plain send(), kind .other). The suite
drives the connection with positional commandNumber:0 replies, so an undrained
follow-up sits at the FIFO head and swallows the reply meant for the reorder/
close list-windows recovery — the batch never recovers, the connection never
reconnects, and retained panes never release. All 33 assertions across 9 tests
failed on clean main for this one reason.

Fix is test-only: publish helpers drain every follow-up (paneRects + .other), a
drainLeadingOther helper clears them ahead of each correlated reply, and the
exact-pending assertions compare with those incidental follow-ups filtered out.
The product is correct — the multiplex fuzzer and the sibling mirror suites build
multi-window mirrors and reorder/close them green.

Red/green: clean main fails the suite with 33 issues; with this it passes 14/14.
No product code changed. Broke in manaflow-ai#7315.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 25, 2026
…anaflow-ai#7315)

manaflow-ai#7315 (exact feed-forward sizing / verified pane geometry) changed a mirror
window to publish only when its own paneRects reply lands, and those fetches are
enqueued incrementally — window @2's fetch appears after @1 resolves. The test
replied to a single snapshot of pending paneRects, so @2 never published, the
mirror built one tab instead of two, and the reorder + windowOrder assertions
failed (panelIds.count == 1, not 2).

The product is correct — the sibling mirror suites and the multiplex fuzzer build
multi-window mirrors green. This is a stale test setup: drain every paneRects
fetch (bounded loop) so both windows publish, then the two-tab reorder holds.

Red/green: on clean main the test fails with panelIds.count → 1 == 2; with the
drain it passes (1 test). Test-only change; no product code touched.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 25, 2026
…ndowReorderTests

manaflow-ai#7315 (verified pane geometry) and the pane-border-status work changed the
control-command stream the reorder/close state machine emits: a window-list
publish now also enqueues a per-window paneRects refetch, and closing a window
issues a border-status unsubscribe (a plain send(), kind .other). The suite
drives the connection with positional commandNumber:0 replies, so an undrained
follow-up sits at the FIFO head and swallows the reply meant for the reorder/
close list-windows recovery — the batch never recovers, the connection never
reconnects, and retained panes never release. All 33 assertions across 9 tests
failed on clean main for this one reason.

Fix is test-only: publish helpers drain every follow-up (paneRects + .other), a
drainLeadingOther helper clears them ahead of each correlated reply, and the
exact-pending assertions compare with those incidental follow-ups filtered out.
The product is correct — the multiplex fuzzer and the sibling mirror suites build
multi-window mirrors and reorder/close them green.

Red/green: clean main fails the suite with 33 issues; with this it passes 14/14.
No product code changed. Broke in manaflow-ai#7315.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 25, 2026
…anaflow-ai#7315)

manaflow-ai#7315 (exact feed-forward sizing / verified pane geometry) changed a mirror
window to publish only when its own paneRects reply lands, and those fetches are
enqueued incrementally — window @2's fetch appears after @1 resolves. The test
replied to a single snapshot of pending paneRects, so @2 never published, the
mirror built one tab instead of two, and the reorder + windowOrder assertions
failed (panelIds.count == 1, not 2).

The product is correct — the sibling mirror suites and the multiplex fuzzer build
multi-window mirrors green. This is a stale test setup: drain every paneRects
fetch (bounded loop) so both windows publish, then the two-tab reorder holds.

Red/green: on clean main the test fails with panelIds.count → 1 == 2; with the
drain it passes (1 test). Test-only change; no product code touched.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 25, 2026
…ndowReorderTests

manaflow-ai#7315 (verified pane geometry) and the pane-border-status work changed the
control-command stream the reorder/close state machine emits: a window-list
publish now also enqueues a per-window paneRects refetch, and closing a window
issues a border-status unsubscribe (a plain send(), kind .other). The suite
drives the connection with positional commandNumber:0 replies, so an undrained
follow-up sits at the FIFO head and swallows the reply meant for the reorder/
close list-windows recovery — the batch never recovers, the connection never
reconnects, and retained panes never release. All 33 assertions across 9 tests
failed on clean main for this one reason.

Fix is test-only: publish helpers drain every follow-up (paneRects + .other), a
drainLeadingOther helper clears them ahead of each correlated reply, and the
exact-pending assertions compare with those incidental follow-ups filtered out.
The product is correct — the multiplex fuzzer and the sibling mirror suites build
multi-window mirrors and reorder/close them green.

Red/green: clean main fails the suite with 33 issues; with this it passes 14/14.
No product code changed. Broke in manaflow-ai#7315.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 25, 2026
…anaflow-ai#7315)

manaflow-ai#7315 (exact feed-forward sizing / verified pane geometry) changed a mirror
window to publish only when its own paneRects reply lands, and those fetches are
enqueued incrementally — window @2's fetch appears after @1 resolves. The test
replied to a single snapshot of pending paneRects, so @2 never published, the
mirror built one tab instead of two, and the reorder + windowOrder assertions
failed (panelIds.count == 1, not 2).

The product is correct — the sibling mirror suites and the multiplex fuzzer build
multi-window mirrors green. This is a stale test setup: drain every paneRects
fetch (bounded loop) so both windows publish, then the two-tab reorder holds.

Red/green: on clean main the test fails with panelIds.count → 1 == 2; with the
drain it passes (1 test). Test-only change; no product code touched.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 25, 2026
…ndowReorderTests

manaflow-ai#7315 (verified pane geometry) and the pane-border-status work changed the
control-command stream the reorder/close state machine emits: a window-list
publish now also enqueues a per-window paneRects refetch, and closing a window
issues a border-status unsubscribe (a plain send(), kind .other). The suite
drives the connection with positional commandNumber:0 replies, so an undrained
follow-up sits at the FIFO head and swallows the reply meant for the reorder/
close list-windows recovery — the batch never recovers, the connection never
reconnects, and retained panes never release. All 33 assertions across 9 tests
failed on clean main for this one reason.

Fix is test-only: publish helpers drain every follow-up (paneRects + .other), a
drainLeadingOther helper clears them ahead of each correlated reply, and the
exact-pending assertions compare with those incidental follow-ups filtered out.
The product is correct — the multiplex fuzzer and the sibling mirror suites build
multi-window mirrors and reorder/close them green.

Red/green: clean main fails the suite with 33 issues; with this it passes 14/14.
No product code changed. Broke in manaflow-ai#7315.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 31, 2026
…anaflow-ai#7315)

manaflow-ai#7315 (exact feed-forward sizing / verified pane geometry) changed a mirror
window to publish only when its own paneRects reply lands, and those fetches are
enqueued incrementally — window @2's fetch appears after @1 resolves. The test
replied to a single snapshot of pending paneRects, so @2 never published, the
mirror built one tab instead of two, and the reorder + windowOrder assertions
failed (panelIds.count == 1, not 2).

The product is correct — the sibling mirror suites and the multiplex fuzzer build
multi-window mirrors green. This is a stale test setup: drain every paneRects
fetch (bounded loop) so both windows publish, then the two-tab reorder holds.

Red/green: on clean main the test fails with panelIds.count → 1 == 2; with the
drain it passes (1 test). Test-only change; no product code touched.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 31, 2026
…ndowReorderTests

manaflow-ai#7315 (verified pane geometry) and the pane-border-status work changed the
control-command stream the reorder/close state machine emits: a window-list
publish now also enqueues a per-window paneRects refetch, and closing a window
issues a border-status unsubscribe (a plain send(), kind .other). The suite
drives the connection with positional commandNumber:0 replies, so an undrained
follow-up sits at the FIFO head and swallows the reply meant for the reorder/
close list-windows recovery — the batch never recovers, the connection never
reconnects, and retained panes never release. All 33 assertions across 9 tests
failed on clean main for this one reason.

Fix is test-only: publish helpers drain every follow-up (paneRects + .other), a
drainLeadingOther helper clears them ahead of each correlated reply, and the
exact-pending assertions compare with those incidental follow-ups filtered out.
The product is correct — the multiplex fuzzer and the sibling mirror suites build
multi-window mirrors and reorder/close them green.

Red/green: clean main fails the suite with 33 issues; with this it passes 14/14.
No product code changed. Broke in manaflow-ai#7315.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 31, 2026
…anaflow-ai#7315)

manaflow-ai#7315 (exact feed-forward sizing / verified pane geometry) changed a mirror
window to publish only when its own paneRects reply lands, and those fetches are
enqueued incrementally — window @2's fetch appears after @1 resolves. The test
replied to a single snapshot of pending paneRects, so @2 never published, the
mirror built one tab instead of two, and the reorder + windowOrder assertions
failed (panelIds.count == 1, not 2).

The product is correct — the sibling mirror suites and the multiplex fuzzer build
multi-window mirrors green. This is a stale test setup: drain every paneRects
fetch (bounded loop) so both windows publish, then the two-tab reorder holds.

Red/green: on clean main the test fails with panelIds.count → 1 == 2; with the
drain it passes (1 test). Test-only change; no product code touched.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 31, 2026
…ndowReorderTests

manaflow-ai#7315 (verified pane geometry) and the pane-border-status work changed the
control-command stream the reorder/close state machine emits: a window-list
publish now also enqueues a per-window paneRects refetch, and closing a window
issues a border-status unsubscribe (a plain send(), kind .other). The suite
drives the connection with positional commandNumber:0 replies, so an undrained
follow-up sits at the FIFO head and swallows the reply meant for the reorder/
close list-windows recovery — the batch never recovers, the connection never
reconnects, and retained panes never release. All 33 assertions across 9 tests
failed on clean main for this one reason.

Fix is test-only: publish helpers drain every follow-up (paneRects + .other), a
drainLeadingOther helper clears them ahead of each correlated reply, and the
exact-pending assertions compare with those incidental follow-ups filtered out.
The product is correct — the multiplex fuzzer and the sibling mirror suites build
multi-window mirrors and reorder/close them green.

Red/green: clean main fails the suite with 33 issues; with this it passes 14/14.
No product code changed. Broke in manaflow-ai#7315.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 31, 2026
…anaflow-ai#7315)

manaflow-ai#7315 (exact feed-forward sizing / verified pane geometry) changed a mirror
window to publish only when its own paneRects reply lands, and those fetches are
enqueued incrementally — window @2's fetch appears after @1 resolves. The test
replied to a single snapshot of pending paneRects, so @2 never published, the
mirror built one tab instead of two, and the reorder + windowOrder assertions
failed (panelIds.count == 1, not 2).

The product is correct — the sibling mirror suites and the multiplex fuzzer build
multi-window mirrors green. This is a stale test setup: drain every paneRects
fetch (bounded loop) so both windows publish, then the two-tab reorder holds.

Red/green: on clean main the test fails with panelIds.count → 1 == 2; with the
drain it passes (1 test). Test-only change; no product code touched.
ejc3 added a commit to ejc3/cmux that referenced this pull request Jul 31, 2026
…ndowReorderTests

manaflow-ai#7315 (verified pane geometry) and the pane-border-status work changed the
control-command stream the reorder/close state machine emits: a window-list
publish now also enqueues a per-window paneRects refetch, and closing a window
issues a border-status unsubscribe (a plain send(), kind .other). The suite
drives the connection with positional commandNumber:0 replies, so an undrained
follow-up sits at the FIFO head and swallows the reply meant for the reorder/
close list-windows recovery — the batch never recovers, the connection never
reconnects, and retained panes never release. All 33 assertions across 9 tests
failed on clean main for this one reason.

Fix is test-only: publish helpers drain every follow-up (paneRects + .other), a
drainLeadingOther helper clears them ahead of each correlated reply, and the
exact-pending assertions compare with those incidental follow-ups filtered out.
The product is correct — the multiplex fuzzer and the sibling mirror suites build
multi-window mirrors and reorder/close them green.

Red/green: clean main fails the suite with 33 issues; with this it passes 14/14.
No product code changed. Broke in manaflow-ai#7315.
austinywang added a commit that referenced this pull request Aug 4, 2026
…eting suites (#8427)

* tests: drain all paneRects in programmaticMirrorReorder… (broken by #7315)

#7315 (exact feed-forward sizing / verified pane geometry) changed a mirror
window to publish only when its own paneRects reply lands, and those fetches are
enqueued incrementally — window @2's fetch appears after @1 resolves. The test
replied to a single snapshot of pending paneRects, so @2 never published, the
mirror built one tab instead of two, and the reorder + windowOrder assertions
failed (panelIds.count == 1, not 2).

The product is correct — the sibling mirror suites and the multiplex fuzzer build
multi-window mirrors green. This is a stale test setup: drain every paneRects
fetch (bounded loop) so both windows publish, then the two-tab reorder holds.

Red/green: on clean main the test fails with panelIds.count → 1 == 2; with the
drain it passes (1 test). Test-only change; no product code touched.

* tests: drain post-#7315 follow-up commands in RemoteTmuxWindowReorderTests

#7315 (verified pane geometry) and the pane-border-status work changed the
control-command stream the reorder/close state machine emits: a window-list
publish now also enqueues a per-window paneRects refetch, and closing a window
issues a border-status unsubscribe (a plain send(), kind .other). The suite
drives the connection with positional commandNumber:0 replies, so an undrained
follow-up sits at the FIFO head and swallows the reply meant for the reorder/
close list-windows recovery — the batch never recovers, the connection never
reconnects, and retained panes never release. All 33 assertions across 9 tests
failed on clean main for this one reason.

Fix is test-only: publish helpers drain every follow-up (paneRects + .other), a
drainLeadingOther helper clears them ahead of each correlated reply, and the
exact-pending assertions compare with those incidental follow-ups filtered out.
The product is correct — the multiplex fuzzer and the sibling mirror suites build
multi-window mirrors and reorder/close them green.

Red/green: clean main fails the suite with 33 issues; with this it passes 14/14.
No product code changed. Broke in #7315.

* tests: address review findings on the mirror/reorder test fixups

From the CodeRabbit/Greptile pass:

- drainLeadingOther replied to every paneRects with a hardcoded `%0`; a
  re-published @2/@3 needs its own pane id (the `windowId * 10` convention
  publishWindows stages), or its pending layout can't publish.
- reorderPending filtered incidentals globally, so a paneRects landing BETWEEN
  two list-windows (an ordering anomaly) would be elided and the equality
  assertion would still pass. Trim only TRAILING incidental follow-ups; an
  interleaved one now survives and fails the assertion.
- The mirror-targeting rects drain iterated a stale snapshot while each reply
  consumes the FIFO head, so an incidental preceding a fetch could mis-correlate
  pane data. Drain strictly from the head and stop at the first correlated command.

* tests: stop the reorder drains from swallowing correlated commands

Both drain helpers replied to whatever sat at the FIFO head, so a `listWindows` or
`windowReorder` arriving early was consumed with an empty reply and its later
positional result mis-correlated — the failure the drains exist to prevent. Each now
answers only the incidental follow-ups (`paneRects`, `.other`) and stops at the first
correlated command. `drainLeadingOther` also gains the bounded guard the other drains
already had.

---------

Co-authored-by: ejc3 <ejc3@users.noreply.github.com>
Co-authored-by: Austin Wang <austinwang115@gmail.com>

This branch was successfully deployed

1 active deployment
Preview – cmux — 0f6ac5ad Deployed Jul 9, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants