J428: animations are slow; every UI motion is ease-out and settles in ≤ 300 ms #471

Open
opened 2026-11-09 10:41:38 +00:00 by kayg · 10 comments
Owner

Owner, 2026-11-09, verbatim: "when did animations become so slow in the mockup and production? did you forget emil's rulue? https://emilkowal.ski/skill https://animations.dev/". They quoted: "Use ease-out for anything entering or leaving the screen. Keep UI animations under 300ms." and "ease-in ... starts slow and speeds up at the end, so almost nothing moves in the first few frames, right when you're waiting for a response. [ease-out] covers most of the distance right away."

Branch motion-fast-ease-out, base 54a9da2a8 (origin/dev).

Plan (estimate ~4 h)

  1. Measure (≈1.5 h): a Playwright frame recorder on a production build at 1440×900 and 393×852 touch, about 12 interactions (popover, ⌘K by pointer, sheet, pill→card, split, close, move, view switch, camera flight, sidebar, Add host Continue/Back, full screen). Per interaction: t50, t90, settled, curve shape. Builds: before the no-overshoot change (b5099e3be, the dev merge just before 816f9ab3e), current production, and this branch. Plus an exact analytic table of every Spring call site (the spring is closed-form, so these numbers are exact).
  2. Fix (≈1.5 h): a Spring released from rest becomes a fixed-duration ease-out tween on the product curve cubic-bezier(0.23,1,0.32,1). The physical spring stays only for fling (finger velocity). Every duration ≤ 0.3 s (pops 0.18–0.24 s). FLIP and CSS outliers move to the same curve and tokens. Keyboard stays instant (#427), and reduced motion stays opacity-only.
  3. Gate (≈0.5 h): apps/tracer/test/motion-speed.test.ts next to no-overshoot.test.ts in verify fast. It fails on any transition or spring over 300 ms and on any ease-in or ease-in-out enter/exit, with a commented allow-list (spinners, ambient loops). Written red first. Plus a TASTE row quoting the owner.
  4. Apply the same fix to the #146 mockup (appwin-mock-j209) if it has its own springs.
**Owner, 2026-11-09, verbatim:** "when did animations become so slow in the mockup and production? did you forget emil's rulue? https://emilkowal.ski/skill https://animations.dev/". They quoted: "Use ease-out for anything entering or leaving the screen. Keep UI animations under 300ms." and "ease-in ... starts slow and speeds up at the end, so almost nothing moves in the first few frames, right when you're waiting for a response. [ease-out] covers most of the distance right away." Branch `motion-fast-ease-out`, base 54a9da2a8 (origin/dev). ## Plan (estimate ~4 h) 1. **Measure** (≈1.5 h): a Playwright frame recorder on a production build at 1440×900 and 393×852 touch, about 12 interactions (popover, ⌘K by pointer, sheet, pill→card, split, close, move, view switch, camera flight, sidebar, Add host Continue/Back, full screen). Per interaction: t50, t90, settled, curve shape. Builds: before the no-overshoot change (b5099e3be, the dev merge just before 816f9ab3e), current production, and this branch. Plus an exact analytic table of every `Spring` call site (the spring is closed-form, so these numbers are exact). 2. **Fix** (≈1.5 h): a `Spring` released from rest becomes a fixed-duration ease-out tween on the product curve `cubic-bezier(0.23,1,0.32,1)`. The physical spring stays only for `fling` (finger velocity). Every duration ≤ 0.3 s (pops 0.18–0.24 s). FLIP and CSS outliers move to the same curve and tokens. Keyboard stays instant (#427), and reduced motion stays opacity-only. 3. **Gate** (≈0.5 h): `apps/tracer/test/motion-speed.test.ts` next to `no-overshoot.test.ts` in `verify fast`. It fails on any transition or spring over 300 ms and on any ease-in or ease-in-out enter/exit, with a commented allow-list (spinners, ambient loops). Written red first. Plus a TASTE row quoting the owner. 4. Apply the same fix to the #146 mockup (`appwin-mock-j209`) if it has its own springs.
Author
Owner

Started: branch motion-fast-ease-out, base 54a9da2a8. Worktree on code, ~/Developer/Worktrees/infraternal-j428.

Finding 1: when it became slow (exact, analytic)

Spring is closed-form, so these numbers are exact for one unit of travel. Columns: t50 / t90 / settled (last time > 1 % away), in ms, then the share of the distance covered in the first 60 Hz frame.

  • before 11-02: b5099e3be, the dev merge right before 816f9ab3e "no elastic overshoot" (#308). Underdamped, with the bounce each site had then.
  • now / prod: critically damped, ω = 2π/(1.2·vd).
  • after: this branch, a fixed-duration ease-out tween on cubic-bezier(0.23,1,0.32,1) (the product curve).
site before 11-02 t50/t90/settled ms, frame-1, overshoot now (prod) t50/t90/settled, frame-1 after t50/t90/settled, frame-1
layout flip.ts (close/split/reflow) (0.5s, bounce 0.22 → 0.28s) 120 / 192 / 1035, 1 %, +22 % 161 / 372 / 634, 1 % 37 / 102 / 178, 25 %
lifecycle.ts FLIP (0.38s → 0.28s) 122 / 283 / 482, 2 %, +0 % 122 / 283 / 482, 2 % 37 / 102 / 178, 25 %
lifecycle.ts open (0.5s, bounce 0.28 → 0.28s) 117 / 182 / 1095, 1 %, +28 % 161 / 372 / 634, 1 % 37 / 102 / 178, 25 %
world.ts POS (view switch) (0.5s, bounce 0.2 → 0.28s) 122 / 195 / 839, 1 %, +20 % 161 / 372 / 634, 1 % 37 / 102 / 178, 25 %
float.on-demand.ts (0.85s, bounce 0.05 → 0.28s) 231 / 423 / 1065, 1 %, +5 % 273 / 632 / 1078, 0 % 37 / 102 / 178, 25 %
columns.ts scroll (0.4s, bounce 0.1 → 0.24s) 104 / 178 / 476, 2 %, +10 % 129 / 298 / 508, 2 % 32 / 87 / 153, 29 %
toolbar/pill.ts settle (0.42s, bounce 0.22 → 0.26s) 101 / 161 / 869, 2 %, +22 % 135 / 313 / 533, 2 % 35 / 94 / 166, 27 %
move.on-demand.ts land (0.42s, bounce 0.3 → 0.28s) 98 / 151 / 924, 2 %, +30 % 135 / 313 / 533, 2 % 37 / 102 / 178, 25 %
core-screen.ts row (0.38s → 0.28s) 122 / 283 / 482, 2 %, +0 % 122 / 283 / 482, 2 % 37 / 102 / 178, 25 %
core-screen.ts slide (0.36s → 0.26s) 116 / 268 / 457, 3 %, +0 % 116 / 268 / 457, 3 % 35 / 94 / 166, 27 %
session-ui (0.34s, bounce 0.12 → 0.28s) 87 / 147 / 555, 3 %, +12 % 109 / 253 / 432, 3 % 37 / 102 / 178, 25 %
windows-proto (mockup) (0.42s → 0.28s) 135 / 313 / 533, 2 %, +0 % 135 / 313 / 533, 2 % 37 / 102 / 178, 25 %
pane-stepper height (0.36s → 0.28s) 116 / 268 / 457, 3 %, +0 % 116 / 268 / 457, 3 % 37 / 102 / 178, 25 %
pane-stepper pane (0.34s → 0.26s) 109 / 253 / 432, 3 %, +0 % 109 / 253 / 432, 3 % 35 / 94 / 166, 27 %
window-resize (full screen) (0.42s → 0.28s) 135 / 313 / 533, 2 %, +0 % 135 / 313 / 533, 2 % 37 / 102 / 178, 25 %
pill-morph (pill→card) (0.4s → 0.22s) 129 / 298 / 508, 2 %, +0 % 129 / 298 / 508, 2 % 29 / 80 / 140, 31 %
shortcuts-view (0.4s → 0.22s) 129 / 298 / 508, 2 %, +0 % 129 / 298 / 508, 2 % 29 / 80 / 140, 31 %
row-menu (0.32s → 0.24s) 103 / 238 / 406, 3 %, +0 % 103 / 238 / 406, 3 % 32 / 87 / 153, 29 %
wizard / add-host step (0.32s, bounce 0.14 → 0.26s) 81 / 134 / 533, 3 %, +14 % 103 / 238 / 406, 3 % 35 / 94 / 166, 27 %
icon-morph / lock-toggle (0.26s → 0.2s) 84 / 194 / 330, 5 %, +0 % 84 / 194 / 330, 5 % 27 / 73 / 128, 34 %
camera FLIGHT (0.16s → 0.26s) 52 / 119 / 203, 10 %, +0 % 52 / 119 / 203, 10 % 35 / 94 / 166, 27 %
camera KEY_FLIGHT (0.08s → 0.12s) 26 / 60 / 102, 30 %, +0 % 26 / 60 / 102, 30 % 16 / 44 / 77, 52 %

Reading: before 11-02, springs reached 90 % in about 150–195 ms but then overshot by 20–30 % and took 0.8–1.1 s to settle. The no-overshoot change (2026-11-02 21:11 UTC, 816f9ab3e) kept ω and set ζ = 1, which roughly doubled t90 to 250–630 ms. Both versions start from rest with zero velocity: 1–3 % of the distance in the first frame, an S-curve (slow start). That is the "ease-in" feel the owner quotes. The tween covers 25–34 % in frame 1, reaches 90 % at 73–102 ms and is within 1 % by 128–178 ms.

Script: ~/.cache/codex-scratch/j428/tools/analytic.ts (on code). Browser frame recordings follow.

Started: branch `motion-fast-ease-out`, base `54a9da2a8`. Worktree on code, `~/Developer/Worktrees/infraternal-j428`. ## Finding 1: when it became slow (exact, analytic) `Spring` is closed-form, so these numbers are exact for one unit of travel. Columns: t50 / t90 / settled (last time > 1 % away), in ms, then the share of the distance covered in the first 60 Hz frame. - **before 11-02**: b5099e3be, the dev merge right before `816f9ab3e` "no elastic overshoot" (#308). Underdamped, with the bounce each site had then. - **now / prod**: critically damped, ω = 2π/(1.2·vd). - **after**: this branch, a fixed-duration ease-out tween on `cubic-bezier(0.23,1,0.32,1)` (the product curve). | site | before 11-02 t50/t90/settled ms, frame-1, overshoot | now (prod) t50/t90/settled, frame-1 | after t50/t90/settled, frame-1 | |---|---|---|---| | layout flip.ts (close/split/reflow) (0.5s, bounce 0.22 → 0.28s) | 120 / 192 / 1035, 1 %, +22 % | 161 / 372 / 634, 1 % | 37 / 102 / 178, 25 % | | lifecycle.ts FLIP (0.38s → 0.28s) | 122 / 283 / 482, 2 %, +0 % | 122 / 283 / 482, 2 % | 37 / 102 / 178, 25 % | | lifecycle.ts open (0.5s, bounce 0.28 → 0.28s) | 117 / 182 / 1095, 1 %, +28 % | 161 / 372 / 634, 1 % | 37 / 102 / 178, 25 % | | world.ts POS (view switch) (0.5s, bounce 0.2 → 0.28s) | 122 / 195 / 839, 1 %, +20 % | 161 / 372 / 634, 1 % | 37 / 102 / 178, 25 % | | float.on-demand.ts (0.85s, bounce 0.05 → 0.28s) | 231 / 423 / 1065, 1 %, +5 % | 273 / 632 / 1078, 0 % | 37 / 102 / 178, 25 % | | columns.ts scroll (0.4s, bounce 0.1 → 0.24s) | 104 / 178 / 476, 2 %, +10 % | 129 / 298 / 508, 2 % | 32 / 87 / 153, 29 % | | toolbar/pill.ts settle (0.42s, bounce 0.22 → 0.26s) | 101 / 161 / 869, 2 %, +22 % | 135 / 313 / 533, 2 % | 35 / 94 / 166, 27 % | | move.on-demand.ts land (0.42s, bounce 0.3 → 0.28s) | 98 / 151 / 924, 2 %, +30 % | 135 / 313 / 533, 2 % | 37 / 102 / 178, 25 % | | core-screen.ts row (0.38s → 0.28s) | 122 / 283 / 482, 2 %, +0 % | 122 / 283 / 482, 2 % | 37 / 102 / 178, 25 % | | core-screen.ts slide (0.36s → 0.26s) | 116 / 268 / 457, 3 %, +0 % | 116 / 268 / 457, 3 % | 35 / 94 / 166, 27 % | | session-ui (0.34s, bounce 0.12 → 0.28s) | 87 / 147 / 555, 3 %, +12 % | 109 / 253 / 432, 3 % | 37 / 102 / 178, 25 % | | windows-proto (mockup) (0.42s → 0.28s) | 135 / 313 / 533, 2 %, +0 % | 135 / 313 / 533, 2 % | 37 / 102 / 178, 25 % | | pane-stepper height (0.36s → 0.28s) | 116 / 268 / 457, 3 %, +0 % | 116 / 268 / 457, 3 % | 37 / 102 / 178, 25 % | | pane-stepper pane (0.34s → 0.26s) | 109 / 253 / 432, 3 %, +0 % | 109 / 253 / 432, 3 % | 35 / 94 / 166, 27 % | | window-resize (full screen) (0.42s → 0.28s) | 135 / 313 / 533, 2 %, +0 % | 135 / 313 / 533, 2 % | 37 / 102 / 178, 25 % | | pill-morph (pill→card) (0.4s → 0.22s) | 129 / 298 / 508, 2 %, +0 % | 129 / 298 / 508, 2 % | 29 / 80 / 140, 31 % | | shortcuts-view (0.4s → 0.22s) | 129 / 298 / 508, 2 %, +0 % | 129 / 298 / 508, 2 % | 29 / 80 / 140, 31 % | | row-menu (0.32s → 0.24s) | 103 / 238 / 406, 3 %, +0 % | 103 / 238 / 406, 3 % | 32 / 87 / 153, 29 % | | wizard / add-host step (0.32s, bounce 0.14 → 0.26s) | 81 / 134 / 533, 3 %, +14 % | 103 / 238 / 406, 3 % | 35 / 94 / 166, 27 % | | icon-morph / lock-toggle (0.26s → 0.2s) | 84 / 194 / 330, 5 %, +0 % | 84 / 194 / 330, 5 % | 27 / 73 / 128, 34 % | | camera FLIGHT (0.16s → 0.26s) | 52 / 119 / 203, 10 %, +0 % | 52 / 119 / 203, 10 % | 35 / 94 / 166, 27 % | | camera KEY_FLIGHT (0.08s → 0.12s) | 26 / 60 / 102, 30 %, +0 % | 26 / 60 / 102, 30 % | 16 / 44 / 77, 52 % | **Reading:** before 11-02, springs reached 90 % in about 150–195 ms but then overshot by 20–30 % and took 0.8–1.1 s to settle. The no-overshoot change (2026-11-02 21:11 UTC, 816f9ab3e) kept ω and set ζ = 1, which **roughly doubled** t90 to 250–630 ms. Both versions start from rest with zero velocity: **1–3 % of the distance in the first frame**, an S-curve (slow start). That is the "ease-in" feel the owner quotes. The tween covers 25–34 % in frame 1, reaches 90 % at 73–102 ms and is within 1 % by 128–178 ms. Script: `~/.cache/codex-scratch/j428/tools/analytic.ts` (on code). Browser frame recordings follow.
Author
Owner

Progress (head fb8f6ebfc, pushed):

  • e524c2576 Spring.set plays the product ease-out curve for exactly visualDuration; physics only for fling (finger velocity) and raw springs.
  • ab627cd53 every spring and reflow ≤ 0.28 s (pops 0.2–0.24, spatial 0.26–0.28, camera 0.26, key flight 0.12); FLIP uses EASE_OUT directly.
  • ba41d7258 39 opacity fades from ease/linear to ease-out; Add host 340/420 ms → --t-panel; auth height 320 → 260; pulse 360 → 240; session frame fade 520 → 240; welcome caption delay 900 → 60 ms; three WAAPI fades with ease/no easing → EASE_OUT; spinners 720/800/900 → 600 ms per turn.
  • 92f27c3e2 gate apps/tracer/test/motion-speed.test.ts in verify fast + TASTE row "Fast, ease-out, under 300 ms".
  • fb8f6ebfc D345 doc comments the diff row asked for.

Red first (gate against unmodified origin/dev 54a9da2a8, dev-02): 2 pass, 6 fail, 80 listed offences, for example:

(fail) no spring lasts more than 300 ms
(fail) no CSS transition or animation lasts more than 300 ms, delay included
(fail) nothing that moves or fades in CSS starts slowly
(fail) no Web Animation lasts more than 300 ms or starts slowly
(fail) the FLIP reflow curve is the product's ease-out and lasts its duration
(fail) a Spring from rest is ease-out: a fifth of the way in the first frame, half by a third of its time, done at its time
+   "apps/tracer/auth.css:283 animation: auth-welcome-caption 240ms 900ms var(--ease-out) both",
+   "apps/tracer/addhost.css:157 transition: grid-template-rows 340ms var(--ease-drawer, ...)",
+   "apps/tracer/src/canvas/windows/session-ui.on-demand.ts:258 .animate([{ opacity: 0.9 }, { opacity: 0 }], { duration: 520, easing: \"ease-out\" })",

Green on the branch: motion-speed, no-overshoot and spring tests 20 pass, 0 fail. src/canvas + motion tests: 569 pass. The 8 failures are all packages/ui/src/ui-scale.test.ts, which isn't in the gate's list and also fails on unmodified base in the same invocation (14 fail there).

Finding (⌘K by pointer): the toolbar's Search item opens the palette through a synthetic ⌘K keydown (toolbar/row.ts:25), so it's already instant: nothing to slow down.

#146 mockup (appwin-mock-j209, base 37f060a45, 2026-11-04): no springs of its own. It uses the shared packages/ui/src/spring.ts, which already has the 11-02 critical damping, so it's slow for the same reason. Its own CSS (4 transitions) is already ease-out at ≤ 200 ms. No change on that branch: it inherits the fix when it rebases onto dev after this merges.

verify quick passed on dev-02. verify fast is queued on compute01 (position 9). The browser frame recordings on three builds (before b5099e3be / prod 54a9da2a8 / this branch) are queued on dev-02.

Progress (head `fb8f6ebfc`, pushed): - `e524c2576` `Spring.set` plays the product ease-out curve for exactly `visualDuration`; physics only for `fling` (finger velocity) and raw springs. - `ab627cd53` every spring and reflow ≤ 0.28 s (pops 0.2–0.24, spatial 0.26–0.28, camera 0.26, key flight 0.12); FLIP uses `EASE_OUT` directly. - `ba41d7258` 39 opacity fades from `ease`/`linear` to ease-out; Add host 340/420 ms → `--t-panel`; auth height 320 → 260; pulse 360 → 240; session frame fade 520 → 240; welcome caption delay 900 → 60 ms; three WAAPI fades with `ease`/no easing → `EASE_OUT`; spinners 720/800/900 → 600 ms per turn. - `92f27c3e2` gate `apps/tracer/test/motion-speed.test.ts` in `verify fast` + TASTE row "Fast, ease-out, under 300 ms". - `fb8f6ebfc` D345 doc comments the diff row asked for. **Red first** (gate against unmodified origin/dev 54a9da2a8, dev-02): `2 pass, 6 fail`, 80 listed offences, for example: ``` (fail) no spring lasts more than 300 ms (fail) no CSS transition or animation lasts more than 300 ms, delay included (fail) nothing that moves or fades in CSS starts slowly (fail) no Web Animation lasts more than 300 ms or starts slowly (fail) the FLIP reflow curve is the product's ease-out and lasts its duration (fail) a Spring from rest is ease-out: a fifth of the way in the first frame, half by a third of its time, done at its time + "apps/tracer/auth.css:283 animation: auth-welcome-caption 240ms 900ms var(--ease-out) both", + "apps/tracer/addhost.css:157 transition: grid-template-rows 340ms var(--ease-drawer, ...)", + "apps/tracer/src/canvas/windows/session-ui.on-demand.ts:258 .animate([{ opacity: 0.9 }, { opacity: 0 }], { duration: 520, easing: \"ease-out\" })", ``` **Green** on the branch: motion-speed, no-overshoot and spring tests `20 pass, 0 fail`. `src/canvas` + motion tests: 569 pass. The 8 failures are all `packages/ui/src/ui-scale.test.ts`, which isn't in the gate's list and also fails on unmodified base in the same invocation (14 fail there). **Finding (⌘K by pointer):** the toolbar's Search item opens the palette through a synthetic ⌘K keydown (`toolbar/row.ts:25`), so it's already instant: nothing to slow down. **#146 mockup (`appwin-mock-j209`, base 37f060a45, 2026-11-04):** no springs of its own. It uses the shared `packages/ui/src/spring.ts`, which already has the 11-02 critical damping, so it's slow for the same reason. Its own CSS (4 transitions) is already ease-out at ≤ 200 ms. No change on that branch: it inherits the fix when it rebases onto dev after this merges. `verify quick` passed on dev-02. `verify fast` is queued on compute01 (position 9). The browser frame recordings on three builds (before b5099e3be / prod 54a9da2a8 / this branch) are queued on dev-02.
Author
Owner

Finding 2: measured in the browser, three production builds (dev-02, Chromium, through the test latency proxy)

The same harness runs on each build (~/.cache/codex-scratch/j428/tools/motion-timing.spec.ts on code; it records getBoundingClientRect, opacity, clip-path and the camera every frame, timed from the pointer event).

  • Cells read: t50 / t90 / settled (stays within 1 %) in ms from the input, then the share covered 33 ms after the first moving frame, then the shape.
  • Builds: before = b5099e3be (the dev merge right before no-overshoot), prod = 54a9da2a8 (what ssh.kayg.org runs, /opt/infraternal/54a9da2a8), after = this branch at bfdcddc60.
interaction before 11-02 (b5099e3be) prod (54a9da2a8) after (this branch)
desktop-pill-to-card 131 / 198 / 497 · 24 % · mild ease-out · +10 % 162 / 329 / 529 · 11 % · mild ease-out 61 / 111 / 161 · 61 % · ease-out
desktop-view-switch 78 / 144 / 229 · 40 % · ease-out 79 / 145 / 228 · 43 % · ease-out 62 / 128 / 195 · 61 % · ease-out
desktop-popover-open 45 / 95 / 145 · 74 % · ease-out 61 / 111 / 161 · 76 % · ease-out 52 / 102 / 152 · 78 % · ease-out
desktop-popover-close no motion no motion no motion
desktop-split-right 12 / 12 / 12 · 100 % · instant 12 / 12 / 12 · 100 % · instant 11 / 11 / 11 · 100 % · instant
desktop-close-window not driven not driven not driven
desktop-sidebar-toggle 80 / 147 / 181 · 43 % · ease-out 102 / 187 / 205 · 39 % · ease-out 95 / 161 / 194 · 41 % · ease-out
desktop-camera-flight 105 / 177 / 244 · 38 % · ease-out 106 / 171 / 237 · 36 % · ease-out 60 / 125 / 192 · 54 % · ease-out
desktop-addhost-open 666 / 666 / 874 · 3 % · slow start (S) · +4.7 % 647 / 647 / 818 · 2 % · slow start (S) 651 / 651 / 651 · 5 % · instant
desktop-addhost-continue 171 / 298 / 513 · 20 % · mild ease-out 158 / 347 / 518 · 12 % · mild ease-out 75 / 156 / 210 · 61 % · ease-out
desktop-addhost-back 87 / 141 / 141 · 38 % · ease-out 117 / 143 / 143 · 48 % · ease-out 49 / 72 / 72 · 76 % · ease-out
phone-pill-to-card 131 / 215 / 498 · 13 % · mild ease-out · +10 % 165 / 330 / 547 · 15 % · mild ease-out 65 / 115 / 164 · 62 % · ease-out
phone-view-switch 81 / 149 / 231 · 43 % · ease-out 83 / 151 / 232 · 44 % · ease-out 68 / 116 / 200 · 60 % · ease-out
phone-popover-open not driven not driven not driven
phone-popover-close not driven not driven not driven
phone-split-right not driven not driven not driven
phone-close-window 564 / 714 / 931 · 17 % · mild ease-out 582 / 732 / 933 · 16 % · mild ease-out 166 / 216 / 266 · 73 % · ease-out
phone-camera-flight 70 / 137 / 237 · 49 % · ease-out 81 / 148 / 231 · 45 % · ease-out 59 / 126 / 193 · 59 % · ease-out
phone-addhost-open 408 / 474 / 541 · 50 % · ease-out 414 / 480 / 564 · 46 % · ease-out 381 / 446 / 513 · 50 % · ease-out
phone-addhost-continue 150 / 298 / 481 · 20 % · mild ease-out 146 / 313 / 496 · 19 % · mild ease-out 66 / 132 / 215 · 54 % · ease-out
phone-addhost-back 115 / 165 / 597 · 18 % · mild ease-out 131 / 281 / 464 · 14 % · mild ease-out 65 / 115 / 198 · 62 % · ease-out

Reading: every driven interaction is now ease-out (54–78 % of the way within two frames of starting) and settled in 72–266 ms. Prod's slow ones were 464–933 ms.

Not driven or not motion:

  • desktop split: the window snaps (the droplet overlay isn't tracked).
  • desktop close: its button didn't take a click with this harness. Phone close is measured.
  • popover close: no motion in any build.
  • phone popover and phone split: not on the window header on phone.
  • sheet, move, full screen: not driven by this harness. Their call sites (move.on-demand 0.42 → 0.28, window-resize 0.42 → 0.28, float 0.85 → 0.28) are in the exact table above.

Further findings:

  • Phone close (fixed in bfdcddc60). On prod the neighbours wait for the exit to finish (932 ms). With the curve alone it was still 432 ms, so the exit is now 100 ms and the reflow 200 ms: 266 ms measured. A ~65 ms still frame remains between the exit and the neighbour's slide (frames 101–168 ms in the strip); not chased in this job.
  • Add host open starts about 350–650 ms after the click on every build, before any motion plays. The wizard's float appears late (the on-demand module and discovery go through the test latency proxy), so it's load latency, not animation. Worth its own look.

Strips (cropped to the moving element) below: prod vs after for the same interactions, plus the before build for close.

## Finding 2: measured in the browser, three production builds (dev-02, Chromium, through the test latency proxy) The same harness runs on each build (`~/.cache/codex-scratch/j428/tools/motion-timing.spec.ts` on code; it records `getBoundingClientRect`, opacity, clip-path and the camera every frame, timed from the pointer event). - Cells read: t50 / t90 / **settled (stays within 1 %)** in ms from the input, then the share covered 33 ms after the first moving frame, then the shape. - Builds: before = b5099e3be (the dev merge right before no-overshoot), prod = 54a9da2a8 (what ssh.kayg.org runs, `/opt/infraternal/54a9da2a8`), after = this branch at bfdcddc60. | interaction | before 11-02 (b5099e3be) | prod (54a9da2a8) | after (this branch) | |---|---|---|---| | desktop-pill-to-card | 131 / 198 / **497** · 24 % · mild ease-out · +10 % | 162 / 329 / **529** · 11 % · mild ease-out | 61 / 111 / **161** · 61 % · ease-out | | desktop-view-switch | 78 / 144 / **229** · 40 % · ease-out | 79 / 145 / **228** · 43 % · ease-out | 62 / 128 / **195** · 61 % · ease-out | | desktop-popover-open | 45 / 95 / **145** · 74 % · ease-out | 61 / 111 / **161** · 76 % · ease-out | 52 / 102 / **152** · 78 % · ease-out | | desktop-popover-close | no motion | no motion | no motion | | desktop-split-right | 12 / 12 / **12** · 100 % · instant | 12 / 12 / **12** · 100 % · instant | 11 / 11 / **11** · 100 % · instant | | desktop-close-window | not driven | not driven | not driven | | desktop-sidebar-toggle | 80 / 147 / **181** · 43 % · ease-out | 102 / 187 / **205** · 39 % · ease-out | 95 / 161 / **194** · 41 % · ease-out | | desktop-camera-flight | 105 / 177 / **244** · 38 % · ease-out | 106 / 171 / **237** · 36 % · ease-out | 60 / 125 / **192** · 54 % · ease-out | | desktop-addhost-open | 666 / 666 / **874** · 3 % · slow start (S) · +4.7 % | 647 / 647 / **818** · 2 % · slow start (S) | 651 / 651 / **651** · 5 % · instant | | desktop-addhost-continue | 171 / 298 / **513** · 20 % · mild ease-out | 158 / 347 / **518** · 12 % · mild ease-out | 75 / 156 / **210** · 61 % · ease-out | | desktop-addhost-back | 87 / 141 / **141** · 38 % · ease-out | 117 / 143 / **143** · 48 % · ease-out | 49 / 72 / **72** · 76 % · ease-out | | phone-pill-to-card | 131 / 215 / **498** · 13 % · mild ease-out · +10 % | 165 / 330 / **547** · 15 % · mild ease-out | 65 / 115 / **164** · 62 % · ease-out | | phone-view-switch | 81 / 149 / **231** · 43 % · ease-out | 83 / 151 / **232** · 44 % · ease-out | 68 / 116 / **200** · 60 % · ease-out | | phone-popover-open | not driven | not driven | not driven | | phone-popover-close | not driven | not driven | not driven | | phone-split-right | not driven | not driven | not driven | | phone-close-window | 564 / 714 / **931** · 17 % · mild ease-out | 582 / 732 / **933** · 16 % · mild ease-out | 166 / 216 / **266** · 73 % · ease-out | | phone-camera-flight | 70 / 137 / **237** · 49 % · ease-out | 81 / 148 / **231** · 45 % · ease-out | 59 / 126 / **193** · 59 % · ease-out | | phone-addhost-open | 408 / 474 / **541** · 50 % · ease-out | 414 / 480 / **564** · 46 % · ease-out | 381 / 446 / **513** · 50 % · ease-out | | phone-addhost-continue | 150 / 298 / **481** · 20 % · mild ease-out | 146 / 313 / **496** · 19 % · mild ease-out | 66 / 132 / **215** · 54 % · ease-out | | phone-addhost-back | 115 / 165 / **597** · 18 % · mild ease-out | 131 / 281 / **464** · 14 % · mild ease-out | 65 / 115 / **198** · 62 % · ease-out | **Reading:** every driven interaction is now ease-out (54–78 % of the way within two frames of starting) and settled in 72–266 ms. Prod's slow ones were 464–933 ms. Not driven or not motion: - **desktop split:** the window snaps (the droplet overlay isn't tracked). - **desktop close:** its button didn't take a click with this harness. Phone close is measured. - **popover close:** no motion in any build. - **phone popover and phone split:** not on the window header on phone. - **sheet, move, full screen:** not driven by this harness. Their call sites (`move.on-demand` 0.42 → 0.28, `window-resize` 0.42 → 0.28, `float` 0.85 → 0.28) are in the exact table above. Further findings: - **Phone close (fixed in `bfdcddc60`).** On prod the neighbours wait for the exit to finish (932 ms). With the curve alone it was still 432 ms, so the exit is now 100 ms and the reflow 200 ms: 266 ms measured. A ~65 ms still frame remains between the exit and the neighbour's slide (frames 101–168 ms in the strip); not chased in this job. - **Add host open starts about 350–650 ms after the click on every build, before any motion plays.** The wizard's float appears late (the on-demand module and discovery go through the test latency proxy), so it's load latency, not animation. Worth its own look. Strips (cropped to the moving element) below: prod vs after for the same interactions, plus the before build for close.
Author
Owner

Gate progress:

  • verify fast on compute01 at bfdcddc60 (log ~claude/.cache/codex-scratch/j428/fast-bfdcddc60.log on compute01) failed 3 Rust rows. The Rust phase failing also stops fast before its TypeScript phase.
    • test: logins::durable_tests::login_durable_writes_leave_the_reactor_responsive.
    • shim process-per-user: session::user_host::tests::zero_ring_capacity_does_not_stall_a_live_pty.
    • cargo-deny: error[vulnerability]: libsqlite3-sys via C SQLite CVE-2026-119375.
  • None come from this branch. git diff --stat origin/dev...HEAD -- '*.rs' Cargo.lock '*/Cargo.toml' deny.toml is empty: the branch changes TS, CSS, docs and the verify test list only. cargo deny --locked check on unmodified origin/dev 9d225cb0e fails with the same single error[vulnerability]: libsqlite3-sys via C SQLite CVE-2026-119375 (deny-dev.log on compute01). The two Rust tests are load-timing tests on code this branch doesn't touch.
  • Merged origin/dev (33 commits, clean) → d5f09c43e, pushed. On the merged tree, motion-speed, no-overshoot, spring and src/canvas tests: 524 pass, 0 fail.
  • Queued: verify fast on compute01 at d5f09c43e, plus the TS phase of fast alone on dev-02, so the tracer gate (including motion-speed.test.ts) gets a run even if the Rust rows fail again.
Gate progress: - `verify fast` on compute01 at `bfdcddc60` (log `~claude/.cache/codex-scratch/j428/fast-bfdcddc60.log` on compute01) failed 3 Rust rows. The Rust phase failing also stops `fast` before its TypeScript phase. - `test`: `logins::durable_tests::login_durable_writes_leave_the_reactor_responsive`. - `shim process-per-user`: `session::user_host::tests::zero_ring_capacity_does_not_stall_a_live_pty`. - `cargo-deny`: `error[vulnerability]: libsqlite3-sys via C SQLite CVE-2026-119375`. - **None come from this branch.** `git diff --stat origin/dev...HEAD -- '*.rs' Cargo.lock '*/Cargo.toml' deny.toml` is empty: the branch changes TS, CSS, docs and the verify test list only. `cargo deny --locked check` on **unmodified origin/dev 9d225cb0e** fails with the same single `error[vulnerability]: libsqlite3-sys via C SQLite CVE-2026-119375` (`deny-dev.log` on compute01). The two Rust tests are load-timing tests on code this branch doesn't touch. - Merged origin/dev (33 commits, clean) → `d5f09c43e`, pushed. On the merged tree, motion-speed, no-overshoot, spring and `src/canvas` tests: `524 pass, 0 fail`. - Queued: `verify fast` on compute01 at d5f09c43e, plus the TS phase of `fast` alone on dev-02, so the tracer gate (including `motion-speed.test.ts`) gets a run even if the Rust rows fail again.
Author
Owner

Rebased on #427 (origin/dev c442500c2) as the orchestrator asked. New head: 9a600904c (force-pushed, own branch).

How the conflicts were resolved (as in J436's trial merge, #427 comment 88843):

  • camera.ts: KEY_FLIGHT dropped (#427 removed it, a key flight has no motion). FLIGHT stays 0.26 s on the ease-out curve. Both the commit message and the comment now say so.
  • lifecycle.ts pulse: #427's triggeredByKeyboard() guard kept, plus this branch's 240 ms EASE_OUT.
  • anchored-popover.ts: #427's !triggeredByKeyboard() kept, plus this branch's EASE_OUT fade (was "ease").
  • Doc-comment conflicts (wizard.ts, world.ts, pill-morph.ts): #427's sentences kept, each with the new timing appended.

P3-E fixed in 9a600904c. ms() in motion-speed.test.ts resolves calc()/var(): duration tokens to ms, --ui-motion-scale to 1 (the pointer case), and other vars to their fallback. A new test checks the #427 forms and that every scaled transition in the stylesheets comes out with a non-zero duration.

  • Red first: on the rebased head before the fix, Expected: 180, Received: undefined.
  • On unmodified origin/dev c442500c2 the gate now reports J436's findings among the rest:
+   "apps/tracer/src/canvas/windows/lifecycle.ts:335 .animate([{ transform: "scale(1)" }, { transform: "scale(1.012)" }, { transform: "scale(1)" }], { duration: 360, easing: EASE_OUT })",
+   "packages/ui/src/anchored-popover.ts:209 .animate([{ opacity: 0 }, { opacity: 1 }], { duration: 160, easing: "ease" })",
  • On the branch: motion-speed, no-overshoot and spring tests 21 pass, 0 fail.

Dev is red independently of this branch (TS phase): bun test ./src/connection*.test.ts on unmodified origin/dev c442500c2 gives 95 pass, 10 fail, with TypeError: undefined is not an object (evaluating 'this.clipboardWrites.reset') (terminal.ts:1416/1900, from #440's OSC 52 change; the connection test fakes lack clipboardWrites). Expect those 10 in this branch's verify fast too.

Gates queued on 9a600904c: verify quick on dev-02, verify fast on compute01.

Rebased on #427 (origin/dev c442500c2) as the orchestrator asked. New head: `9a600904c` (force-pushed, own branch). How the conflicts were resolved (as in J436's trial merge, #427 comment 88843): - `camera.ts`: `KEY_FLIGHT` dropped (#427 removed it, a key flight has no motion). `FLIGHT` stays 0.26 s on the ease-out curve. Both the commit message and the comment now say so. - `lifecycle.ts` pulse: #427's `triggeredByKeyboard()` guard kept, plus this branch's 240 ms `EASE_OUT`. - `anchored-popover.ts`: #427's `!triggeredByKeyboard()` kept, plus this branch's `EASE_OUT` fade (was `"ease"`). - Doc-comment conflicts (`wizard.ts`, `world.ts`, `pill-morph.ts`): #427's sentences kept, each with the new timing appended. **P3-E fixed** in `9a600904c`. `ms()` in `motion-speed.test.ts` resolves `calc()`/`var()`: duration tokens to ms, `--ui-motion-scale` to 1 (the pointer case), and other vars to their fallback. A new test checks the #427 forms and that every scaled transition in the stylesheets comes out with a non-zero duration. - Red first: on the rebased head before the fix, `Expected: 180, Received: undefined`. - On **unmodified origin/dev c442500c2** the gate now reports J436's findings among the rest: ``` + "apps/tracer/src/canvas/windows/lifecycle.ts:335 .animate([{ transform: "scale(1)" }, { transform: "scale(1.012)" }, { transform: "scale(1)" }], { duration: 360, easing: EASE_OUT })", + "packages/ui/src/anchored-popover.ts:209 .animate([{ opacity: 0 }, { opacity: 1 }], { duration: 160, easing: "ease" })", ``` - On the branch: motion-speed, no-overshoot and spring tests `21 pass, 0 fail`. **Dev is red independently of this branch (TS phase):** `bun test ./src/connection*.test.ts` on unmodified origin/dev c442500c2 gives `95 pass, 10 fail`, with `TypeError: undefined is not an object (evaluating 'this.clipboardWrites.reset')` (terminal.ts:1416/1900, from #440's OSC 52 change; the connection test fakes lack `clipboardWrites`). Expect those 10 in this branch's `verify fast` too. Gates queued on `9a600904c`: `verify quick` on dev-02, `verify fast` on compute01.
Author
Owner

Progress:

  • Dev moved again, so the branch is rebased (clean) onto origin/dev 198c4a736. New head: a48a0c2e8, pushed. Motion, no-overshoot, spring, layout and camera tests on it: 68 pass, 0 fail.
  • verify quick passed on 9a600904c, which has the same branch diff (dev-02, every row PASS including ui literals and tracer typecheck).
  • TS phase of fast alone (dev-02, ts-phase3.log): stops at (fail) a closing notice cannot hide its replacement in src/canvas/sessions/startup-toast.test.ts. The stack is in #427's markMotion (input-source.ts:83) inside the toast fake DOM. It fails the same way on unmodified origin/dev 198c4a736 (0 pass, 1 fail). The branch doesn't touch toast.ts, input-source.ts, toast-fake-dom.ts or the test. This is dev-red, together with the clipboardWrites connection tests and the cargo-deny SQLite advisory.
  • verify fast on compute01 for a48a0c2e8: still in the queue (6th).
Progress: - Dev moved again, so the branch is rebased (clean) onto origin/dev 198c4a736. New head: **`a48a0c2e8`**, pushed. Motion, no-overshoot, spring, layout and camera tests on it: `68 pass, 0 fail`. - `verify quick` passed on 9a600904c, which has the same branch diff (dev-02, every row PASS including ui literals and tracer typecheck). - **TS phase of `fast` alone** (dev-02, `ts-phase3.log`): stops at `(fail) a closing notice cannot hide its replacement` in `src/canvas/sessions/startup-toast.test.ts`. The stack is in #427's `markMotion` (`input-source.ts:83`) inside the toast fake DOM. **It fails the same way on unmodified origin/dev 198c4a736** (`0 pass, 1 fail`). The branch doesn't touch toast.ts, input-source.ts, toast-fake-dom.ts or the test. This is dev-red, together with the `clipboardWrites` connection tests and the cargo-deny SQLite advisory. - `verify fast` on compute01 for a48a0c2e8: still in the queue (6th).
Author
Owner

Finish: head a48a0c2e8 (branch motion-fast-ease-out, rebased on origin/dev 198c4a736, which includes #427 c442500c2)

git ls-remote origin motion-fast-ease-out → a48a0c2e8ed4a1b9d3621bd327159b86afcb73a2

scripts/verify fast on compute01 at a48a0c2e8 (log ~claude/.cache/codex-scratch/j428/fast-a48a0c2e8.log on compute01), verbatim:

| Rust gate                 | Result       |
+---------------------------+--------------+
| Nix Cargo vendoring       | PASS         |
| fmt                       | PASS         |
| migration names           | PASS         |
| migration tooling tests   | PASS         |
| VT native fmt             | PASS         |
| CLI plugin compile-out    | PASS         |
| clippy                    | PASS         |
| SSH runtime test helper   | PASS         |
| real-binary fixture paths | PASS         |
| Traefik component manifest | PASS         |
| release component publication | PASS         |
| test                      | PASS         |
| SSH inode reuse fixture   | PASS         |
| shim process-per-user     | PASS         |
| cargo-deny                | PASS         |
| gate architecture         | PASS         |
| doc coverage ratchet      | PASS         |
| shape lints off (D351)    | PASS         |
| doc comments on diff      | PASS         |
| fixed test waits          | PASS         |
| privileged integration    | SKIP         |
| probe release exclusion   | PASS         |
| nightly fuzz reporter     | PASS         |
| nightly browser suite reporter | PASS         |
| static builds             | PASS         |
+---------------------------+--------------+
+ @types/bun@1.4.4

1 tests failed:
(fail) a closing notice cannot hide its replacement [1.98ms]

 664 pass
 1 fail
 24849 expect() calls
Ran 665 tests across 103 files. [38.53s]
  • The motion gate inside that run: all 9 test/motion-speed.test.ts tests pass. That includes "durations scaled by #427's --ui-motion-scale are read at scale 1, not as 0".
  • The one TS failure is dev-red, not this branch. a closing notice cannot hide its replacement (src/canvas/sessions/startup-toast.test.ts, stack in #427's markMotion, input-source.ts:83) fails the same way on unmodified origin/dev 198c4a736 (0 pass, 1 fail). The branch touches none of toast.ts, input-source.ts, toast-fake-dom.ts or the test.
  • Static sizes (report only, D243): x86_64 28 272 536 B, aarch64 25 382 600 B. Both deltas (+5.85 MB / +4.93 MB) are against the gate's older stored baseline. This branch changes only TS, CSS and docs.
  • verify quick passed on the same branch diff (dev-02, every row PASS).

Before / after (browser, prod build; t50 / t90 / settled ms)

interaction prod 54a9da2a8 after
pill → card (desktop / phone) 162/329/529 · 165/330/547 61/111/161 · 65/115/164
view switch (desktop / phone) 79/145/228 · 83/151/232 62/128/195 · 68/116/200
camera flight (desktop / phone) 106/171/237 · 81/148/231 60/125/192 · 59/126/193
Add host Continue (desktop / phone) 158/347/518 · 146/313/496 75/156/210 · 66/132/215
Add host Back (phone) 131/281/464 65/115/198
close window (phone) 582/732/933 166/216/266
popover open (desktop) 61/111/161 52/102/152

The full table, the strips and the exact per-call-site table are in the comments above.

Open items, not fixed in this job

  • Add host open starts 350–650 ms after the click in every build (load latency before any motion).
  • A ~65 ms still frame between the exit and the neighbours' reflow on close.
  • Dev-red: startup-toast, the clipboardWrites connection tests, and the cargo-deny SQLite advisory (green again in this run).
## Finish: head `a48a0c2e8` (branch `motion-fast-ease-out`, rebased on origin/dev 198c4a736, which includes #427 c442500c2) `git ls-remote origin motion-fast-ease-out` → `a48a0c2e8ed4a1b9d3621bd327159b86afcb73a2` ### `scripts/verify fast` on compute01 at a48a0c2e8 (log `~claude/.cache/codex-scratch/j428/fast-a48a0c2e8.log` on compute01), verbatim: ``` | Rust gate | Result | +---------------------------+--------------+ | Nix Cargo vendoring | PASS | | fmt | PASS | | migration names | PASS | | migration tooling tests | PASS | | VT native fmt | PASS | | CLI plugin compile-out | PASS | | clippy | PASS | | SSH runtime test helper | PASS | | real-binary fixture paths | PASS | | Traefik component manifest | PASS | | release component publication | PASS | | test | PASS | | SSH inode reuse fixture | PASS | | shim process-per-user | PASS | | cargo-deny | PASS | | gate architecture | PASS | | doc coverage ratchet | PASS | | shape lints off (D351) | PASS | | doc comments on diff | PASS | | fixed test waits | PASS | | privileged integration | SKIP | | probe release exclusion | PASS | | nightly fuzz reporter | PASS | | nightly browser suite reporter | PASS | | static builds | PASS | +---------------------------+--------------+ + @types/bun@1.4.4 1 tests failed: (fail) a closing notice cannot hide its replacement [1.98ms] 664 pass 1 fail 24849 expect() calls Ran 665 tests across 103 files. [38.53s] ``` - The motion gate inside that run: all 9 `test/motion-speed.test.ts` tests pass. That includes "durations scaled by #427's --ui-motion-scale are read at scale 1, not as 0". - The **one TS failure is dev-red, not this branch.** `a closing notice cannot hide its replacement` (`src/canvas/sessions/startup-toast.test.ts`, stack in #427's `markMotion`, `input-source.ts:83`) fails the same way on **unmodified origin/dev 198c4a736** (`0 pass, 1 fail`). The branch touches none of toast.ts, input-source.ts, toast-fake-dom.ts or the test. - Static sizes (report only, D243): x86_64 28 272 536 B, aarch64 25 382 600 B. Both deltas (+5.85 MB / +4.93 MB) are against the gate's older stored baseline. This branch changes only TS, CSS and docs. - `verify quick` passed on the same branch diff (dev-02, every row PASS). ### Before / after (browser, prod build; t50 / t90 / settled ms) | interaction | prod 54a9da2a8 | after | |---|---|---| | pill → card (desktop / phone) | 162/329/529 · 165/330/547 | 61/111/161 · 65/115/164 | | view switch (desktop / phone) | 79/145/228 · 83/151/232 | 62/128/195 · 68/116/200 | | camera flight (desktop / phone) | 106/171/237 · 81/148/231 | 60/125/192 · 59/126/193 | | Add host Continue (desktop / phone) | 158/347/518 · 146/313/496 | 75/156/210 · 66/132/215 | | Add host Back (phone) | 131/281/464 | 65/115/198 | | close window (phone) | 582/732/933 | 166/216/266 | | popover open (desktop) | 61/111/161 | 52/102/152 | The full table, the strips and the exact per-call-site table are in the comments above. ### Open items, not fixed in this job - Add host open starts 350–650 ms after the click in every build (load latency before any motion). - A ~65 ms still frame between the exit and the neighbours' reflow on close. - Dev-red: startup-toast, the `clipboardWrites` connection tests, and the cargo-deny SQLite advisory (green again in this run).
Author
Owner

Review of motion-fast-ease-out @ a48a0c2e8 (issue #471, finish comment 89097)

Verdict: APPROVE, with one P2 (a visible gap on close, already listed as open by the author) and P3 gate blind spots. Two things I could not verify are listed under "Not verified". Nothing here blocks merge.

Environment: production build of a48a0c2e8 on dev-02 (aarch64, Chromium, through the build-slot queue), J428's own harness motion-timing.spec.ts copied unchanged. Artifacts are in ~/.cache/codex-scratch/r471/evidence/ on the control host: motion-review-*.json, strip-review-*.png.

1. Interactions re-measured (phone 393×852, touch; ms from the input; t50 / t90 / settled)

interaction start first-frame share t50 / t90 / settled shape overshoot
pill → card (desktop) 28 6.7 % 60 / 121 / 181 ease-out 0
pill → card (phone) 32 17.7 % 66 / 115 / 166 ease-out 0
view switch (phone) 32 20.5 % 66 / 116 / 199 ease-out 0
close window (phone) 149 33.7 % 166 / 216 / 265 ease-out 0
camera flight (phone) 30 17.2 % 64 / 112 / 197 ease-out 0
Add host Continue (phone) 32 13.1 % 65 / 132 / 215 ease-out 0
Add host Back (phone) 32 15.2 % 68 / 132 / 199 ease-out 0
Add host open (phone) 358 22.9 % 392 / 442 / 525 ease-out 0

Seven of eight settle within 266 ms of the input, which matches J428's table (close 266, Continue 215, pill→card 166). All eight reach half the distance within about 35 ms of their first moving frame. Add host open settles at 525 ms, but its motion starts at 358 ms (load latency before any motion, which the author lists as open); the motion itself lasts 167 ms.

Harness gaps, not product findings: the desktop run died at its second screenshot ("Test ended"), so desktop has only pill→card. Phone popover and split steps found no [data-active] header button, because the harness hovers on desktop only. Those two are covered by the unit-level gate and the no-overshoot test, not by a browser frame strip.

2. No overshoot

no-overshoot.test.ts, motion-speed.test.ts, spring.test.ts on this SHA: 21 pass, 0 fail. The browser curves above have overshoot: 0 on every channel. The close strip (strip-review-phone-close-window.png, attached) shows no bounce or settle-back.

3. Keyboard opens still instant (#427)

keyboard-sweep.spec.ts and keyboard-instant.spec.ts: 38 passed (20.5 m), including both viewports (1440×900 and 393×852) of "no shortcut leaves anything animating after the key" and "no palette row leaves anything animating after Enter".

4. Fling and swipe

row-swipe-continuity.spec.ts: 4 passed. That includes "a short right swipe reveals Open beside and springs open from the finger's speed" and the continuity check (133 frames, p95 16.7 ms, largest row step 10.2 px). row-menu.ts:195 is still the one product call site that calls fling(value, velocity); Spring.fling keeps the finger's velocity and is critically damped (no overshoot). I did not feel it on a real touch device; this is spec evidence only.

5. Gate red/green (scratch copy, one injected change at a time)

Baseline 9 pass. Went red as required: a CSS transform 360ms var(--ease-out) (A); a CSS opacity 200ms ease-in (B); .animate() with duration: 360 (D); .animate() with easing: "ease-in" (F); visualDuration: 0.5 (H). So the two cases in the brief, a new 360 ms transition and an ease-in, are caught in CSS shorthand and in .animate().

The allow-list is justified: spinners (constant-speed turn, 600 ms), glitch, setup pulse, mesh-note breathe, caret blink, keywiggle, conn pulse, setting-found tint are all loops or colour-in-place. I grepped every infinite in the source: each is on the list. Note cl-note-breathe, keywiggle and ui-setup-pulse use ease-in-out, which is fine for a loop.

6. Spinners

ui-spinner-turn, ui-mark-spin, ui-conn-turn are all 600ms linear infinite (were 720/800/900 ms). Linear is right for a constant turn. Confirmed from source (packages/ui/styles.css:332,752,1017); not timed in a browser.

7. startup-toast

Not part of this review's findings. J428 reports it fails identically on unmodified dev; I did not re-run it.

Findings

P2: close leaves an empty gap of about 50 ms. From the strip (strip-review-phone-close-window.png): the closing window is gone by 102 ms, nothing moves at 102 to 150 ms, the neighbour starts to slide in at 169 ms and settles by about 265 ms. The 100 ms exit and 200 ms reflow run one after the other, so the screen shows an empty slot for roughly 50 to 65 ms, which reads as a stutter. The author already lists this ("a ~65 ms still frame"). Overlapping the reflow with the last half of the exit (start it at ~50 ms) would remove the gap and shorten the total. Not blocking, since it is within 300 ms and ease-out shaped, but it is the kind of thing the owner will notice.

P3: the gate misses four ways to write the same offence. All pass (9 pass) when injected into the scratch copy:

  • CSS longhands: transition-duration: 600ms; transition-timing-function: ease-in (C).
  • A named constant for a Web Animation duration: duration: ZZ_LONG where const ZZ_LONG = 600 (E). The scan only reads numeric literals after duration:.
  • Inline el.style.transition = "transform 600ms ease-in" (G). The scan reads .css only; main.ts:414, branched.ts, capsule-tabs.ts, tooltip.ts set transitions this way today.
  • visualDuration through a variable (I).

An existing example of the constant hole: window-resize.ts:271 animates HOLD_MS (450) plus a 120 ms delay = 570 ms. It is a deliberate press-and-hold lean-in, not an enter or exit, so it is probably legitimate, but it sits outside the 300 ms rule only because the gate cannot see it. Suggest either resolving same-file const NAME = <number> in the scan, and scanning style.transition = "..." strings, or listing that one on the allow-list with its reason. I found no current offender in the longhand or inline-style paths by grep.

Not verified

  • Four canvas-swipe specs fail on a precondition, before any swipe is sent: clusters-swipes.spec.ts (both variants) and clusters-swipes-both.spec.ts (both variants) expect canvasLock().stops >= 8 and get 5 in the settings view. That is a scene-content count, not a motion value, and this branch touches no scene or settings code. But I did not run the specs on the base (198c4a736), so I cannot say they fail there too. Please run clusters-swipes.spec.ts on dev before merge and say so on the issue.
  • Touch feel of a fling on a real device, and a split frame strip (the harness could not find the split button on phone).

SHA reviewed: a48a0c2e8ed4a1b9d3621bd327159b86afcb73a2. Scratch worktrees and the red-team copy are removed from dev-02 and compute01.

## Review of `motion-fast-ease-out` @ `a48a0c2e8` (issue #471, finish comment 89097) **Verdict: APPROVE**, with one P2 (a visible gap on close, already listed as open by the author) and P3 gate blind spots. Two things I could not verify are listed under "Not verified". Nothing here blocks merge. Environment: production build of `a48a0c2e8` on dev-02 (aarch64, Chromium, through the build-slot queue), J428's own harness `motion-timing.spec.ts` copied unchanged. Artifacts are in `~/.cache/codex-scratch/r471/evidence/` on the control host: `motion-review-*.json`, `strip-review-*.png`. ### 1. Interactions re-measured (phone 393×852, touch; ms from the input; t50 / t90 / settled) | interaction | start | first-frame share | t50 / t90 / settled | shape | overshoot | |---|---|---|---|---|---| | pill → card (desktop) | 28 | 6.7 % | 60 / 121 / 181 | ease-out | 0 | | pill → card (phone) | 32 | 17.7 % | 66 / 115 / 166 | ease-out | 0 | | view switch (phone) | 32 | 20.5 % | 66 / 116 / 199 | ease-out | 0 | | close window (phone) | 149 | 33.7 % | 166 / 216 / 265 | ease-out | 0 | | camera flight (phone) | 30 | 17.2 % | 64 / 112 / 197 | ease-out | 0 | | Add host Continue (phone) | 32 | 13.1 % | 65 / 132 / 215 | ease-out | 0 | | Add host Back (phone) | 32 | 15.2 % | 68 / 132 / 199 | ease-out | 0 | | Add host open (phone) | 358 | 22.9 % | 392 / 442 / 525 | ease-out | 0 | Seven of eight settle within 266 ms of the input, which matches J428's table (close 266, Continue 215, pill→card 166). All eight reach half the distance within about 35 ms of their first moving frame. Add host open settles at 525 ms, but its motion starts at 358 ms (load latency before any motion, which the author lists as open); the motion itself lasts 167 ms. Harness gaps, not product findings: the desktop run died at its second `screenshot` ("Test ended"), so desktop has only pill→card. Phone popover and split steps found no `[data-active]` header button, because the harness hovers on desktop only. Those two are covered by the unit-level gate and the no-overshoot test, not by a browser frame strip. ### 2. No overshoot `no-overshoot.test.ts`, `motion-speed.test.ts`, `spring.test.ts` on this SHA: **21 pass, 0 fail**. The browser curves above have `overshoot: 0` on every channel. The close strip (`strip-review-phone-close-window.png`, attached) shows no bounce or settle-back. ### 3. Keyboard opens still instant (#427) `keyboard-sweep.spec.ts` and `keyboard-instant.spec.ts`: **38 passed (20.5 m)**, including both viewports (1440×900 and 393×852) of "no shortcut leaves anything animating after the key" and "no palette row leaves anything animating after Enter". ### 4. Fling and swipe `row-swipe-continuity.spec.ts`: 4 passed. That includes "a short right swipe reveals Open beside and springs open from the finger's speed" and the continuity check (133 frames, p95 16.7 ms, largest row step 10.2 px). `row-menu.ts:195` is still the one product call site that calls `fling(value, velocity)`; `Spring.fling` keeps the finger's velocity and is critically damped (no overshoot). **I did not feel it on a real touch device**; this is spec evidence only. ### 5. Gate red/green (scratch copy, one injected change at a time) Baseline 9 pass. Went red as required: a CSS `transform 360ms var(--ease-out)` (A); a CSS `opacity 200ms ease-in` (B); `.animate()` with `duration: 360` (D); `.animate()` with `easing: "ease-in"` (F); `visualDuration: 0.5` (H). So the two cases in the brief, a new 360 ms transition and an `ease-in`, are caught in CSS shorthand and in `.animate()`. The allow-list is justified: spinners (constant-speed turn, 600 ms), glitch, setup pulse, mesh-note breathe, caret blink, keywiggle, conn pulse, setting-found tint are all loops or colour-in-place. I grepped every `infinite` in the source: each is on the list. Note `cl-note-breathe`, `keywiggle` and `ui-setup-pulse` use `ease-in-out`, which is fine for a loop. ### 6. Spinners `ui-spinner-turn`, `ui-mark-spin`, `ui-conn-turn` are all `600ms linear infinite` (were 720/800/900 ms). Linear is right for a constant turn. Confirmed from source (`packages/ui/styles.css:332,752,1017`); not timed in a browser. ### 7. startup-toast Not part of this review's findings. J428 reports it fails identically on unmodified dev; I did not re-run it. ### Findings **P2: close leaves an empty gap of about 50 ms.** From the strip (`strip-review-phone-close-window.png`): the closing window is gone by 102 ms, nothing moves at 102 to 150 ms, the neighbour starts to slide in at 169 ms and settles by about 265 ms. The 100 ms exit and 200 ms reflow run one after the other, so the screen shows an empty slot for roughly 50 to 65 ms, which reads as a stutter. The author already lists this ("a ~65 ms still frame"). Overlapping the reflow with the last half of the exit (start it at ~50 ms) would remove the gap and shorten the total. Not blocking, since it is within 300 ms and ease-out shaped, but it is the kind of thing the owner will notice. **P3: the gate misses four ways to write the same offence.** All pass (9 pass) when injected into the scratch copy: - CSS longhands: `transition-duration: 600ms; transition-timing-function: ease-in` (C). - A named constant for a Web Animation duration: `duration: ZZ_LONG` where `const ZZ_LONG = 600` (E). The scan only reads numeric literals after `duration:`. - Inline `el.style.transition = "transform 600ms ease-in"` (G). The scan reads `.css` only; `main.ts:414`, `branched.ts`, `capsule-tabs.ts`, `tooltip.ts` set transitions this way today. - `visualDuration` through a variable (I). An existing example of the constant hole: `window-resize.ts:271` animates `HOLD_MS` (450) plus a 120 ms delay = 570 ms. It is a deliberate press-and-hold lean-in, not an enter or exit, so it is probably legitimate, but it sits outside the 300 ms rule only because the gate cannot see it. Suggest either resolving same-file `const NAME = <number>` in the scan, and scanning `style.transition = "..."` strings, or listing that one on the allow-list with its reason. I found no current offender in the longhand or inline-style paths by grep. ### Not verified - **Four canvas-swipe specs fail on a precondition**, before any swipe is sent: `clusters-swipes.spec.ts` (both variants) and `clusters-swipes-both.spec.ts` (both variants) expect `canvasLock().stops >= 8` and get 5 in the settings view. That is a scene-content count, not a motion value, and this branch touches no scene or settings code. But I did not run the specs on the base (`198c4a736`), so I cannot say they fail there too. Please run `clusters-swipes.spec.ts` on dev before merge and say so on the issue. - Touch feel of a fling on a real device, and a split frame strip (the harness could not find the split button on phone). SHA reviewed: `a48a0c2e8ed4a1b9d3621bd327159b86afcb73a2`. Scratch worktrees and the red-team copy are removed from dev-02 and compute01.
Author
Owner

Merged dev again; J455's blind spots closed. Head 437a4a56a

git ls-remote origin motion-fast-ease-out → 437a4a56a4869ac56f340806414f016ba0d83fa0. Trial merge with the newest origin/dev 3c0b20252: clean.

Merges

  • 13a8a7c09 merges a0f5a8aee. scripts/verify.d/ts.sh keeps both rows: dev's theme-audit.test.ts and this branch's test/motion-speed.test.ts. The two sides differed only in those rows.
  • 437a4a56a merges 97928be8a (#427 follow-ups). sessions.css (.cl-sx-meta, .cl-sx-acts) and styles.css (.ui-sp-tick) keep dev's calc(var(--t-press) * var(--ui-motion-scale, 1)) and its new [data-motion="instant"] rule, with ease-out instead of ease on those fades.
  • 28dfc73be: the gate caught 4 new opacity 160ms ease fades from the themes work (.cl-theme-name, .cl-glide and their reduced-motion fallbacks) → var(--ease-out), same 160 ms.

J455's blind spots (090f6795d, red first; red log attached)

  • CSS longhands: transition-/animation- duration, delay and timing-function are now read. A timing-function longhand refuses ease/linear, because it names no property.
  • WAAPI durations: named constants and expressions are resolved. That includes comma declarations (const POP = 160, SWAP = 140), constants exported by other files, and both branches of a conditional. A duration the scan can't resolve is refused, not read as 0.
  • Inline transitions: el.style.transition = …, its longhands and style.setProperty("transition", …) are parsed like CSS, with ${CONSTANT} in template strings substituted.
  • Spring variables: visualDuration: VAR and { visualDuration } are resolved; a parameter is refused. Type annotations and comments are ignored.
  • Each blind spot has a fixture test. Before the change all four failed (9 pass, 5 fail; the fifth was the clusters fades). After it: 26 pass, 0 fail across motion-speed, no-overshoot and spring. The "scan sees" test also checks that tooltip's SWAP resolves and that `width ${TRAVEL}ms ${EASE_OUT}` expands.

window-resize.ts:271 (the 570 ms lean-in): allow-listed, with the reason in the file. It's a press-and-hold progress cue, not an enter or exit:

  • it lasts exactly the hold time (HOLD_MS = 450 ms, starting 120 ms in so a tap shows nothing);
  • the hold timer cancels it at 450 ms, so it never plays its nominal 570 ms;
  • the release reverses in 220 ms.

Shortening it would make the cue finish before the hold counts. Two more allow-list entries, each with its reason: the FLIP reflow's { duration, easing: css } (from flipEasing, which has its own test) and the windows prototype mock's stagger delays.

Runs on dev-02

  • verify quick at 437a4a56a: all rows PASS (scope guard, migration names, Nix vendoring, VT source recipe, fmt, doc coverage, shape lints, doc comments on diff, fixed test waits, ui literals, tracer typecheck, D365 theme contrast audit). QUICK_EXIT=0.
  • keyboard-sweep, themes, terminal-theme and clusters-theme-transition specs at 437a4a56a: 26 passed, 7 skipped, 1 failed. Theme unit tests: 8 pass, 0 fail.
    • The failure is terminal-theme.spec.ts:206 every curated theme recolors…. On 090f6795d it failed at the selection colour (Received: 109, line 321); on 437a4a56a, earlier, waiting for the fixture to render (outputSequence 0, line 228). It fails the same way on unmodified origin/dev a0f5a8aee (Received: 109), and it tests the terminal renderer, not motion.
  • keyboard-sweep at 090f6795d failed once at 393×852 ("Open the command palette": .cl-sx-meta/.cl-sx-acts fading 200–325 ms after the key). Repeated 4× on the branch: 8 passed, the same as dev. Dev's #427 follow-up (97928be8a, merged above) gives exactly those two elements a key-instant rule, and at 437a4a56a every keyboard-sweep test passes.
## Merged dev again; J455's blind spots closed. Head **`437a4a56a`** `git ls-remote origin motion-fast-ease-out` → `437a4a56a4869ac56f340806414f016ba0d83fa0`. Trial merge with the newest origin/dev 3c0b20252: clean. **Merges** - `13a8a7c09` merges a0f5a8aee. `scripts/verify.d/ts.sh` keeps both rows: dev's `theme-audit.test.ts` and this branch's `test/motion-speed.test.ts`. The two sides differed only in those rows. - `437a4a56a` merges 97928be8a (#427 follow-ups). `sessions.css` (`.cl-sx-meta`, `.cl-sx-acts`) and `styles.css` (`.ui-sp-tick`) keep dev's `calc(var(--t-press) * var(--ui-motion-scale, 1))` and its new `[data-motion="instant"]` rule, with ease-out instead of `ease` on those fades. - `28dfc73be`: the gate caught 4 new `opacity 160ms ease` fades from the themes work (`.cl-theme-name`, `.cl-glide` and their reduced-motion fallbacks) → `var(--ease-out)`, same 160 ms. **J455's blind spots** (`090f6795d`, red first; red log attached) - **CSS longhands:** `transition-`/`animation-` `duration`, `delay` and `timing-function` are now read. A timing-function longhand refuses `ease`/`linear`, because it names no property. - **WAAPI durations:** named constants and expressions are resolved. That includes comma declarations (`const POP = 160, SWAP = 140`), constants exported by other files, and both branches of a conditional. A duration the scan can't resolve is refused, not read as 0. - **Inline transitions:** `el.style.transition = …`, its longhands and `style.setProperty("transition", …)` are parsed like CSS, with `${CONSTANT}` in template strings substituted. - **Spring variables:** `visualDuration: VAR` and `{ visualDuration }` are resolved; a parameter is refused. Type annotations and comments are ignored. - Each blind spot has a fixture test. Before the change all four failed (`9 pass, 5 fail`; the fifth was the clusters fades). After it: `26 pass, 0 fail` across motion-speed, no-overshoot and spring. The "scan sees" test also checks that tooltip's `SWAP` resolves and that `` `width ${TRAVEL}ms ${EASE_OUT}` `` expands. **window-resize.ts:271 (the 570 ms lean-in): allow-listed, with the reason in the file.** It's a press-and-hold progress cue, not an enter or exit: - it lasts exactly the hold time (`HOLD_MS` = 450 ms, starting 120 ms in so a tap shows nothing); - the hold timer cancels it at 450 ms, so it never plays its nominal 570 ms; - the release reverses in 220 ms. Shortening it would make the cue finish before the hold counts. Two more allow-list entries, each with its reason: the FLIP reflow's `{ duration, easing: css }` (from `flipEasing`, which has its own test) and the windows prototype mock's stagger delays. **Runs on dev-02** - `verify quick` at 437a4a56a: all rows PASS (scope guard, migration names, Nix vendoring, VT source recipe, fmt, doc coverage, shape lints, doc comments on diff, fixed test waits, ui literals, tracer typecheck, D365 theme contrast audit). `QUICK_EXIT=0`. - keyboard-sweep, themes, terminal-theme and clusters-theme-transition specs at 437a4a56a: `26 passed, 7 skipped, 1 failed`. Theme unit tests: `8 pass, 0 fail`. - The failure is `terminal-theme.spec.ts:206 every curated theme recolors…`. On 090f6795d it failed at the selection colour (`Received: 109`, line 321); on 437a4a56a, earlier, waiting for the fixture to render (`outputSequence` 0, line 228). **It fails the same way on unmodified origin/dev a0f5a8aee** (`Received: 109`), and it tests the terminal renderer, not motion. - keyboard-sweep at 090f6795d failed once at 393×852 ("Open the command palette": `.cl-sx-meta`/`.cl-sx-acts` fading 200–325 ms after the key). Repeated 4× on the branch: `8 passed`, the same as dev. Dev's #427 follow-up (97928be8a, merged above) gives exactly those two elements a key-instant rule, and at 437a4a56a every keyboard-sweep test passes.
5.2 KiB
Author
Owner

scripts/verify fast at 437a4a56a: passed (compute01, through build-slot)

The log ends with the TS phase's last step (==> TypeScript protocol tests, 50 pass, 0 fail): every Rust row and every TS step passed. Full log attached (fast-437a4a56a.log; on compute01 at ~claude/.cache/codex-scratch/j428/fast-437a4a56a.log). Excerpts, verbatim:

+---------------------------+--------------+
| Rust gate                 | Result       |
+---------------------------+--------------+
| Nix Cargo vendoring       | PASS         |
| Nix release inputs        | PASS         |
| fmt                       | PASS         |
| migration names           | PASS         |
| migration tooling tests   | PASS         |
| VT native fmt             | PASS         |
| CLI plugin compile-out    | PASS         |
| clippy                    | PASS         |
| SSH runtime test helper   | PASS         |
| real-binary fixture paths | PASS         |
| Traefik component manifest | PASS         |
| release component publication | PASS         |
| test                      | PASS         |
| SSH inode reuse fixture   | PASS         |
| shim process-per-user     | PASS         |
| cargo-deny                | PASS         |
| gate architecture         | PASS         |
| doc coverage ratchet      | PASS         |
| shape lints off (D351)    | PASS         |
| doc comments on diff      | PASS         |
| fixed test waits          | PASS         |
| privileged integration    | SKIP         |
| probe release exclusion   | PASS         |
| nightly fuzz reporter     | PASS         |
| nightly browser suite reporter | PASS         |
| static builds             | PASS         |
+---------------------------+--------------+


test/motion-speed.test.ts:
(pass) the scan sees the motion it is meant to police [102.17ms]
(pass) durations scaled by #427's --ui-motion-scale are read at scale 1, not as 0 [9.26ms]
(pass) CSS longhands are read: transition-duration, -delay and -timing-function (and the animation ones) [0.56ms]
(pass) Web Animation durations given by a named constant or an expression are resolved [3.27ms]
(pass) transitions set inline from script are read, constants in template strings included [6.10ms]
(pass) a spring duration given through a variable is resolved, and an unknown one is refused [3.15ms]
(pass) no spring lasts more than 300 ms [102.86ms]
(pass) no CSS transition or animation lasts more than 300 ms, delay included [10.48ms]
(pass) every looping CSS animation is on the allow-list [9.40ms]
(pass) nothing that moves or fades in CSS starts slowly [10.04ms]
(pass) no Web Animation lasts more than 300 ms or starts slowly [81.89ms]
(pass) no transition set from script lasts more than 300 ms or starts slowly [90.75ms]
(pass) the FLIP reflow curve is the product's ease-out and lasts its duration [0.11ms]
(pass) a Spring from rest is ease-out: a fifth of the way in the first frame, half by a third of its time, done at its time [0.55ms]


PT_INTERP: absent; size: 28330680 bytes; baseline 28268440; delta +62240 bytes (report only, D243 amended 2026-11-01)
PT_INTERP: absent; size: 25429976 bytes; baseline 25378504; delta +51472 bytes (report only, D243 amended 2026-11-01)

 706 pass
 0 fail
 26537 expect() calls
Ran 706 tests across 107 files. [34.66s]


 50 pass
 0 fail
 77253 expect() calls
Ran 50 tests across 13 files. [7.44s]
  • terminal-theme.spec.ts:206 is not part of verify fast (it runs no browser specs), so it does not appear. A dev comparison isn't needed for this run.
  • Static sizes vs the current baseline: x86_64 +62 240 B, aarch64 +51 472 B (report only).
  • Target dirs deleted on compute01 and dev-02. Not merged.
## `scripts/verify fast` at `437a4a56a`: passed (compute01, through build-slot) The log ends with the TS phase's last step (`==> TypeScript protocol tests`, 50 pass, 0 fail): every Rust row and every TS step passed. Full log attached (`fast-437a4a56a.log`; on compute01 at `~claude/.cache/codex-scratch/j428/fast-437a4a56a.log`). Excerpts, verbatim: ``` +---------------------------+--------------+ | Rust gate | Result | +---------------------------+--------------+ | Nix Cargo vendoring | PASS | | Nix release inputs | PASS | | fmt | PASS | | migration names | PASS | | migration tooling tests | PASS | | VT native fmt | PASS | | CLI plugin compile-out | PASS | | clippy | PASS | | SSH runtime test helper | PASS | | real-binary fixture paths | PASS | | Traefik component manifest | PASS | | release component publication | PASS | | test | PASS | | SSH inode reuse fixture | PASS | | shim process-per-user | PASS | | cargo-deny | PASS | | gate architecture | PASS | | doc coverage ratchet | PASS | | shape lints off (D351) | PASS | | doc comments on diff | PASS | | fixed test waits | PASS | | privileged integration | SKIP | | probe release exclusion | PASS | | nightly fuzz reporter | PASS | | nightly browser suite reporter | PASS | | static builds | PASS | +---------------------------+--------------+ test/motion-speed.test.ts: (pass) the scan sees the motion it is meant to police [102.17ms] (pass) durations scaled by #427's --ui-motion-scale are read at scale 1, not as 0 [9.26ms] (pass) CSS longhands are read: transition-duration, -delay and -timing-function (and the animation ones) [0.56ms] (pass) Web Animation durations given by a named constant or an expression are resolved [3.27ms] (pass) transitions set inline from script are read, constants in template strings included [6.10ms] (pass) a spring duration given through a variable is resolved, and an unknown one is refused [3.15ms] (pass) no spring lasts more than 300 ms [102.86ms] (pass) no CSS transition or animation lasts more than 300 ms, delay included [10.48ms] (pass) every looping CSS animation is on the allow-list [9.40ms] (pass) nothing that moves or fades in CSS starts slowly [10.04ms] (pass) no Web Animation lasts more than 300 ms or starts slowly [81.89ms] (pass) no transition set from script lasts more than 300 ms or starts slowly [90.75ms] (pass) the FLIP reflow curve is the product's ease-out and lasts its duration [0.11ms] (pass) a Spring from rest is ease-out: a fifth of the way in the first frame, half by a third of its time, done at its time [0.55ms] PT_INTERP: absent; size: 28330680 bytes; baseline 28268440; delta +62240 bytes (report only, D243 amended 2026-11-01) PT_INTERP: absent; size: 25429976 bytes; baseline 25378504; delta +51472 bytes (report only, D243 amended 2026-11-01) 706 pass 0 fail 26537 expect() calls Ran 706 tests across 107 files. [34.66s] 50 pass 0 fail 77253 expect() calls Ran 50 tests across 13 files. [7.44s] ``` - `terminal-theme.spec.ts:206` is not part of `verify fast` (it runs no browser specs), so it does not appear. A dev comparison isn't needed for this run. - Static sizes vs the current baseline: x86_64 +62 240 B, aarch64 +51 472 B (report only). - Target dirs deleted on compute01 and dev-02. Not merged.
Sign in to join this conversation.
No labels
launch-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/infraternal#471
No description provided.