You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Root cause: the transcript is an invertedFlatList, so "scroll to the newest message" means scrolling to offset 0, not to the end — and nothing was observing content growth, so nothing ever scrolled at all.
The fix follows new content via a signature over (message count, last message id, last part count, last part text length). That changes both when a new message arrives and when the last one is edited in place, so a reply that streams in also follows, not just a completed one.
It deliberately does not follow unconditionally: shouldAutoScroll only scrolls when the view is already within 200px of the newest message, so scrolling up to read history isn't yanked away mid-sentence. The scroll-to-bottom button uses the same predicate rather than its own copy, so the two can't disagree about where "bottom" is.
Fixed by the merge of #182 into the Qiiks/opencode-mobile maintenance branch (maint/v0.4.16) — shipping in release v0.4.16-maint.1 (APK will be attached there). Upstream review of #182 is still recommended; this merge keeps the fork usable while it waits.
Claimed: Ember — dev-workflow lead for the #155 auto-scroll fix. Branch: fix/auto-scroll-155.
Scope: transcript follows new content in the inverted FlatList (follow signature + shouldAutoScroll guard so a user scrolled up reading history is never yanked).
Noted: PR #182 claims this fix but is unmergeable as a whole (49 files, spans the keyboard, SSE, voice-alert and other work) — this task ships a scoped fix against current main, reusing #182's verified approach where it applies. The SSE lead's claim on #182 covers #189/#186 only, so no overlap on the auto-scroll part. Investigating first; fix lands via PR with typecheck + full test suite before merge.
Context pack — #155 auto-scroll (maintained by the lead; artifacts win over this pack)
Stage: 1 done, 3 next (implementation). Lead: Ember — dev-workflow lead, branch fix/auto-scroll-155, worktree ~/workspace/opencode-mobile-autoscroll-155 at HEAD 2667ad5c. Claim: comment 6856121059. Work Log:https://app.notion.com/p/417ac25eb49f812a9aeccd169d621db8 (In progress). No Notion design/PRD exists for this; the spec is this issue + #182's approach + the acceptance criteria below.
Bug still present, verbatim. No onContentSizeChange anywhere in app/session/[id].tsx or src/components/chat/. No scrollOffsetRef, transcriptSignature, shouldAutoScroll, or any auto-follow wiring. The only scroll-to-newest path is manual: scrollToBottom (L319-321, scrollToOffset({ offset: 0 })) called only from the scroll button onPress (L796-800).
Transcript is inline in SessionScreen (app/session/[id].tsx): inverted FlatList L757-785, data={messageData} (L222-233, reversed so newest is index 0), maintainVisibleContentPosition={{ minIndexForVisible: 0 }} L777 (must stay — stops jumps when older pages load via onEndReached). handleScroll L518-522 only sets showScrollButton on contentOffset.y > 200.
Drift check: PR fix(android): keep chat composer above the keyboard (#194, #156) #236 touched [id].tsx only in the keyboard/IME area. It did not touch the FlatList, handleScroll, scrollToBottom, messageData, or maintainVisibleContentPosition. Auto-scroll anchor points survive.
Sweep (defect class: inverted FlatList without content-growth follow)
Command: grep -rn "inverted" --include="*.tsx" --include="*.ts" app src on main @ 2667ad5.
Hit list: exactly ONE inverted FlatList in the app — app/session/[id].tsx:760 (the transcript). All other FlatLists are non-inverted lists/pickers where follow-newest does not apply. No siblings to fix; no pattern sweep beyond this site.
Design gap (bug lane, recorded before the fix starts)
No design ever specified the transcript scroll behavior. The inverted FlatList was built with a manual scroll button only and nothing observing content growth, so a streaming reply never followed. The gap is the missing follow rule itself: follow new content when the view is already near the newest message, never yank a user reading history. The fix records that rule in code (shouldAutoScroll + shared 200px threshold) and in tests.
Lane: simple fix (root cause known, one component, no contract change). No Notion design stage. Stage 4 applies in full.
Acceptance criteria
New message arriving, and a reply streaming in, scrolls to offset 0 when the view is within 200px of the newest message.
A user scrolled up reading history (>200px away) is never yanked back down.
Scroll-to-bottom button visibility uses the same predicate/threshold as auto-follow (no second copy of 200).
maintainVisibleContentPosition stays; loading older pages via onEndReached must not jump the view.
Typecheck clean + full test suite green, including the ported auto-scroll tests.
Verified on device (Work Log: verify on device) — emulator/physical proof, not just unit tests.
Signature decision: kept 2-field form (count:length); rationale recorded in comment 6856285744.
Gates (worktree, real checkout): npm ci exit 0; check:versions exit 0 (0.4.17); typecheck exit 0; full suite exit 0, 405 pass / 0 fail (baseline 395 + 10 ported). Test-count delta +1 file / +10 cases under the recorded AC-mapped exception. Logs in worktree: opencode-mobile-autoscroll-155-{npm-ci,check-versions,typecheck,test}.log.
CI on the PR head was pending at verification time (typecheck, test, build, activation-e2e pending; secret scan passed). Merge waits on CI green.
Owed: device proof (criterion 6) before merge.
Next action
Done — task complete. No release from this task (release is the product owner's separate act).
Stage 4 verdict: PASS (comment 6856376173 on PR #238, read back via API)
Checked against head 8c7747a. Both passes run by the reviewer in sequence (no subagents at that depth). Diff read via gh api. Gates re-run in the real worktree: typecheck exit 0, full suite 405 pass / 0 fail.
Test-count delta independently verified: baseline re-run at 2667ad5 = 395 pass / 0 fail; PR head = 405 pass / 0 fail; 50 to 51 test files. AC-mapped exception covers the +10 (all map to ACs 1-3). Pure-helper lib-test convention confirmed on main (40 src/lib/*.test.ts files, node:test, no mocks).
Compliance: ACs 1-5 PASS, AC 6 device proof owed (release residual, not a merge block), AC 7 PASS on merge. Bug hunt: no validated findings. Two filed notes (not fails): scrollEventThrottle 100ms leaves scrollOffsetRef up to ~100ms stale mid-fling (narrow yank window, pre-existing); signature counts only newest-message text length, so non-text-part growth alone would not trigger a follow.
CI at review time: typecheck/test/secret-scan green; build, iOS Simulator build, activation-e2e still pending. Merge waits on CI green.
Next action
Stage 5: merge on CI green + verdict read back (done), then Stage 5.5 sync.
Stage 5 update (branch brought up to date with main)
Gate met: Stage 4 verdict PASS (comment 6856376173) + CI green on the merged head a2146cf (typecheck, test, build, iOS Simulator build, activation-e2e 28m34s, secret scan all pass; build-fdroid/release skipping as expected).
Stage 5.5 outcome: PRD updated, TDD checked
PRD section 3.4 gained one line: the transcript follows new messages while the view is near the newest message, and never pulls the view away from a user scrolled up reading history. Verified by re-fetch (https://app.notion.com/p/40bac25eb49f8112afebd66e0e8475b6).
TDD checked: no transcript scroll behavior described there, no update needed.
Signature decision: kept #182's 2-field form (count:length) — the only case the 4-field form would catch is a same-length swap of the newest message, negligible for a streaming transcript.
Gates (worktree, base 2667ad5): npm run check:versions clean (0.4.17/44); tsc --noEmit clean; full suite 405 pass / 0 fail (395 baseline + 10 ported auto-scroll tests). Device proof (criterion 6) still owed before merge.
Merged: PR #238, merge commit cad292a (main HEAD, read back). Stage 4 verdict PASS (comment 6856376173) on the reviewed head 8c7747a; branch updated to a2146cf by a clean merge of main (#235 only); CI green on a2146cf.
Shipped: scoped auto-scroll fix — transcript follows new content via a signature over (message count, newest-message text length) with a shouldAutoScroll guard (200px threshold, shared with the scroll-to-bottom button), so a user scrolled up reading history is never yanked. 3 files +181/-2: new src/lib/auto-scroll.ts + test (10 cases, ported from #182), wiring in app/session/[id].tsx reusing scrollToBottom. Full suite 405 pass / 0 fail.
Residuals: device proof owed on the v0.4.18 build before it ships (owner phone check). PR #182 stays open — its other parts belong to their own tasks; this issue is closed by the scoped PR, not by #182.
Activity
fix(chat): follow new messages automatically; copyable demo text
fix(chat): follow new messages automatically (dzianisv#155)
omgoshjosh commentedon Aug 16, 2026
Fixed in #182.
Root cause: the transcript is an inverted
FlatList, so "scroll to the newest message" means scrolling to offset 0, not to the end — and nothing was observing content growth, so nothing ever scrolled at all.The fix follows new content via a signature over (message count, last message id, last part count, last part text length). That changes both when a new message arrives and when the last one is edited in place, so a reply that streams in also follows, not just a completed one.
It deliberately does not follow unconditionally:
shouldAutoScrollonly scrolls when the view is already within 200px of the newest message, so scrolling up to read history isn't yanked away mid-sentence. The scroll-to-bottom button uses the same predicate rather than its own copy, so the two can't disagree about where "bottom" is.Qiiks commentedon Aug 21, 2026
Fixed by the merge of #182 into the Qiiks/opencode-mobile maintenance branch (
maint/v0.4.16) — shipping in release v0.4.16-maint.1 (APK will be attached there). Upstream review of #182 is still recommended; this merge keeps the fork usable while it waits.dzianisv commentedon Nov 13, 2026
Claimed: Ember — dev-workflow lead for the #155 auto-scroll fix. Branch:
fix/auto-scroll-155.Scope: transcript follows new content in the inverted FlatList (follow signature + shouldAutoScroll guard so a user scrolled up reading history is never yanked).
Noted: PR #182 claims this fix but is unmergeable as a whole (49 files, spans the keyboard, SSE, voice-alert and other work) — this task ships a scoped fix against current main, reusing #182's verified approach where it applies. The SSE lead's claim on #182 covers #189/#186 only, so no overlap on the auto-scroll part. Investigating first; fix lands via PR with typecheck + full test suite before merge.
dzianisv commentedon Nov 13, 2026
Context pack — #155 auto-scroll (maintained by the lead; artifacts win over this pack)
Stage: 1 done, 3 next (implementation). Lead: Ember — dev-workflow lead, branch
fix/auto-scroll-155, worktree~/workspace/opencode-mobile-autoscroll-155at HEAD2667ad5c. Claim: comment 6856121059. Work Log: https://app.notion.com/p/417ac25eb49f812a9aeccd169d621db8 (In progress). No Notion design/PRD exists for this; the spec is this issue + #182's approach + the acceptance criteria below.Stage 1 verdict (verified against main @ 2667ad5)
onContentSizeChangeanywhere inapp/session/[id].tsxorsrc/components/chat/. NoscrollOffsetRef,transcriptSignature,shouldAutoScroll, or any auto-follow wiring. The only scroll-to-newest path is manual:scrollToBottom(L319-321,scrollToOffset({ offset: 0 })) called only from the scroll button onPress (L796-800).SessionScreen(app/session/[id].tsx): invertedFlatListL757-785,data={messageData}(L222-233, reversed so newest is index 0),maintainVisibleContentPosition={{ minIndexForVisible: 0 }}L777 (must stay — stops jumps when older pages load viaonEndReached).handleScrollL518-522 only setsshowScrollButtononcontentOffset.y > 200.src/lib/auto-scroll.ts(+65, no imports) andsrc/lib/auto-scroll.test.ts(+79, 10 tests) at PR head1181372f. Its[id].tsxhunks do NOT apply as-is (they bundle SelectableTextModal, a transcriptBound flash change, and a keyboard fix fix(android): keep chat composer above the keyboard (#194, #156) #236 superseded). PR fix: keyboard/composer (#156), auto-scroll (#155), dead SSE (#186), blocked SSE startup (#189), stale transcripts, uncopyable text, axis-perfect table scrolling, 50-session list cap, transcript flash, dark-mode contrast #182 overall: 49 files, mergeable false.count:length). Swapping the last message for a different same-length one would not trigger a follow. Implementer decides: keep 2-field or restore 4-field.[id].tsxonly in the keyboard/IME area. It did not touch the FlatList, handleScroll, scrollToBottom, messageData, or maintainVisibleContentPosition. Auto-scroll anchor points survive.Sweep (defect class: inverted FlatList without content-growth follow)
grep -rn "inverted" --include="*.tsx" --include="*.ts" app srcon main @ 2667ad5.app/session/[id].tsx:760(the transcript). All other FlatLists are non-inverted lists/pickers where follow-newest does not apply. No siblings to fix; no pattern sweep beyond this site.Design gap (bug lane, recorded before the fix starts)
shouldAutoScroll+ shared 200px threshold) and in tests.Acceptance criteria
maintainVisibleContentPositionstays; loading older pages viaonEndReachedmust not jump the view.Decisions
SessionInfo.onScrollToTopnaming, transcriptBound flash fix.Open residuals
Next action
fix/auto-scroll-155(portsrc/lib/auto-scroll.ts+ test from fix: keyboard/composer (#156), auto-scroll (#155), dead SSE (#186), blocked SSE startup (#189), stale transcripts, uncopyable text, axis-perfect table scrolling, 50-session list cap, transcript flash, dark-mode contrast #182 @ 1181372, wire signature + effect intoapp/session/[id].tsxreusingscrollToBottom), open the PR linking Message cannot be scrolled automatically. #155.Stage 3 verdict (PR #238, head 8c7747a, read back via API)
Next action
Stage 4 verdict: PASS (comment 6856376173 on PR #238, read back via API)
Next action
Stage 5 update (branch brought up to date with main)
Stage 5 verdict: MERGED (read back via API)
Stage 5.5 outcome: PRD updated, TDD checked
Open residuals
Next action
dzianisv commentedon Nov 13, 2026
Stage 3: scoped fix shipped as PR #238 (head 8c7747a, branch fix/auto-scroll-155).
Signature decision: kept #182's 2-field form (
count:length) — the only case the 4-field form would catch is a same-length swap of the newest message, negligible for a streaming transcript.Gates (worktree, base 2667ad5):
npm run check:versionsclean (0.4.17/44);tsc --noEmitclean; full suite 405 pass / 0 fail (395 baseline + 10 ported auto-scroll tests). Device proof (criterion 6) still owed before merge.Merge pull request #238 from dzianisv/fix/auto-scroll-155
dzianisv commentedon Nov 13, 2026
Merged: PR #238, merge commit cad292a (main HEAD, read back). Stage 4 verdict PASS (comment 6856376173) on the reviewed head 8c7747a; branch updated to a2146cf by a clean merge of main (#235 only); CI green on a2146cf.
Shipped: scoped auto-scroll fix — transcript follows new content via a signature over (message count, newest-message text length) with a shouldAutoScroll guard (200px threshold, shared with the scroll-to-bottom button), so a user scrolled up reading history is never yanked. 3 files +181/-2: new src/lib/auto-scroll.ts + test (10 cases, ported from #182), wiring in app/session/[id].tsx reusing scrollToBottom. Full suite 405 pass / 0 fail.
Design/PRD sync: PRD section 3.4 updated with the follow behavior (https://app.notion.com/p/40bac25eb49f8112afebd66e0e8475b6); TDD checked — no transcript scroll behavior described there, no update needed.
Residuals: device proof owed on the v0.4.18 build before it ships (owner phone check). PR #182 stays open — its other parts belong to their own tasks; this issue is closed by the scoped PR, not by #182.
fix(chat): auto-scroll transcript to newest message (dzianisv#155)
release: v0.4.18 — keyboard, voice, and stability fixes