Skip to content

fix(motion): Cmd-B and Cmd-D move every pane on one timeline - #36677

Merged
cmux-bot[bot] merged 17 commits into
feat-cmux-nextfrom
lane/one-motion-system
Nov 5, 2026
Merged

cmux-bot[bot] merged 17 commits into
feat-cmux-nextfrom
lane/one-motion-system

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Cmd-B, Cmd-D and the two together now move every pane on one timeline: the sidebar, the main pane, the new right pane and the content inside them start in the same frame and land in the same frame.

Leo, 2026-11-05:

the command B and then the command D, the things, the right pane shifts and then the main pane waits for the right pane to shift before it shifts. Like, why are you waiting? If we had a proper animation system, everything moves as one, as one. React, I swear, like, there's been so many— actually, like, can't we literally just use React? How much of this pane manipulation thing is literally just, yes, you can just do it in React, because how many animation libraries are there? Like, there's infinite amounts of prior work that we can look to. So if you just— if we just make it better, it should be better. Like, we shouldn't have these weird animations. Like, that's why I'm saying, like, it gets desynchronized. It's weird.

Cause (measured)

scripts/measure/split-spring.sh --suite=one-motion on a fleet Mac (macOS 27.0.1, 60 Hz) records every motion beside an agent chat holding 5000 rows. For each pane it notes the first frame and the last frame that moved its frame or its content, along with frame drops and main-thread work, as the motion runs.

At base (c9ead45), on Cmd-D (split-right), both pane frames moved from 33 ms to 300 ms. The chat's content stayed at its old size the whole time and changed once, at 300 ms, when the spring landed. The chat waited 267 ms for the new pane. On Cmd-B then Cmd-D, the sidebar ran 50 → 190 ms on its own timer, and the new pane started at 217 ms, chasing the edge while the sidebar slid.

Three things kept the panes apart:

  1. The chat waited for the landing. On a split, close or zoom spring, an agent chat's content kept its start width (the larger of its start and rest size) until the spring landed, and only then reflowed (PaneHostView.contentFrame; the provider holds .agent content only, cmux-next: prevent agent pane blink during native split motion #26118). On Cmd-D the right pane slid in, and then the chat reflowed. A terminal in the same pane hears its final grid at the spring's start, so the chat was the one that waited.
  2. Two clocks. The sidebar slid through a constraint animator(), which AppKit runs on a timer of its own, while the panes step on the window's MotionClock (display link). Their frames landed in different passes.
  3. Panes chased the sidebar. With a split still springing (Cmd-B, then Cmd-D within its slide), every sidebar frame resized the screen, and ScreenContentView only moved each spring's target. The panes then chased the moving edge, a spring's lag behind it.

Fix: one motion driver

  • One clock. TimedProgress (CmuxNextDesign) is a timed transition stepped on the window's MotionClock, the clock every pane, divider and tab spring already steps on. The sidebar's width and its clip's reach ride one progress on it, so a slide frame and the springs' frame step together and show in one commit. The tokens (appear and disappear durations) and the ease-out curve (cmux-motion's cubic-bezier) are unchanged, Reduce Motion and the sidebar motion switch still land it in one frame, and a toggle mid-slide turns around from the width on screen.
  • Additive retargeting. A viewport resize under a running spring moves each frame and its target together (AnimatedFrame.rebase): the pane follows the resize in that frame, and only what the split had left to travel still springs, at the velocity it had. A resize at rest still snaps; a layout change (split, preset, equalize) still springs as before.
  • The target size up front. A held chat takes its rest size on the spring's first frame, pinned at the edge that stays, and keeps it to the landing. It still lays out once per spring and never on every frame (Leo, 2026-10-20), but that one layout is at the start. A retarget keeps cmux-next: prevent agent pane blink during native split motion #26118's rule: it grows the page once to a larger rest, and keeps the larger page through a smaller one until the landing. The keyboard-resize hold (P0 RESIZE KEYS K3: a keyboard resize holds pane content through its spring and lays it out once after the landing #32635, fix(layout): a keyboard resize press presents nothing in its own turn; its first frame lays out once (FRAME OWNER S1B) #34380) is unchanged.

Can't we just use React?

Not for the panes, but we can use what React-world motion libraries learned, and this PR does.

What is native and what is web in a window today:

Part What it is
Left sidebar AppKit (SidebarContainerView)
Pane layout: splits, columns, dividers, springs AppKit (CmuxNextLayout: ScreenContentView, PaneHostView) on the window's MotionClock
Right sidebar AppKit (RightSidebarView)
Terminal Ghostty, a Metal surface (TerminalSurfaceView)
Browser pane a Chromium page in a child window (CEF)
Agent chat a React page (webviews/) in WebKit or Chromium

A pane that holds a terminal or a Chromium page can't be a React element: the Metal surface and the page's child window are native views that the layout must place every frame. So React could only lay out the chat, and the problem is how the chat moves relative to the native panes. A React layout library would add a third clock, not remove one.

What we took instead:

  • One shared frame loop for all motion. Framer Motion runs every animation, layout animations included, on one frameloop (read, update, render per animation frame), and react-spring runs every spring on one requestAnimationFrame loop. MotionClock was already ours for the springs; the sidebar now joins it.
  • Additive retargeting (interruptible from where it shows). Core Animation's additive animations (WWDC 2014, "Building Interruptible and Responsive Interactions") and Framer Motion's layout projection keep an animation's remaining delta when its target moves, rather than restarting toward the new target. AnimatedFrame.rebase does that for a viewport resize.
  • Measure the final layout first (FLIP). FLIP (Paul Lewis, "FLIP Your Animations", 2015) lays out at the Last state before animating from the First. A held chat now takes its Last size up front instead of laying out at the end.

Before and after

Start and end of each mover, ms after the key (off-screen pages left out):

Motion Mover Before After
Cmd-D (split-right) main pane frame 33 → 300 50 → 333
main pane content (the chat) 300 → 300 (one jump at the landing) 33 (one layout at the start)
new pane frame 72 → 300 50 → 333
Cmd-B then Cmd-D sidebar 50 → 190 33 → 150
main pane frame 50 → 450 33 → 450
main pane content 50 → 450 33 → 150 (lays out with the sidebar, then holds)
new pane frame 217 → 450 167 → 450

On Cmd-D, the chat now lays out once, at its final size, in the spring's first frame. Then it rides the frames, which move together. Before, it waited for the landing.

Per motion, the spread is the largest gap between two movers' first frames (start) and last frames (end); 0 means everything moved in the same frames. Content lag is how long a pane's content kept changing after its frame landed.

Metric Before After Delta Harness
Cmd-D, start spread between movers 267 ms 17 ms −250 ms one-motion suite, fleet Macs, 60 Hz, 5000-row chat
Cmd-D, missed frames 3 1 −2 same
Cmd-D, worst frame 78 ms 33 ms −45 ms same
Cmd-D, hangs 1 0 −1 same
Cmd-D, main thread p95 per frame 7.2 ms 6.7 ms −0.5 ms same
Cmd-B then Cmd-D, start spread 167 ms 133 ms −34 ms same
Cmd-B then Cmd-D, missed frames 3 1 −2 same
Cmd-B then Cmd-D, worst frame 50 ms 33 ms −17 ms same
Cmd-B then Cmd-D, main thread p95 9.4 ms 9.8 ms +0.4 ms same
Cmd-B (hide), missed frames 2 1 −1 same
Cmd-B (hide), main thread p95 13.8 ms 8.7 ms −5.1 ms same
Right sidebar open, missed frames 1 0 −1 same

Cmd-D beside a long chat drops fewer frames than at base (3 to 1), so the chat's reflow cost doesn't add drops. The before and after runs landed on different Macs of the same pool; each is one run of three passes. Where the "end spread" metric grows on Cmd-D (0 to 300 ms), that is the chat's single layout moving from the landing to the start: the content's only change is now in the first frame.

Tests

Red first: each fix's test is committed before it.

Fleet runs: scripts/ci/package-test-lane.sh suites on a display Mac (CMUX_TEST_REQUIRE_GUI=1).

Test At its red commit At the head
ViewportResizeMotionTests (aSidebarSlideFrameReachesTheSplittingPanesInTheSameFrame) fails at a9ad577: a pane is 20 pt from where the sidebar's frame put it passes
PaneMotionContentHoldTests (rest size on the first frame, once per spring) fails at cd5b2ae: the chat page is 384 pt wide on the first frame, not its rest 184 passes
SidebarSlideClockTests (the slide steps on the window's motion clock) run pending; result in a comment passes

At the head, every pane-motion and sidebar suite this touches passes:

Three existing tests encoded the old rule, which held the chat's content until the landing. They now check the new one, a single layout at the spring's start (c214134): FrameOwnerS1HoldPathsTests, PaneHeldContentLandingTests and PaneMotionContentHoldTests. SidebarToggleSettlementTests caught two real regressions of the clock move along the way, both fixed here: a completion one frame early, and a reversal that went one frame past the turn.

Observed / Not run

Observed:

  • The before and after one-motion bench runs above, on fleet Macs, with their frame captures and per-mover traces.
  • The fleet suite runs above.

Not run:

  • A capture tour of the app (cmux-ci capture). The bench's own screen recordings are in its artifact.
  • The 120 Hz Macs: the bench ran at 60 Hz.
  • More than one bench run per side. The before and after runs above landed on different Macs of one pool; a pair pinned to one Mac is running, and its numbers will replace these.

Follow-ups (same principles)

Prior art read (prior-art MUST ACK): #26118, #24153, #24342, #28894, #29894, #30064, #32635, #33607, #33725, #33874, #34170, #34195, #34247, #34380, #35161, #35841, #35940 and the rest of the list; #18840 (open, lucasr1b) is the classic sidebar glide and frozen, a design reference only. Owner: PL-motion-core (Layout motion, PaneHostView); the sidebar spec design/sidebar.md (no look change).

Changelog

Fixed: Toggling the sidebar (Cmd-B) and splitting (Cmd-D) move every pane together; an agent chat no longer waits for a new pane to slide in before it reflows.

🤖 Generated with Claude Code
Agent session: agent session

teamleaderleo and others added 13 commits November 5, 2026 08:31
…ether beside an agent chat

Leo 2026-11-05: "everything moves as one". scripts/cmux-next/one-motion-trace.py
(split-spring.sh --suite=one-motion) traces the sidebar, every pane's frame and
laid-out content, terminals and pages on each display frame (debug.motion
trace_*), optionally records each motion (record_*), and reports when each
mover started and ended, the spreads between them, and content lag behind its
pane's landing, with an SVG progress timeline per motion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-00bbcfe801cee26c35db
…ame frame (red)

Leo 2026-11-05 (Cmd-B then Cmd-D): "the right pane shifts and then the main
pane waits for the right pane to shift before it shifts". A viewport resize
under a running split only retargets each spring, so the panes chase the
sidebar's moving edge instead of riding it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-00bbcfe801cee26c35db
…one motion)

The sidebar's slide resizes the screen on every frame. With a split still
springing, each resize only moved the springs' targets, so the panes chased the
moving edge a spring's lag behind it. Now each frame on a spring moves with its
target (AnimatedFrame.rebase, an additive animation: the frame keeps what it had
left to travel and its velocity), so the panes follow the slide in its frame and
only the split's own travel springs. A resize at rest still snaps; a layout
change (preset, equalize, split) still springs as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-00bbcfe801cee26c35db
…'s first frame (red)

Leo 2026-11-05: "the main pane waits for the right pane to shift before it
shifts. Like, why are you waiting?". An agent chat on a split, close or zoom
spring still lays out once (Leo 2026-10-20: never per frame), but at its rest
size on the first frame, pinned at the edge that stays, not at the landing; a
retarget lays out once more at its new rest, as a terminal's grid does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-00bbcfe801cee26c35db
…first frame, not at the landing

Leo 2026-11-05: "the main pane waits for the right pane to shift before it
shifts. Like, why are you waiting?". An agent chat on a split, close or zoom
spring kept its start width (the larger of start and rest) until the landing,
so a shrinking chat reflowed only after the new pane had slid in. It now takes
its rest size on the spring's first frame, pinned at the edge that stays, as a
terminal hears its final grid at the start: still one layout per spring (Leo
2026-10-20), and a retarget lays out once more at its new rest. Nothing changes
at the landing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-00bbcfe801cee26c35db
Leo 2026-11-05: "If we had a proper animation system, everything moves as one,
as one." The sidebar slid on an AppKit animation timer of its own while the
panes' springs step on the window's MotionClock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-00bbcfe801cee26c35db
…anes (one motion)

Cmd-B slid the sidebar's width (and its clip's reach) through a constraint
animator, an AppKit timer of its own, while the panes beside it step on the
window's MotionClock. The two landed in different passes and drifted, worst with
a split still springing (Cmd-B then Cmd-D). TimedProgress (CmuxNextDesign) is a
timed transition stepped on MotionClock: the sidebar's width and clip ride one
progress on it, so each slide frame and the springs' frame step together and show
in one commit. Same tokens (appear/disappear durations), same ease-out curve
(cmux-motion's cubic-bezier), Reduce Motion and the sidebar motion switch still
land it in one frame, and a toggle mid-slide turns around from the width on
screen. The fade leg (opacity) is unchanged.

Agent-Session: https://cmux.com/deeplink/agent?id=aid-00bbcfe801cee26c35db
Sidebar-Look: no visual change (same tokens and curve, stepped on the window's motion clock)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a 5000-row chat

Untraced repeats of each motion (--frame-rounds, default 3) report missed frames,
frame interval p95/max, main-thread work per frame and hangs (debug.frames,
debug.main_cpu, debug.hangs), so the chat's one layout at a split's start is
judged against today's on the same harness (a long transcript, --rows 5000).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-00bbcfe801cee26c35db
The rebase moved each frame with its target, and then settleCarried put
every pane back at the x it showed before the pass: a resize reconcile ran
with animated: true, which counted as an animated width change (M8) and
carried. The new pane of a split then chased its moved target by the
resize (ViewportResizeMotionTests: 20 pt left over after a 40 pt slide
frame). A rebasing pass is not carried.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-00bbcfe801cee26c35db
Under the rest-size-up-front rule (392b4cc): the takeover test now expects
the key spring's rest once and the split's once, and nothing at the
landing; the landing test's precondition is the rest size before the
landing, not the start size; the shrink test compares the pin with the
clip's leading edge (the 8 pt padding is in the clip's own frame).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-00bbcfe801cee26c35db
TimedProgress ran its completion in the step that applied the end, before
that frame was committed, so the sidebar reported its slide settled while
the compositor still showed the frame before: SidebarToggleSettlementTests
(hide, no reversal) read a drawn width 2 pt off its target on its first
settled sample, at 6cfc894. AppKit's animator ran the completion once the
last frame showed; the end now waits one frame for its completion the same
way. motionSettle (an end at once) still completes at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-00bbcfe801cee26c35db
SidebarToggleSettlementTests (startShown true, reverse true) failed at e30ae3c:
when the reversal came after the slide had stepped in the same frame, the new
leg started from the stepped constraint, one frame past the laid-out width on
screen, so the sidebar went one frame further before it turned around. The new
leg now starts from the laid-out width, with the clip's reach at the same
point of the old leg.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-00bbcfe801cee26c35db
Sidebar-Look: no visual change (a slide that turns around turns from the frame on screen; same tokens and curve)
…screen at once

SidebarToggleSettlementTests (startShown false, reverse true) still failed at
86e49d2: the old slide had stepped the constraints earlier in the frame, and
the new leg started from the shown width but left them there, so that frame's
layout drew one step past the turn. The new leg now sets them to its start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-00bbcfe801cee26c35db
Sidebar-Look: no visual change (a slide that turns around turns from the frame on screen; same tokens and curve)
@teamleaderleo teamleaderleo added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

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

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

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

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr36677 -f ref=lane/one-motion-system -f platform=macos -f backend_mode=local

Shipped in:

… as #26118 did

The held chat now lays out once at its rest size on the spring's first frame
(392b4cc). Its retarget path had also dropped #26118's rule ('retain held size
across pane retargets', 'keep chat paint stable across pane interruptions'):
a close interrupted by an early reopen reflowed the chat at each new target.
A retarget now keeps the largest size this motion laid the chat out at, and a
larger rest grows it once; the spring's start is unchanged.

PaneMotionContentHoldTests: aRetargetToASmallerRestKeepsTheLargerPageUntilTheLanding
(was aRetargetedSpringLaysOutOnceAtTheNewRest, which encoded the dropped rule).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-00bbcfe801cee26c35db
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

For awareness (lawrencecchen, FRAME OWNER; #26118): this PR changes pane-motion code those shaped.

The suites around these, including the FRAME OWNER S1 and S3 suites, pass at the head. The numbers are in #36677.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex, router-review): SHIP at 92c7522

Reviewer: codex gpt-6.1-sol xhigh, fast tier.

Follow-ups: 1. They land as checklist items in this PR's one follow-up tracking issue in this repo (Follow-ups: #N ..., label follow-up; add to an open one before filing, never one issue per finding), linking this receipt; the lane that opened the PR fixes them forward.

SHIP at 92c7522

Visual verdict: no images; Leo tries the build.

  1. Finding:
    • [P2] [follow-up] [category:correctness] [finding:F1] [outcome:pending] A sidebar side/style or pane-reach change during an active width slide leaves widthLeg pointing at the old clip constraint. apply()/applySide() resets the new edge, then stepWidth() keeps writing the old edge, so the clip can remain incorrectly constrained after the slide (SidebarContainerView+WidthMotion.swift:46, SidebarContainerView.swift:378).

Checked the focused Python tests and motion static check; both pass.

Follow-ups:

  • [P2] [follow-up] [category:correctness] [finding:F1] [outcome:pending] Retarget or cancel/rebuild the width leg when its active clip edge or reach changes mid-slide.

@cmux-bot cmux-bot 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.

cmux-bot approves 92c7522: an independent review passed this exact head (router-review receipt by teamleaderleo). A push needs a new review; a later BLOCK at this head dismisses this approval.

teamleaderleo and others added 3 commits November 5, 2026 16:09
…ro (red)

Router review of #36677 at 92c7522 (F1): the slide kept writing the clip edge it
started on after applySide moved the reach to the other edge.

Sidebar-Look: no visual change (test only)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-00bbcfe801cee26c35db
…a side or reach change

setClipReach (applySide, a style switch, a reach change in apply) now points a
running slide's reach leg at the edge it set, at its new reach, so the slide's
next frames never write the edge it started on.

Sidebar-Look: no visual change (a side switch mid-slide ends with the same clip as one at rest)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-00bbcfe801cee26c35db
GitHub kept #36677's head at 92c7522 after 4808b49 was pushed (no pull_request
event fired). No change to the tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-00bbcfe801cee26c35db
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex, router-review): SHIP at 1e45fde

Reviewer: codex gpt-6-astra high, fast tier.

Follow-ups: 1. They land as checklist items in this PR's one follow-up tracking issue in this repo (Follow-ups: #N ..., label follow-up; add to an open one before filing, never one issue per finding), linking this receipt; the lane that opened the PR fixes them forward.

SHIP at 1e45fde

Visual verdict: no images; Leo tries the build

  1. [P2] [follow-up] [category:ux] [finding:F1] [outcome:pending] No after screenshots were supplied, so the visual result could not be independently compared. Python motion-series tests and script compilation pass.

Follow-ups:

  • [P2] [follow-up] [category:ux] [finding:F1] [outcome:pending] No after screenshots were supplied for visual comparison.

@cmux-bot cmux-bot 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.

cmux-bot approves 1e45fde: an independent review passed this exact head (router-review receipt by teamleaderleo). A push needs a new review; a later BLOCK at this head dismisses this approval.

@cmux-bot
cmux-bot Bot merged commit a8bcdff into feat-cmux-next Nov 5, 2026
177 of 185 checks passed
@cmux-bot
cmux-bot Bot deleted the lane/one-motion-system branch November 5, 2026 17:52
lawrencecchen added a commit that referenced this pull request Nov 6, 2026
…its rest as it starts (#37079)

* test(layout): the retarget test reopens the close on its way, not at the new rest's place

aRetargetToASmallerRestKeepsTheLargerPageUntilTheLanding has failed on every tip Swift suite run
since #36677 (a8bcdff, first red at f1965d7, run 42089396624; the PR's own Swift suite was
skipped): it retargeted the close back to 200 pt while the host still stood at 200 pt, its place and
size at the new rest. A host there is at rest and holds nothing (contentFrame), so the chat laid out
at 184 pt. An early reopen comes while the pane is on its way to the wider rest, as the open-then-close
case of openCloseAndInterruptedPaneMotionNeverShowsBlankOrReflowsPerFrame already does (300 pt): the
test now presents that frame first, and keeps every expectation (384 pt through the retarget and its
frames, 184 pt at the landing).

* test(layout): a split taking a key hold over lays held content out once, at the split's rest, on its first frame (red)

#36677 changed this test to expect a layout at the key spring's rest, which
RESIZE KEYS K3 forbids for held content (the key hold keeps the start size;
#36478 removed it for terminals only). The test now expects no layout during the
key hold, one at the split's rest on the split's first frame, and none at the landing.

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

* fix(layout): a split taking a key hold over lays held content out at its rest as it starts

After endKeyResizeHold the held size was still the key hold's start size, and the
split's announceRest kept the larger of it and the split's rest (#26118), so a chat
that shrinks kept its start width until the landing. The first rest after a
takeover now replaces the held size; later retargets keep the larger.

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

---------

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

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 1e45fde6 Deployed Nov 5, 2026 by teamleaderleo via namespace autofix #15468
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant