镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

fix(mobile): keep a sent thread reply above the composer - #9026

Merged
brow merged 5 commits into
mainfrom
mobile-thread-send-scroll
Nov 9, 2026
Merged

brow merged 5 commits into
mainfrom
mobile-thread-send-scroll

Conversation

@brow

@brow brow commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes two thread symptoms that come from the same state: after sending a reply, the composer clears but the new reply sits hidden under the composer (or out of view entirely) until you leave and return, and the Latest button stays visible even though the list looks like it is at the bottom.

Cause

  1. A sent reply grows into place over its 500 ms entrance transition (LocalSendRow). The thread's one-shot tail correction can finish while the row is still growing. Once it has finished, nothing moves the list as the reply keeps growing, so the list stops short of the real end (about 40 pt on an iPhone 17 Pro simulator) and the reply ends up behind the composer. Because the list isn't actually at its end, Latest shows. It is visible even with the composer focused when an earlier drag detached tail-follow.
  2. A thread opened at a specific reply (from Inbox, a push notification or a message link) skipped the new-reply follow logic for its whole lifetime, not just for the replies it opened with. If the reader scrolled at all before replying, their own sent reply was never brought into view: it landed below the viewport, and a quick swipe couldn't reach it before the next layout.

Fix

  • While the thread follows its tail, a ScrollMetricsNotification listener keeps a position that was at its previous end on the new end whenever the extent grows. It only acts when the last reply is laid out, the reader isn't dragging or opted out, and no navigation to the tail is in progress, so programmatic jumps away from the tail are left alone. This also covers other growth at the tail, such as media or typing rows.
  • A targeted entry now absorbs the replies it hydrates with until its target jump has revealed the list. Hydration can come in two steps (the route's snapshot of the target, then the relay's full thread). Absorbing only the first let the second, when it held the reader's own earlier replies, schedule a tail correction that cancelled the one-shot target jump and left the list hidden behind its loading placeholder. Later arrivals use the ordinary rules: the reader's own reply comes into view, and other people's replies still don't move a reader who is away from the tail.

Before and after

Same flow on an iPhone 17 Pro simulator, in a synthetic QA channel thread: scroll the list away from the bottom, focus the composer, send a long reply that wraps onto many lines. Before (main) the sent reply ends with its bottom edge under the composer; after (this branch) it ends clear above it.

Before (main):

ev9026-before.mp4

After (this branch):

ev9026-after.mp4

Validation

  • Test a sent reply stays above the composer as it grows in now runs for a plain entry, a targeted entry (initialMessageId), and a targeted entry whose relay hydration is delayed and includes the reader's own replies. The targeted row opens at an older reply, checks it is visible, drags a little, focuses the composer with the keyboard up, sends a multi-line reply, and checks that the reply ends above the composer and Latest is hidden. Each row fails without its fix (growth: reply bottom at 504, composer top at 402; targeted: the reply is never followed; delayed hydration: the target never becomes hit-testable because the list stays hidden).
  • Full flutter analyze and mobile test suite pass.
  • Before/after capture on an iPhone 17 Pro simulator (iOS 27, debug builds of this branch head and of current main), in a synthetic QA channel thread with enough replies to scroll. Flow: scroll away from the bottom, focus the composer, send a long wrapping reply. Over 7 runs on main, the reply's bottom ended 3 to 25 pt under the composer in 5; over 6 runs on this branch, it ended 17 pt above the composer every time. Limitation: debug simulator builds only, and the shortfall on main varies run to run.
  • Automated full-app run on an iPhone 17 Pro simulator (iOS 27) against the Remote Agents Pilot community. In a test DM thread, I sent short and wrapping replies with no prior scroll, after dragging up and returning, and after the follow-up narrowing. Each reply ended fully above the composer, and Latest stayed hidden at the bottom. I also dragged up with the keyboard dismissed and confirmed the list stays at the tail with no stray Latest. For the targeted entry, I opened a thread from a message link, scrolled, and sent replies: without the targeted-entry fix they stayed out of view under the keyboard; with it hot-reloaded they were followed (the combined build is covered by the widget test rows above). I then reopened that link right after an app restart, which hydrates in two steps: before the hydration fix the thread showed only its root with the replies hidden, and with it the list revealed at the target and a sent reply came into view. Human testing is waived per the owner's standing exception. Limitation: this run did not cover physical devices or media replies.

The simulator recording was taken in an internal community, so it is shared internally rather than embedded here.

🤖 Generated with Claude Code

A sent reply grows in over its entrance transition, and the one-shot
tail correction could finish before that growth did. The list then
stopped short of its real end, hiding the reply under the composer and
leaving the Latest button visible at the apparent bottom.

While the thread follows its tail and the last reply is laid out, keep a
position that was at its previous end on the new end as the extent grows.

Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@localhost>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tom Brow <tomb@squareup.com>
@brow

brow commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-08T21:58:16.699494Z 2f577ae Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: d7dca52ddf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@johnmatthewtennant johnmatthewtennant left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Reviewed exact head d7dca52ddfdb26ff49c55ae33fedd2b3a6b74fbd with the complete teammate-review workflow. No correctness blockers found in the thread tail-growth correction.

square-tomb and others added 2 commits November 4, 2026 09:12
A thread opened at a specific reply (Inbox, push or link) skipped the
new-reply follow logic for its whole lifetime, so after the reader
scrolled, their own sent reply never came into view. Absorb only the
replies the entry hydrated with, then use the ordinary follow rules.

Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tom Brow <tomb@squareup.com>
…roll

Signed-off-by: Tom Brow <tomb@squareup.com>
@brow

brow commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b82927048b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// A targeted entry starts at its target, not the tail. Absorb the
// replies it hydrated with once; later arrivals use the ordinary rules
// below, so a reply sent from this page still comes into view.
if (initialMessageId != null && !initialTailSettle.isComplete) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Absorb hydrated replies before enabling targeted follow

When a targeted thread initially renders a provisional snapshot and the authoritative query later adds older replies, the earlier target-positioning effect has already called initialTailSettle.abandon(), so this condition is false and the hydration delta falls through to the new-reply logic. If any of those historical replies was authored by currentPubkey, hasNewLocalReply becomes true, clears the target's opt-out, and schedules a jump to the tail, overriding the message opened from Inbox, a notification, or a link. Track whether the initial targeted hydration has been absorbed independently of initialTailSettle; the added test currently preloads all replies and uses only another author's replies, so it does not exercise this case.

Useful? React with 👍 / 👎.

A targeted entry hydrates twice: the route's snapshot of the target, then
the relay's thread. Absorbing only the first let the second, when it held
the reader's own replies, schedule a tail correction that cancelled the
one-shot target jump, leaving the list hidden. Absorb hydration until the
target jump reveals the list.

Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tom Brow <tomb@squareup.com>
@brow

brow commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2f577ae3cf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// the target jump reveals the list; a tail correction scheduled earlier
// would cancel that one-shot jump and leave the list hidden. Later
// arrivals use the ordinary rules below, so a sent reply comes into view.
if (initialMessageId != null && !initialViewportReady.value) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep refresh deltas from overriding a targeted entry

When a targeted thread is already visible and its relay session reconnects, threadRepliesProvider automatically refreshes (thread_replies_provider.dart:44-50), so this condition now lets any increase in the refreshed result fall through to the new-reply logic. That logic uses replies.skip(previous) rather than reply IDs: if the refresh inserts one older remote reply while the previously newest reply belongs to the current user, the shifted suffix is misclassified as a new local send, clears the target opt-out, and jumps from the linked message to the tail. The added delayed-hydration test completes before target readiness and therefore misses this later production refresh path; distinguish optimistic local reply IDs from authoritative refresh additions before enabling tail follow.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Preserve the target through provisional-list dragging

This also appears reachable before initial hydration completes: the provisional list is visible while relayRepliesAvailable is false, and dragging it sets initialViewportReady before the target jump runs. If hydration then includes historical self-authored replies, the ordinary follow path can supersede the pending target jump. Could target completion be tracked separately, with only genuinely new local sends overriding that intent? That would also address the reconnect case above.

Posted by Codex (gpt-6.1-sol) on behalf of kylez

@brow
brow marked this pull request as ready for review November 8, 2026 21:55
@brow
brow requested a review from a team as a code owner November 8, 2026 21:55
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 8, 2026
@github-actions

github-actions Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

🔐 Codex Security Review

Note: This is an automated, security-focused review generated by Codex.
Use it as a supplement to human review; false positives are possible.

Scope

  • Exact PR diff: 8cce12f591988f29cc2beab5ff0c89d51862f31b...ca7d5f42b234d19d070bed56cc7075458172cf68
  • Model: gpt-6.1-sol

💡 Click "edited" above to see earlier reviews for this PR.


Review Summary

Overall Risk: NONE

No concrete security, correctness, or reliability findings were identified in the mobile thread-scrolling changes after tracing hydration, tail following, and user-scroll guards.

Findings

No concrete security, correctness, or reliability findings were identified.

Notes

  • Review used read-only source and history inspection. Builds and tests were not executed, as instructed.

Generated by Codex Security Review |
Requested by: @brow |
Workflow run

…roll

Signed-off-by: Tom Brow <tomb@squareup.com>

# Conflicts:
#	mobile/lib/features/channels/thread_detail_page.dart
@github-actions github-actions Bot added codex-security-review-current The posted Codex security review matches its recorded range. and removed codex-security-review-current The posted Codex security review matches its recorded range. labels Nov 8, 2026

@tlongwell-block tlongwell-block left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Meli, Tyler’s AI agent, posting on his explicit behalf.

Approved as directed by Tyler.

Full mobile suite passed. Mutation checks demonstrated that the regression tests detect both targeted fixes. Simulator before/after verification remains incomplete.

Reviewed head: ca7d5f42b234d19d070bed56cc7075458172cf68.

@kazemek21 kazemek21 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kyle reviewed this PR and the Nexus review-tracker notes. Approved with the non-blocking P2 comment.

Posted by Codex (gpt-6.1-sol) on behalf of kylez

@brow
brow merged commit 439d7bc into main Nov 9, 2026
53 checks passed
@brow
brow deleted the mobile-thread-send-scroll branch November 9, 2026 14:58
bostonaholic added a commit that referenced this pull request Nov 9, 2026
…ction-p

* origin/main: (111 commits)
  feat(mobile): tag the reacted-to message author on reactions (#9141)
  fix(mobile): steady New DM picker rows and animate picks (#9249)
  perf(mobile): keep remote images on disk and show small ones as thumbnails (#9158)
  fix(relay): deliver slow cross-node events late instead of closing their recipients (#9235)
  fix(mobile): read only shown events from filtered Activity rows (#9215)
  fix(mobile): show the channel name as the notification subtitle (#9173)
  fix(mobile): keep Home's header clean under sheets (#9152)
  fix(mobile): show already-loaded custom emoji in the iOS reactions sheet (#9150)
  mobile: add follow and remind actions to inbox row menu (#9142)
  fix(mobile): start no threads in Solo chats (#9123)
  mobile: persist user profiles so names and avatars render on launch (#9116)
  fix(mobile): keep a sent thread reply above the composer (#9026)
  [buzz] Recover database pool capacity after stalled connections (#9267)
  feat: send a User-Agent on relay WebSocket upgrades (#9246)
  Record why relay connections close and quiet close warnings (#9245)
  fix(relay): say "channel owner or admin" in the call_webhook save refusal (#9169)
  feat(mobile): consolidate Solo chat settings in one sheet (#9255)
  fix(cli): preserve attachments across successive edits (#9226)
  fix(mobile): swipe left to reply so swipe-back keeps rightward drags (#9127)
  mobile: long-press a message link for open, copy and share (#9135)
  ...

Signed-off-by: Matthew Boston <mboston@squareup.com>

This branch was previously deployed

1 inactive deployment
codex-review — ca7d5f42 Deployed Nov 8, 2026 by brow via Run Codex Security Review #13905
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex-security-review-current The posted Codex security review matches its recorded range.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants