Commit ef2e325
Fix thinking-block collapse and Safari layout shifts (#2426)
* Fix thinking-block collapse and Safari layout shifts
Reproduced three shift mechanisms around reasoning blocks with harness
probes before fixing:
- When thinking outgrew the send-anchor's fill slack, its ~300px
collapse at answer-start re-inflated the spacer and re-anchored the
sent message — a measured 267px scroll of the view mid-read. The
spacer is now monotonic within a turn (fresh turns still inflate, the
composer-clearance floor still wins), so the collapse rides the
controller's clamp path instead, which keeps everything below the
block viewport-stable.
- Safari has no native scroll anchoring (overflow-anchor unsupported),
so any above-viewport shrink — a collapsing thinking block, a late
image — shoved a detached reader's text by the full delta (350px in
the probe; 0px on Chrome, whose native anchoring compensates). Where
anchoring is unavailable, the chat glue now tracks the message at the
viewport top while detached (binary search per scroll event) and
restores its position after content resizes through a new
attribution-safe controller adjustment.
- The collapse itself was a single-frame teleport, and for the common
lone-thinking case it couldn't even be animated: the answer-start
branch flip destroyed the expanded component and mounted a collapsed
one. ChatMessage now renders one renderUnits template for both the
streaming and settled phases, keeping the lone thinking block's
instance alive across the flip, and its expand/collapse is a height
slide (220ms, disabled under prefers-reduced-motion). Side effect:
earlier multi-block tool runs stay summarized while a later step
streams instead of re-expanding flat.
Two regression tests pin the fixed behaviors (in-turn collapse keeps
content below stable; simulated-Safari detached reader stays put
through an above-viewport collapse). WebKit itself cannot launch in
this container, so Safari is simulated by forcing overflow-anchor off —
the only engine difference relevant to these bugs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0148VmxBDazfhczvfE122XVW
* Anchor manual scroll compensation to in-message descendants
The Safari manual-anchoring fallback tracked only top-level message
wrappers. With the viewport top inside a long assistant message, a
thinking block collapsing (or an image loading) above the reading
position within that same message leaves the wrapper's top unchanged —
delta reads zero and the jump survives. Descend from the straddling
wrapper to the deepest element at the viewport top (linear scans below
the top level, where children are few and can sit out of flow), which
is what native scroll anchoring does. Regression test covers the
in-message collapse.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0148VmxBDazfhczvfE122XVW
* Re-anchor manual scroll compensation when the tracked node is replaced
Markdown worker swaps and streaming re-renders replace exactly the deep
paragraph nodes the manual anchor tracks. A dead node made compensation
early-return, silencing it until the next user scroll — reintroducing
the Safari jump for rendered/streaming markdown. The anchor is now a
deepest-first ancestor chain: compensation falls back to the nearest
surviving ancestor (still covering every shift above it) and re-resolves
a fresh deep anchor after every pass, so a replacement can never leave a
stale chain behind. Regression test replaces the anchored node in the
same pass as an above-viewport shrink and checks the following pass too.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0148VmxBDazfhczvfE122XVW
* Collapse long thoughts in their capped streaming shape
When loading flipped false, the template swapped the reasoning content
from the capped streaming viewport to the uncapped settled prose in the
same flush, BEFORE the auto-collapse effect flipped isOpen — so the
slide-out of a long thought started from the full reasoning height
(potentially thousands of px), an expand-then-collapse bounce worse
than the jump the animation replaced. The open/collapse tracker now
runs pre-render ($effect.pre), so the outro starts from the still-capped
DOM, and the inner branch keeps the streaming shape whenever the block
is closing, so no mid-outro re-render can grow the collapsing box.
The regression test asserts the settled prose never appears during the
collapse and the box never exceeds its streaming height (shape-based
assertions: Tailwind's cap utilities don't load in the browser test
environment, which is also why the pre-existing mask test fails there).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0148VmxBDazfhczvfE122XVW
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent be4e935 commit ef2e325
6 files changed
Lines changed: 481 additions & 81 deletions
File tree
- src/lib
- components/chat
- utils/scroll
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
410 | 410 | | |
411 | 411 | | |
412 | 412 | | |
413 | | - | |
414 | | - | |
415 | | - | |
416 | | - | |
417 | | - | |
418 | | - | |
419 | | - | |
420 | | - | |
421 | | - | |
422 | | - | |
423 | | - | |
424 | | - | |
425 | | - | |
426 | | - | |
427 | | - | |
428 | | - | |
429 | | - | |
430 | | - | |
431 | | - | |
432 | | - | |
433 | | - | |
434 | | - | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
435 | 426 | | |
436 | 427 | | |
437 | | - | |
438 | | - | |
439 | | - | |
440 | | - | |
441 | | - | |
442 | | - | |
443 | | - | |
444 | | - | |
445 | | - | |
446 | | - | |
447 | | - | |
448 | | - | |
449 | | - | |
450 | | - | |
451 | | - | |
452 | | - | |
453 | | - | |
454 | | - | |
455 | | - | |
456 | | - | |
457 | | - | |
458 | | - | |
459 | | - | |
460 | | - | |
461 | | - | |
462 | | - | |
463 | | - | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
464 | 459 | | |
465 | | - | |
466 | | - | |
467 | | - | |
468 | | - | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
469 | 464 | | |
470 | 465 | | |
471 | 466 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
2 | 5 | | |
3 | 6 | | |
4 | 7 | | |
| |||
9 | 12 | | |
10 | 13 | | |
11 | 14 | | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
12 | 21 | | |
13 | 22 | | |
14 | 23 | | |
| |||
18 | 27 | | |
19 | 28 | | |
20 | 29 | | |
21 | | - | |
22 | | - | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
23 | 37 | | |
24 | 38 | | |
25 | 39 | | |
| |||
63 | 77 | | |
64 | 78 | | |
65 | 79 | | |
66 | | - | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | | - | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
75 | | - | |
76 | | - | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
77 | 103 | | |
78 | | - | |
79 | | - | |
| 104 | + | |
80 | 105 | | |
81 | 106 | | |
82 | 107 | | |
83 | | - | |
84 | | - | |
85 | | - | |
86 | | - | |
87 | | - | |
88 | | - | |
89 | | - | |
90 | | - | |
| 108 | + | |
| 109 | + | |
91 | 110 | | |
92 | 111 | | |
93 | 112 | | |
| |||
Lines changed: 32 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
35 | 67 | | |
36 | 68 | | |
37 | 69 | | |
| |||
0 commit comments