Two rules, picked to feel natural:
1. Conversation just opened (peer_primary changed) → always scroll to
bottom. You expect to see the latest exchange first.
2. New message landed in the current conversation → scroll to bottom
IF you were already within 120px of the bottom. If you're scrolled
up reading older history, the auto-scroll doesn't yank you back
down. (Slack / Telegram / iMessage all do this; getting yanked out
of history when you're searching for something is infuriating.)
Implementation: a single $effect tracks activeConv.messages.length +
activeConv.peer_primary. Compares against two cursor vars (prevPrimary,
prevMessageCount) to distinguish "opened a new conversation" from
"new message in current one". queueMicrotask after the DOM updates so
scrollHeight reflects the just-rendered message.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>