Skip to content

fix(frontend): clear programmatic scroll flag when a content-fits send is a no-op - #238

Open
detail-app[bot] wants to merge 1 commit into
devfrom
detail/bug-fix/fix-frontend-clear-programmatic-scroll-flag-when-a-751bfd
Open

fix(frontend): clear programmatic scroll flag when a content-fits send is a no-op#238
detail-app[bot] wants to merge 1 commit into
devfrom
detail/bug-fix/fix-frontend-clear-programmatic-scroll-flag-when-a-751bfd

Conversation

@detail-app

@detail-app detail-app Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Detail bug report: View on Detail

Bug

On a short/new conversation whose content fits the viewport (scrollHeight ≤ clientHeight), scrollOnce() calls performScroll(), whose scrollTo({ top: scrollHeight }) clamps to the current position — a no-op that fires 0 scroll/scrollend events. With no event to clear it via onUserScroll's near-bottom branch, programmaticScrollActive stays stuck true for the rest of the stream. Any later scroll event with no intent — e.g. keyboard Tab focus into an off-screen link, which the browser scrollIntoViews without firing wheel/touch/pointer or a USER_SCROLL_KEYS keydown — hits onUserScroll's branch 3 but is gated off by the stuck flag, so follow never disengages. At stream end, revealFinalContentIfFollowing() yanks the viewport back to the bottom and suppresses the unseen-content indicator. Introduced in 30ac922.

Fix

In performScroll(), track a sticky movedDuringScroll accumulator across reconciliation passes — set true only when a pass will actually move the container (scrollTop + clientHeight < scrollHeight). After the loop, clear programmaticScrollActive only when every pass was a no-op. Real smooth scrolls keep the flag raised so their in-flight scroll events stay shielded (preserving the intentional design guarded by the existing intermediate-smooth-scroll test); no-op sends — which fire no events at all — get the flag cleared so a later branch-3 entry can disengage follow. This mirrors the synchronous reset followActiveStream already uses for its behavior:"instant" scrolls, adapted for performScroll's smooth-scroll shielding requirement.

Testing

  • Unit tests (jsdom): added 3 cases to createScrollAnchor.svelte.test.ts — a regression guard for the no-op send + no-intent scroll-away (the exact bug), a mixed-pass guard (no-op pass 0 + real-move pass 1 keeps the flag raised to shield in-flight events), and an all-no-op guard (every pass clamps → flag clears → follow disengages). Verified the two no-op guards fail on the unmodified source (yank to the bottom) and pass with the fix; the mixed-pass guard passes both ways, so it is not a tautology. The full existing suite stays green, and the entire frontend unit suite passes (355 tests across 31 files).
  • Typecheck, lint, and format: npm run validate is clean — svelte-check reports 0 errors/0 warnings, oxlint (regular and type-aware), eslint (zod), and ast-grep all pass, and oxfmt --check reports all files correctly formatted.
  • Real-browser end-to-end (Chromium, against the actual composable via Vite + the Svelte plugin): an ephemeral probe reproduces the four-step scenario — content-fits no-op send → stream grows an off-screen <a> → real Tab focus into the link (no-intent scroll) → stream-end reveal. On the unmodified source it yanks scrollTop 1882 → 2463 (the bottom) with the indicator suppressed; on the fixed source the viewport stays at 1882 and the indicator rises. Two supporting probes confirmed the preconditions jsdom can't establish: a content-fits no-op scrollTo fires 0 scroll/scrollend events, and Tab into an off-screen <a> fires a scroll with no pointerdown/wheel/touchstart intent and lands below the 95% near-bottom threshold. These probes were run and removed per the repo's probes-are-not-tests policy.
  • Build: frontend Vite build and Gradle build -x test both succeed; the changed composable bundles cleanly into the Spring Boot jar.
  • Could not verify: a full manual app smoke (make dev) — it requires a live LLM gateway credential, a running Qdrant, and CSRF/Clerk auth unavailable in this sandbox. The real-browser probe against the actual composable above covers the same end-to-end guarantee (no end-of-stream yank after a Tab-focus scroll-away).

Closes #225


Automatic Fixes PRs can be configured here.

@detail-app
detail-app Bot requested a review from WilliamAGH September 6, 2026 14:00
@detail-app detail-app Bot added the bug Something isn't working label Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Detail Bug] Chat: Keyboard Tab focus can cause end-of-stream scroll yank to bottom on short/new conversations

1 participant