Skip to content

Transcript: ~72–600px scroll-up dead-zone — unpinned + not following + new-message badge hidden (reveal-ratio default) #188

Description

@danljungstrom

Correction (maintainer note): The original version of this issue claimed ChatList.tsx imported three scroll modules that "were never committed" and that dev was broken. That was wrong — it was diagnosed from a local checkout ~40 commits behind origin/dev. The three modules (transcriptAutoFollowGate.ts, transcriptBottomFollowMode.ts, transcriptFlashListBottomMaintenance.ts) were a transient gap at 0a6a47eba and were committed in the very next ChatList commit cc8027b60 (2026-06-12), with tests. Current dev compiles. This issue has been rewritten below with the real, current root cause.

Summary

On current dev, the transcript open-at-bottom and auto-follow wiring is correct. However the "jump to bottom" button + numbered new-message badge has a large reveal dead-zone: when the user scrolls up only a small amount, the list is already unpinned (auto-follow stopped) but the button/badge does not render, so new streaming messages neither follow nor visibly signal that they arrived.

Symptoms (reported)

  • New info posts while reading near the bottom → list does not follow along.
  • No clear indication that new data appeared (numbered badge not shown).
  • (Open-at-bottom: see "Not reproduced in wiring" below.)

Root cause — reveal threshold ≫ pin-release threshold

Two thresholds govern the unpinned state, and they diverge by an order of magnitude:

  • Pin releases (auto-follow stops, isPinned → false, badge starts counting) at distanceFromBottom > pinThresholdPx, default 72px (transcriptScrollPinOffsetThresholdPx).
  • Button/badge reveals at distanceFromBottom >= jumpRevealOffsetThresholdPx:
// apps/ui/sources/components/sessions/transcript/ChatList.tsx:2570
const jumpRevealOffsetThresholdPx = Math.max(pinThresholdPx, Math.trunc(listLayoutHeight * jumpRevealViewportRatio));
// :2792
const showJumpToBottom = jumpEnabled && !scrollPin.isPinned && jumpToBottomDistanceFromBottom >= jumpRevealOffsetThresholdPx;

With defaults pinThresholdPx = 72 and transcriptScrollJumpToBottomRevealViewportRatio = 0.75, on an ~800px viewport jumpRevealOffsetThresholdPx = max(72, 600) = 600px.

resolveNextJumpToBottomDistanceVisibilityState (scroll/jumpToBottomVisibilityDistanceState.ts) snaps the committed reveal distance to 0 whenever it is below threshold, so anywhere in ~72–600px from the bottom:

  • the transcript is unpinned → MVCP auto-follow does not pull new content down,
  • newActivityCount does increment (scroll/transcriptScrollPinController.ts:71-79, only while !isPinned),
  • but showJumpToBottom is false, so neither the button nor its badge renders (ChatList.tsx:9944).

Net: a ~528px band where the user is unpinned with no visible affordance and no follow — exactly the reported symptoms. The badge reveal is gated on the same large ratio as the button, so the "new data arrived" signal is suppressed precisely in the range a reader is most likely to be in.

Platform scope

Platform-agnostic: jumpRevealOffsetThresholdPx (:2570) feeds the shared showJumpToBottom (:2792) on native (flash_v2_inverted, default), web (flash_v2_standard), and legacy FlatList alike.

Not reproduced in wiring (open-at-bottom)

Initial pin state is seeded from resolveSessionEntryBottomFollow(...) at ChatList.tsx:1554, 2010, 2049-2063; a fresh session follows bottom; native arms MVCP startRenderingFromBottom, web calls pinToBottom('initial-open'). Wiring is correct (covered by ChatList.initialScrollBehavior.test.tsx). If "doesn't open at bottom" still reproduces on a current build, the likely cause is a persisted SessionEntryViewportSnapshot with source==='observed', isPinned:false (legitimately makes the open effect early-return at :8409) — a snapshot-persistence question, tracked separately if confirmed.

Suggested fix levers

  1. Decouple badge reveal from the large button reveal ratio — show the numbered new-message badge as soon as newActivityCount >= minNewCount while unpinned, independent of jumpRevealViewportRatio.
  2. Or lower transcriptScrollJumpToBottomRevealViewportRatio default toward parity with pinThresholdPx so the reveal band matches the unpin band.
  3. Add a test covering the 72–600px dead-zone (existing ChatList.jumpToBottom.test.tsx scrolls past the reveal threshold, so it does not exercise this band).

Evidence basis

Read against an origin/dev snapshot (1808b7f57, 2026-06-19). Latest deployed UI build ui-web-v0.2.7-dev.175 (2026-06-20) is post-fix, so this is current behavior, not a stale build. Verification commands (yarn typecheck/test) not run locally — node_modules incomplete in the investigation environment; findings are static + git-evidence based.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions