Skip to content

fix(json-chat): restore Chat-mode auto-scroll on streaming + send - #89

Merged
frenchie4111 merged 1 commit into
mainfrom
fix-chat-autoscroll
May 25, 2026
Merged

fix(json-chat): restore Chat-mode auto-scroll on streaming + send#89
frenchie4111 merged 1 commit into
mainfrom
fix-chat-autoscroll

Conversation

@frenchie4111

Copy link
Copy Markdown
Collaborator

Summary

Regression introduced in #84 (1634f1d): Chat mode (json-mode) stopped auto-scrolling to the bottom — neither on streaming token deltas nor when the user sent a message.

PR #84 added the entriesHydrated gate to suppress the empty-state flash, but on a fresh chat mount (entries=[], !busy, entriesHydrated=false) the entire ternary returns null. That leaves the scroll container with no firstElementChild, so the useLayoutEffect that attaches the ResizeObserver — mounted once with empty deps — finds nothing to observe and never re-runs after entries hydrate. From that point on, streaming token deltas grow the content but never snap-to-bottom.

Fix: wrap the conditional in a stable min-h-full flex flex-col div so the RO always has a firstElementChild to attach to. The empty-state's previous min-h-full referenced the scroll container; with the wrapper in between, switch it to flex-1 so it fills the wrapper's column and the sparkle card stays centered.

Test plan

  • npx electron-vite build passes
  • npx vitest run passes (3 stale-out/ failures are unrelated to this change — source tests green)
  • Manual: brand-new Chat tab — sparkle empty-state still centers correctly
  • Manual: send a message — chat snaps to bottom as tokens stream in
  • Manual: existing chat with history — lands at the bottom on mount
  • Manual: scroll up mid-stream — position stays put (user-scroll-up gesture still respected)

🤖 Generated with Claude Code

PR #84 added the entriesHydrated gate to suppress the empty-state flash,
but the gating made the entire ternary return null on a fresh chat mount
(entries=[], !busy, entriesHydrated=false). The scroll container was
left with no firstElementChild, so the useLayoutEffect that attaches the
ResizeObserver — mounted once with empty deps — found nothing to
observe and never re-ran after entries hydrated. From that point on
streaming token deltas grew the content but never triggered the
snap-to-bottom.

Wrap the conditional in a stable min-h-full flex-col div so the RO
always has a firstElementChild. The empty-state's previous min-h-full
referenced the scroll container; with a wrapper in between, switch it
to flex-1 so it fills the wrapper's column and the sparkle card stays
centered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@frenchie4111
frenchie4111 merged commit 657b7fc into main May 25, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant