Skip to content

DMs: animate sent messages in and scroll to them in one motion - #12302

Draft
mozzius wants to merge 7 commits into
mainfrom
samuel/dm-send-animation
Draft

mozzius wants to merge 7 commits into
mainfrom
samuel/dm-send-animation

Conversation

@mozzius

@mozzius mozzius commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

Sending a DM now plays out in clear steps. The composer clears, the list follows it down, and then the message rises in while the list makes room for it. This replaces the per-frame scroll burst. Each step has a single owner, so no two systems fight over the scroll position.

Before (multi-line send):

s2-before.mp4

After:

trim.E0437553-9254-4688-AE15-60B8A727B1EA.MOV

What was wrong

I measured the old behaviour frame by frame on an iOS 27 simulator, using temporary logging that isn't part of this PR.

  • Late scroll. The send burst re-issued an animated scrollToOffset every frame for 200ms, and each call restarts the UIKit animation. The list crept about 1px per frame while the new bubble sat behind the composer. It only really scrolled at about +300ms.
  • Fighting on multi-line sends. The list followed the shrinking composer down a frame or two late, the bubble then sat hidden behind it, and the late scroll brought it back up.
  • Remount on confirmation. About 500ms after sending, the server confirmation remounted the row, because its key changed from the temporary id to the real one.
  • A stale comment. The comment claiming onContentSizeChange doesn't fire for the send (APP-2223) is wrong; it fires on both iOS and Android.

Changes

  • Stable keys (agent.ts): a confirmed message keeps its pending key (optimisticKeys.get(id) ?? id), so the row never remounts.
  • Entrance (MessageItem): rows sent in the last 3s mount with FadeInDown.duration(220). Existing rows and loaded history don't animate.
  • Pending to sent (MessageItem): a pending bubble is now the sent bubble at 60% opacity, instead of primary_300. It eases to full opacity with a Reanimated CSS transition once confirmed. The old token was lighter than sent in light mode but darker in dark and dim, because those themes invert the palette. Fading toward the background reads the same way in every theme, and pending emoji-only messages fade too, where before they had no pending state.
  • Send steps (MessageComposer, MessagesList):
    1. The composer clears. Just before clearing, it reports how much it's about to shrink, through the new onWillSendMessage(composerShrink).
    2. The list follows the composer down. KeyboardChatScrollView already does this whenever the composer's height changes; we pass it the new height immediately instead of waiting for onLayout. The composer only predicts text-only shrinks. A reply or embed preview is removed by a re-render that lands later, so in that case, and for pre-filled drafts, the list follows the measured layout.
    3. The list scrolls to the message. Once our pending row has laid out, one animated scroll lifts the list to it.
  • The finger wins. A finger on the list blocks every programmatic scroll: the send scroll and the existing "follow new messages" auto-scroll.
  • Far-up sends. From more than a screen up, a send jumps straight to the end. With disableVirtualization, rows appended at the end only render when you're near the end, so the message would otherwise never mount.
  • React Compiler. onContentSizeChange and onScroll are now plain functions. With the manual memoization, these edits made the React Compiler skip the whole component (PreserveManualMemo).

Testing

I ran Argent QA against a test chat, comparing against a baseline of the old code:

  • iOS 27.0 simulator. 22 of 22 sends without a finger on the list ended exactly at the end, with the usual gap. That covers single-line, multi-line, reply, pre-filled draft, keyboard-closed, two sends in a row, and far-up and top-of-chat sends.
    • The list starts moving about 110–175ms after send, against about 300ms before.
    • There's no overshoot and no remount.
    • A drag at 50–500ms after tapping send follows the finger cleanly, with no fighting.
  • Android emulator (Pixel 8, API 34). 9 of 9 sends without a finger on the list ended at the end. That includes the scrolled-up and keyboard-closed sends, where an earlier iteration of this PR failed on the heavily loaded emulator.
    • A drag mid-send follows the finger, with no programmatic scroll.
  • Reply sends: the composer collapses first and the list follows about 4 frames later, with no gap.
  • Checks. pnpm typecheck, pnpm lint and the DM-related Jest tests pass.

Not checked:

  • Web. On web, step 3 uses onLayout and scrollToOffset; there's no KeyboardChatScrollView there.
  • The pending opacity fade, on any device. An earlier version of the PR eased backgroundColor with the same kind of CSS transition and ran smoothly in most sends (2 of 4 iOS sends snapped or stalled under host load). The opacity version is only typechecked and linted so far.

🤖 Generated with Claude Code

mozzius and others added 3 commits November 1, 2026 16:01
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor
Size Old size New size Diff
Uncompressed 9.65 MB 9.65 MB +2.09 KB (0.02%)
Gzip 2.83 MB 2.83 MB +619 B (0.02%)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

The OTA deployment for this PR was successful! You may now apply it by either scanning the QR code or opening the deep link below in your browser:

QR code for the PR OTA deployment

bluesky://intent/apply-ota?channel=pull-request-12302&releaseVersion=1.138.0&iosBuildNumber=2254&androidBuildNumber=1697

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch had an error being deployed

1 failed deployment
samuel/dm-send-animation - social-app PR #12302 — d17898ae Deployed Nov 3, 2026 by arcalinea
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.

2 participants