Repository navigation
feat(desktop): make sending a message smooth - #8215
Open
brucesantos wants to merge 4 commits into
Open
brucesantos wants to merge 4 commits into
brucesantos wants to merge 4 commits into
Conversation
…timeline A pending message always rendered with its own avatar, author, and "Sending…" header, then collapsed into its author group once the relay acknowledged it. Every send therefore flashed a header and shifted the timeline by one header height. Group pending rows exactly like acknowledged ones. A grouped pending row dims its body and keeps the send status for screen readers only, so the acknowledgement changes opacity but never the row's height. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: brucesantos <brucesantos@users.noreply.github.com>
Relay order breaks created_at ties by event id. An optimistic id sorts after every hex event id, so a message sent within the same second as the previous one rendered above it, took over the author header, and dropped back into place on acknowledgement. Treat a pending send as the newest event in its second. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: brucesantos <brucesantos@users.noreply.github.com>
The live-overlay test binds the send mutation's path. Keep only the pageless projection case from the earlier test, which it does not cover. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: brucesantos <brucesantos@users.noreply.github.com>
A sent message appeared in a single frame, and the bottom-pinned timeline jumped up by a whole row at the same moment, so every send read as a pop. A row that first mounts while its send is pending now opens once: it grows from zero height (grid track sizing) while fading in, over the shared standard motion tokens. The pinned timeline re-pins on each resize, so the earlier messages glide up instead of jumping. The animation avoids transforms and filters, which leave stale paint inside virtualized rows in WKWebView, and is skipped entirely under reduced motion. The row keeps its render key through the acknowledgement, so the entrance never replays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: brucesantos <brucesantos@users.noreply.github.com>
🔐 Codex Security Review
|
brucesantos
marked this pull request as ready for review
October 9, 2026 20:14
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A sent message appeared in a single frame, and the bottom-pinned timeline jumped up by a whole row at the same moment, so every send read as a pop (#8214).
A row that first mounts while its send is pending now opens into the timeline once:
SendEntrance(new,desktop/src/features/messages/ui/SendEntrance.tsx) wraps the row. The row grows from zero height (grid-template-rows: 0fr → 1fr) while it fades in, over the shared--motion-duration-standard/--motion-ease-standardtokens. The timeline re-pins to the bottom on each resize, so earlier messages glide up instead of jumping.animationendoranimationcancel, so the hover action rail is never cut off afterwards.motion-reduce:transition-none.MessageRowonly decides when:mountedWhilePendingis captured at mount. The row keeps its render key through the acknowledgement, so the entrance never replays when the send lands, and rows that arrive already sent (other people's messages, history) never play it.Measured first, so the scope stays small:
Not covered / follow-ups
localKeyone-shot set would fix it.Related issue
Closes #8214. Related: #8194 / #8195, #8192, #2441.
Testing
motion.test.mjs: the send entrance uses the shared tokens, its keyframes contain notransform/filter, and it has a reduced-motion rule.message-feedback-snapshots.spec.ts, 3 new e2e tests:The first and third fail without the feature. All 5 tests in the spec passed 3× in a row.
Desktop unit suite and
tsc --noEmit. Smoke e2e for messaging, virtualization, scroll-history, timeline-no-shift and the feedback spec: 130/131. The one failure, "editing a broadcast reply from a thread returns to the main composer", is pre-existing flakiness: 3/10 failures without this change vs 1/10 with it.Manual: rapid sends in a DM on the macOS app, verified frame by frame in a 120 fps recording. The timeline glides monotonically over ~5 frames with no jitter.
Before / after, real speed:
smooth-send-before-after.mp4
buzz-review-completed
🤖 Generated with Claude Code