Skip to content

Safari: Shaking chat threads on specific zoom levels, can freeze Safari. #27196

Open

Description

@bramtechs

What happened

In Safari, a thread gets into a "shaking" state: part of the chat jumps back and forth between two layouts many times per second, and sometimes the Safari tab freezes afterwards. One thread with a pending question now does this every time it is opened, so it can't be used from Safari at all. Reloading the page doesn't help.

Diagnosis

From frame-by-frame analysis of two screen recordings, compared with the source at the matching nightly (main @ 9e09926, 2026-10-28).

1. Pending question card (the thread that won't open). Only the "AskUserQuestion" card above the composer changes. It alternates between two states:

  • A: no vertical overflow. The question fits on one line.
  • B: the scroll area reports data-has-overflow-y. The content gets about 8–10 CSS px narrower, which matches pe-2. The question wraps to two lines, the card grows about 30 CSS px taller, and the scroll thumb appears.

The card is ComposerPendingUserInputPanel inside ComposerBanner.Scroll (apps/web/src/components/chat/ComposerBanner.tsx:237-248):

<ScrollArea radius="none" scrollFade className={cn("h-auto max-h-[min(24rem,40dvh)]", className)}>
  <div className="[[data-has-overflow-y]>&]:pe-2">{children}</div>
</ScrollArea>

Base UI sets hasOverflowY from clientHeight < scrollHeight in its viewport's computeThumbPosition. That runs from a ResizeObserver on the viewport, after getAnimations() settle, and on scroll. Because the padding depends on the overflow flag, the content width depends on the overflow flag too. When the question's text sits right at a wrap point, adding the padding changes the wrapping, which changes the height, which changes the overflow flag. In Safari this never settles: the state flips every 2–20 frames for the whole recording, including after a full reload.

In state B the card is taller than in state A and still below its max-h cap, yet the viewport reports overflow. So Safari's sizing of the h-full max-h-[inherit] viewport inside the auto-height root, while that root is growing, probably plays a part. I could not confirm this without Safari devtools.

A possible direction: make the padding independent of the overflow state (always reserve it), or add hysteresis, so overflow can't change the width that decides overflow.

2. Timeline lane (earlier recording, same symptom). In a different thread (the AskUserQuestion was already answered and the turn was still running), the whole message lane shrank by 15 CSS px on each side and grew back, every 1–4 frames. The composer width stayed the same. In the narrow frames, a paragraph re-wrapped to one extra line and overlapped the next row, because LegendList hadn't repositioned rows for the new height yet.

The only thing that changes the timeline's side padding symmetrically without affecting the composer is --chat-timeline-gutter. .messages-timeline-scroll padding is base − var(--chat-timeline-gutter) (apps/web/src/index.css:2512). ChatCanvas.tsx:58-60 measures it as (offsetWidth − clientWidth) / 2 of the LegendList scroll node from a ResizeObserver and writes it to the canvas store, so it can feed back into the layout it measures.

Caveat: this Mac uses overlay scrollbars (trackpad, "Show scroll bars: Automatically"), so a 15px-per-side gutter is unexpected. The exact trigger here is not confirmed. Even so, it is the same class of measure → width → re-wrap → measure loop, and it may be worth checking alongside (1).

Freeze. This is inferred, not profiled. Each flip re-wraps text, re-runs ResizeObservers and React commits, and in (2) makes LegendList re-measure rows, continuously and with no end.

Steps to reproduce

Not reduced to a minimal case. On the reporter's machine it is deterministic for one thread:

  1. Use Safari 27 on macOS with a trackpad and "Show scroll bars: Automatically".
  2. Open a thread that has a pending AskUserQuestion whose question text wraps right at the card's width: it fits on one line, but would wrap if the content lost about 8px. Resizing the window should let you find that width for any question.
  3. The question card flips between one-line and two-line layouts several times per second. After a while the tab may freeze.

Version

0.1.4-nightly.20261028.3841 (pending-question card recording); 0.1.2-nightly.20261022.3526 (timeline-lane recording)

Environment

Client: Safari 27.0.1 (22625.1.29.11.28), macOS 27.0.1 (26A434), MacBook Air M1, trackpad only, "Show scroll bars: Automatically". Server: Windows 11, web UI served over Tailscale Serve.

Evidence

# Recording 2 (0.1.4 nightly), 60 fps, after page reload. Per-frame state of the question card
# (W = question on one line, N = wrapped / has-overflow-y), first 120 frames:
WWWWNNNNNNNNNNNNNNNNNNNWWWWWWWWWWWWWWWNNNNWWWWWWWWWWWWWWWNNNNNNNNNNNNNNNNNNWWNNNNNNNNNNNNNNNNWWWWWWNNNNNNNNNNWWNNNNNNNNWW
# Only the card region changes between frames (per-pixel change heatmap); bubble and timeline are static.

# Recording 1 (0.1.2 nightly), 60 fps. Horizontal edges of the user bubble (device px, 2x display):
# alternates (461,1861) <-> (479,1831): 190 changes in 357 frames, for the whole 6 s clip.
# Timeline content edge moves 30 device px (15 CSS px) on BOTH sides; composer edges unchanged.

Screen recordings (Safari 27.0.1, 60 fps):

Related issues

#14959 (closed): an earlier render loop between ThreadDetailsCard and resolveChatCanvasLayout. It is the same measure-and-feed-back pattern through the chat canvas, with a different trigger. #26958 changed ComposerBanner.tsx after this build but does not touch Scroll. I found no existing report of this shaking or freeze.

Fix applied or workaround

Nothing was changed on the machine. Suggested workarounds (untested): answer or dismiss the pending question from another client (desktop app, Chrome, mobile), or change the Safari window width or zoom (⌘− / ⌘+) before opening the thread so the question no longer wraps at the boundary.

Filed by

Claude Code (claude-opus-5-5) via t3 triage, run inside T3 Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions