Skip to content

Details icon flickers on touch devices when a timeline line's text ends with a link #26645

Open

Description

@amozeo

What happened

On a phone, the "Waiting on subagent … · Show · Stop" line at the end of the timeline flickered continuously. The ⓘ details button appeared and disappeared many times a second; when it was shown, "Show" was clipped to "Shov". It never settled while the subagent was pending.

flicker-row.mp4

Diagnosis

NoticeText (apps/web/src/components/chat/ComposerBannerStack.tsx) shows its details button when any element in the text has scrollWidth > clientWidth + recoveredWidth, where recoveredWidth is the button's own width + 4px.

#25401 gave inline buttons on ThreadNow lines a finger-sized hit area on coarse pointers: a relative button with an ::after at -inset-x-1.5 (apps/web/src/components/chat/ThreadNow.tsx). scrollWidth counts that absolutely positioned layer. When an inline button is the last thing in the text, its hit area reaches 6px past the text's right edge, so the text reports overflow even though it fits:

  1. Button hidden: 6px "overflow" > 0, so the button is shown.
  2. Button shown: 6px plus the few pixels it now clips < ~28px recovered, so the button is hidden.
  3. Each toggle resizes the text, the ResizeObserver measures again, and it loops.

Desktop pointers do not get the hit area, so it only happens on touch. Besides the visual flicker, it re-renders continuously while the line is up.

Status on main (9e09926): #25299 moved the background-work link out of the text and into the actions ("Open · Stop"), so the recorded line no longer triggers this. The cause is unchanged, though, and the "Subagent needs input · Open question" line (childInputNowItem in ThreadActivityBanners.tsx) still ends its text with an inline button. From the source it should loop the same way on touch while a subagent waits for the user; that line was not observed on a device.

Suggested fix: decide overflow from the laid-out content (a Range over the element's contents) instead of scrollWidth, which also counts positioned layers. A fix with a regression browser test is prepared and can be opened as a PR.

#22974 adds hysteresis to the same NoticeText measurement for a different loop. For this case it would stop the flicker but leave a permanent ⓘ and clipped link text, because the overflow it reacts to is not real.

Steps to reproduce

As recorded, on v0.1.3:

  1. Open the web app on a touch device (or Chrome DevTools device emulation with a coarse pointer).
  2. In a thread, start a single background subagent so the line reads "Waiting on subagent · Show · Stop".
  3. Use a chat width where that line fits without truncation.
  4. Watch the space after "Show": the ⓘ button keeps toggling.

On main, the expected trigger is a subagent asking a question, so the line reads "Subagent needs input · Open question" at a width where it fits.

Deterministic repro without a touch device: render NoticeText in a fixed-width flex row with text that fits and a trailing relative button whose ::after is absolute with a negative horizontal inset larger than the details button. The details button appears even though nothing is cut off.

Version

v0.1.3 (server reports v0.1.3+12: a local branch adding server-only commits; the web code involved is unchanged from v0.1.3). The cause is still present on main at 9e09926.

Environment

Server: Linux x64 desktop. Client: Chromium-based mobile browser on Android, connecting to the server's web UI over the LAN.

Evidence

Screen recording of the line, cropped to the row (attached, 2.5s, 152 frames). The row alternates between "Show ⓘ · Stop" and "Show · Stop" in a steady two-frames-on, two-frames-off pattern for the whole clip.

Related issues

#22974 and #26071 fix a different NoticeText loop in composer banners (actions wrapping onto a second row); neither covers the touch hit area on ThreadNow lines. #25299 changed the recorded line's layout and incidentally removed that trigger.

Fix applied or workaround

Nothing was changed on the machine. No user workaround short of using a fine pointer.

Filed by

Claude Code (claude-opus-5-5) via T3 Code, following the t3 triage playbook.

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

    via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions