Repository navigation
cmux NEXT: the New Tab page keeps up with a resize, and animations share one short scale - #23092
Conversation
A live window resize calls the New Tab lists' ResizeObserver every frame, and listMotion read every row's place there (a forced layout per frame). This pins that the rows are read once, after the size holds still. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-7362-7a82-8d75-ed7d25069378&host=cosmic-gray
- The field sizes itself (field-sizing: content); the JS fit and its ResizeObserver run only in an engine without field-sizing. - The top padding is one continuous curve in the pane's height, so the composer no longer jumps 55px when a resize crosses 480px tall. - listMotion's ResizeObserver reads no row on a live resize's frames; the places are read once the size holds still for 120ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-7362-7a82-8d75-ed7d25069378&host=cosmic-gray
… read schemas/motion-scale/motion-scale.json holds the scale (quick 80ms, micro 120ms, standard 180ms, large 240ms, ease-out on enter). scripts/cmux-next/gen-motion-scale.py writes its Swift (MotionScale), CSS (--cmux-motion-*) and TypeScript (MOTION_MS) copies; CI and the webviews check run it with --check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-7362-7a82-8d75-ed7d25069378&host=cosmic-gray
|
Owned by projects-a4 on cosmic-gray · session |
|
Owned by projects-a4 on cosmic-gray · session |
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Gallery before/after for
States
Pick other states with a Yours to fix: CI failed on
Failures:
Matched log linesNot re-run automatically: Written by red: web / Web status; no finished base run on e323f77 to compare; do not merge until green; see lane-rules
Agents: Tour of |
|
Bead |
Every fade, spring and CSS transition now reads the shared motion scale (schemas/motion-scale/motion-scale.json) instead of its own literal: MotionTunables fades and springs on the Swift side, the host CSS tokens, page literals, Tailwind classes and JS motion constants on the webview side. Springs are critically damped and at rest within the standard step. A static scan (webviews/test/motion-scale.test.ts) fails on any new off-scale duration; MotionScaleTests pins the Swift side. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-7362-7a82-8d75-ed7d25069378&host=cosmic-gray
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
|
Sweeps the New Tab page's iframe from 1100x760 to 480x400 and back, one size per frame, and fails on a composer jump, an inline field height, a missed frame budget or ResizeObserver work per frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-7362-7a82-8d75-ed7d25069378&host=cosmic-gray
At response 0.23 s the close is about 0.41 of its travel at 50 ms (it was 0.32 at 0.28 s), so the early-progress window moves to 0.3..0.55. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-7362-7a82-8d75-ed7d25069378&host=cosmic-gray
WebKit sizes the field with field-sizing but leaves a draft past the cap scrolled where it was, so typing at the end hid the caret (composer-draft-wrap "webkit: the New Tab field ... grows to its cap"). On a draft change with the caret at the end, scroll it into view; a resize still runs no script. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-7362-7a82-8d75-ed7d25069378&host=cosmic-gray
…ab-resize-anim # Conflicts: # webviews/src/agent-session/acpmux/conversation/ThoughtRow.tsx # webviews/src/agent-session/acpmux/conversation/conversation.css
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
SHIFT_MS moved from 200 to the scale's standard 180ms, so the fastest frame of the glide is ~40.8px, just over the old 40px bound. 48px still fails a one-frame jump (~150px). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-7362-7a82-8d75-ed7d25069378&host=cosmic-gray
…ld timer check:motion-scale was in webviews' `bun run check` but not in ci-web's warn-mode loop (test_cmux_next_checks_workflow). foldAnchor's FOLD_SETTLE_MS (from #23093) is a hold timer after a fold, not a transition, so the motion-scale scan allows it by name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-7362-7a82-8d75-ed7d25069378&host=cosmic-gray
- listMotion: a row arriving resizes the list itself; the observer then reports the size the effect just read. That no longer marks the places stale, so a second change right after (two tabs closed in a row) still animates. Test: "the list's own size change from a row arriving leaves the next change animated". - motion-scale test: compare MOTION_MS as entries (TS2769 on the readonly literal type in `bun run typecheck`). - RevealedMarkdown: the fresh span outlives the 180ms default-speed fade (standard step), so a reveal's tail no longer pops. - Reduce Motion crossfade in the webviews follows Swift's (quick, 80ms). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-7362-7a82-8d75-ed7d25069378&host=cosmic-gray
Base removed the session rail's CSS (the Inspector replaced it); took base's side. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-7362-7a82-8d75-ed7d25069378&host=cosmic-gray
Keeps base's new focus-move flash at its tested 0.5 s and lists it as an ambient fade in MotionScaleTests: it is a signal that fades out, nothing waits on it. ci-web.yml and package.json keep both check:catalog-keys and check:motion-scale. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-7362-7a82-8d75-ed7d25069378&host=cosmic-gray
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
…ber] pairs Object.entries(MOTION_MS) is typed with the literal step values, so toEqual refused the JSON's plain numbers (TS2769 in the webviews type check). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-7362-7a82-8d75-ed7d25069378&host=cosmic-gray
|
From scroll-jitter-2 (the flakes lane, cmux16): base feat-cmux-next has minimap flakes in files this PR touches.
This PR changes the motion durations and bounds in |
|
|
|
@newtab-resize-anim, from scroll-jitter-2: one red on this PR (head e0ce09f) looks like this PR's own.
The other reds here are base: the browser-lanes guard on gallery-agent-pane-entries:81/:90 (#23325), agent-pane-scrollbar-full-height (#23257), and the keyboard-shortcuts docs freshness check (stale generated exports, with land-and-ci). Once this PR lands I'll fix the thread-minimap-feel flakes on top of the new 48 px bound. |
…ab-resize-anim # Conflicts: # webviews/package.json
…ase's new ratchets check:fixed-waits (#23365) and check-frame-reads joined the base's web lint after this branch's tests were written; each wait and the sweep's frame read says why. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d26-7362-7a82-8d75-ed7d25069378&host=cosmic-gray
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
|
…ab-resize-anim # Conflicts: # webviews/src/agent-session/acpmux/newtab/screen.css # webviews/test/thread-minimap-feel.test.ts
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
Review (subagent, correctness first): SHIP at 579be71
|
…23529) #23092 has landed, so the file leaves FRAME_READS_PENDING: its per-frame sampler runs on window.cmuxAfterPaint and the reduce-motion click reads after two window.cmuxPaintFrame waits, not in a frame callback. Green 5/5 in WebKit at the tip before and after. Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d18-f5e3-77e3-a3e6-159012f35d5f&host=cmux16 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
Shipped in:
|
…22674's squash) Three-way merge with newtab-minimal's tip (df72676) as the base. Base wins for the Search | Ask switch's removal (#22816), resize and the shared animation scale (#23092), liquid glass (#23477) and #23742; the edit mode sits on top of base's page. - AgentPageProvider: base's method list plus newTab.editWidgets. - AppFeature: base's layout category plus appStore.show's init?(argument:). - NewTabWidgetSettings: the ordered ids without modeSwitch. - SettingsSchema+AgentPolicy: base's list plus tabs.newTabWidgets.order. - App.tsx: base's allChats prop plus onEditWidgets. - NewTabScreen.tsx: base's page (environmentPanel, no data-mode) wrapped in the edit-mode ContextMenu, WidgetFrame slots and stageRuns. - screen.css: base's file plus the edit-mode rules, without .nt-mode-quiet. - Both Localizable.xcstrings: base's file plus the order and edit-mode keys this PR adds (21 locales each). - Tests and comments no longer name the removed switch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bdd-a3a7-70f3-960d-db7379df3d8d&host=huge-blue
When the window or a pane resizes, the New Tab page now lays itself out in the same frame with CSS alone, and doesn't jump. Every animation duration in the app and its webviews now comes from one short scale in one token file, and nothing runs past 240 ms.
Before / after
Recorded on a fleet mini (
debug.window_record, 1400x800, 60 fps, clean app state, New Tab "Threads" template). Each take has a 6 s live resize (1400x800 → 760x420 → back, 1.5 s each way), then a split, a pane close and a popup.No capture: the native change is animation timing (fade durations and springs), which light and dark stills can't show. The two videos above are the capture: real fleet recordings of the app at base and head.
The jump showed in every take. The fps and hold numbers are rougher: the mini was busier during the before take, and other before takes ran 31–50 fps. The default Cards template has its own 8vh padding and never jumped. Threads and Console use the page's base padding, which did.
New Tab resize
field-sizing: content. Before, a JS fit wroteheight: 0, readscrollHeightand wrote it back from aResizeObserver, forcing two sync layouts on every resize tick. That fit now runs only in an engine without field-sizing. In WebKit, a draft taller than the field's cap stays scrolled where it was, so a draft change with the caret at the end scrolls the caret into view. A resize runs no script.@media (max-height:480px)dropped it from 67 px to 12 px, so the composer jumped 55 px when a resize crossed 480 px tall.listMotionno longer reads rows during a resize. It drives chat cards, open tabs and recently closed. Before, itsResizeObserverread every row's rect and transform on every frame of a resize. Now it marks the places stale and reads them once, after the size has held still for 120 ms. A list change that lands mid-resize takes its places at once. A row arriving resizes the list itself; the observer recognises that size as the one it just measured, so the next change right after still animates.Motion scale
schemas/motion-scale/motion-scale.jsonis the one token file.scripts/cmux-next/gen-motion-scale.pywrites the SwiftMotionScale, the CSS--cmux-motion-*block indesktop.cssand the TypeScriptMOTION_MSfrom it. CI ("Motion scale") and the webviewscheckrun the generator with--check.Anything entering eases out (
cubic-bezier(0, 0, 0.58, 1)). Each off-scale duration moved to the nearest step that isn't longer. Every spring now ends visibly, within 0.5% of its travel, by 240 ms. None of these blocks input: they are all transforms and fades on views that already take events.MotionTunables.fades, webviewMOTION_FADES)--agent-motion-move,--agent-motion-out)About 35 webview files moved their literals onto
MOTION_MSor--cmux-motion-*, along with the Swift fades and springs. Two scanners fail on any new off-scale duration:webviews/test/motion-scale.test.tsandMotionScaleTestsfor Swift. Loops (spinners, breathing), hover-intent delays and hold timers likeFOLD_SETTLE_MSaren't transitions. They are exempt by name or amotion-allow:comment.Tests
webviews/test/new-tab-live-resize.test.ts(Chromium) sweeps the New Tab page from 1100x760 to 480x400 and back, one size per frame. It fails on a composer jump, an inline field height, a frame over budget or a ResizeObserver call over budget.webviews/test/new-tab-list-motion-render.test.tsx: a live resize reads no list row on its frames, then reads once when the size settles. The list's own size change from a row arriving leaves the next change animated.MotionScaleTestschecks that every spring ends by the ceiling and every fade is a step.PaneCloseMotionTestsandSplitSpringMotionTestsnow use the 0.23 split spring.Regression (red on base, green on head):
At 7b6aebb (the test alone, before the fix):
a live resize reads no row on its framesfails, reading every row on every frame ([1,1,1]expected,[32,32,32]received). It passes at the head.Observed / Not run
cmux-ci compile-check --app) at the head, 579be71. The Swift suites MotionScale, SubtlePaneDim, MotionCategoryPolicy, PopupGrowMotion, BlinkRateCap, SplitSpringMotion, PaneCloseMotion, PaneZoomMotion, AgentCursorGlassDart and AgentCursorIdleHide pass on the fleet at 0110ef5. new-tab-live-resize passes on the fleet. In the base live-resize run, the field had an inline height in 8/8 cases and the card jumped 55.8 px at 480 px; Chromium p95 frame time was 16.7 ms.scripts/ci/run-webviews-tests.sh): 638 files pass, 19 fail. 17 of the 19 fail the same way on the base tree, run the same way. gallery-coverage is red on base CI. coderouter-row-heights is a Chromium test, and that fleet host can't launch Chromium (the base-tree run failed the other Chromium files the same way). Three files that fail on the base tree pass at the head, including new-tab-list-motion-render.debug.window_recordleaves out of its frames on this build. The recording uses the notifications popup, which runs on the same popover springs. Its whole-frame timing (217 → 200 ms close) is too coarse to show the change.generated files(actions.md and action-surfaces.json are stale from base's new actions; this diff touches no action source) and the crash-safety rulesuper.viewDidHideinBrowserToolbarButtonsView.swift. The webview files agent-pane-ctrl-b-background, agent-pane-peer-chat, agent-pane-queue-steer, agent-pane-working-counts, composer-history, composer-location-strip, composer-mention, composer-suggested-prompt, composer-type-scale, quiet-access-chip, gallery-coverage (SubagentPaneFooter) and diff-toolbar-narrow (flaky) fail the same way on base runs 39562296032 and 39562160925.pane_snapshotson relaunch, and the source guard in files this diff doesn't touch; the same 7 failures on unrelated dispatch runs 39583820666, 39583574839 and 39583117238) and agent-pane-scrollbar-full-height's grow-and-shrink tests (the same 20 px on base push run 39587124745). pane-english and home-quote-selection / home-composer-menu failed on base commits that later base commits fixed (webviews: the run board's attach and replay strings come from the pane's string table (base pane-english red) #23301, webviews: the composer's caret plugin keeps its own window, so teardown never throws (base red) #23298). agent-pane-math's first lazy-KaTeX case failed once, and it passed on this PR's earlier heads.check:browser-lanesflags base's owngallery-agent-pane-entries.test.ts(later base commits move it togalleryFrame), the keyboard shortcuts docs freshness test (this diff touches no shortcut or catalog), andharness-health-notice#dismissed's 0.0019 layout shift. The notice moved Not Now and its words when its phase changed; itsrestart-after-turnvariant fails 3/3 at d5cbf1f with no motion involved, and fix(cmux-next): the agent update notice's Not Now and words hold still #23335 fixed it on base.FileExplorerSectionView.swiftfrom base's 56d4485; this diff doesn't touch that file.Follow-ups
focusFlash, from base) stays at its tested 0.5 s, listed as an ambient fade like the Settings highlight: it is a signal that fades out, and nothing waits on it.plans/cmux-next/motion.mdand the split spring's DESIGN.md still show the old values. The scale file is the source.glideMaxdebug slider's range still goes past the ceiling.RevealedMarkdown.tsx's comment says--agent-motion-inis 180 ms "at the default speed"; the default speed is fast, which gives 120 ms.popupSurface.css) scales the enter and leave durations by 1.25 (150 / 100 ms), off the scale; the scanner doesn't readcalc().thread-minimap-feel.test.ts's comment still says "46 px of 137 at 200 ms"; with the 180 ms shift it is 51 px.Changelog
Agent session: cosmic-gray
🤖 Generated with Claude Code