Files
buzz/mobile/lib/features/channels/thread_detail_page/read_state.dart
Tom Brow b6d97a69f0 fix(mobile): keep thread content visible while positioning replies (#8134)
Opening a mobile thread could blank the original message while the app
positioned replies. This affected empty and populated threads, including
links to a specific reply.

Keep one original-message widget mounted and usable throughout
positioning. It follows a measured placeholder in the reply list, which
becomes visible only at its final position. This preserves media and
reaction ownership, supports scrolling through tall originals, and
excludes the head from input and accessibility when it leaves the
viewport.

### Validation

- Added production-path frame regressions for empty, short, long, and
deep-linked threads; two video lifecycle cases verify exactly one
initialization and disposal, including completion after navigation away.
- Added coverage for dragging, mouse-wheel and accessibility scrolling,
removal of offscreen and app-bar-covered head semantics, and scrolling
to the interactive bottom of a 70-line original message. Removing the
accessibility exclusion or restoring the raw viewport-top boundary fails
both long-thread regressions; restoring the duplicated head fails the
lifecycle test.
- Mobile dependency setup, analysis, the full mobile test suite, and
unconfigured-push tests passed. The native iOS simulator debug build
passed after integrating main, including the conflict resolution for the
new Buzz icon API. The current tree passed 3,076 mobile tests (four
skipped) and three unconfigured-push tests, and the updated native
simulator build passed.
- Exercised empty, three-reply, forty-reply, and older-reply deep-link
flows on an iPhone 17 Pro simulator against an isolated local relay with
a disposable identity. Confirmed correct head accessibility coordinates
and absence when offscreen. The local posterless-video fixture rendered
its attachment fallback; native decoding success is not claimed.
Resource ownership is covered through the production preview-loader
seam. [Updated simulator
captures](https://github.com/block/buzz/pull/8134#issuecomment-6047098230).
- Added delayed-hydration regressions for mouse-wheel and explicit
accessibility scrolling through the persistent head. Both preserve a
visible reading anchor across hydration and viewport resizing; removing
the user-scroll lifecycle callbacks makes both fail. Repeated empty,
populated, and older-reply deep-link opening with the updated native
build against the retained isolated relay.
- Entered and sent a reply from the disposable simulator identity
against the isolated local relay. The app displayed the reply and
cleared the composer, and the relay database confirmed persistence.
Software keyboard open/close remains unverified because the simulator
did not display it.
- Agent review feedback was addressed. Personal mobile testing is not
applicable under the author's standing exception.

Post-merge validation: repeat empty, populated, and deep-linked thread
opening in the integrated mobile candidate, including keyboard
open/close and sending a reply before release.

buzz-review-completed

---------

Signed-off-by: Tom Brow <tomb@block.xyz>
2026-10-09 08:05:55 -07:00

25 lines
706 B
Dart

part of '../thread_detail_page.dart';
void _useThreadReplyReadState(
WidgetRef ref,
String threadHeadId,
List<TimelineMessage> replies,
) {
final readState = ref.watch(readStateProvider);
final visibleReplyReadKey = replies
.map((reply) => '${reply.id}:${reply.createdAt}')
.join(',');
useEffect(() {
if (!readState.isReady || replies.isEmpty) return null;
WidgetsBinding.instance.addPostFrameCallback((_) {
for (final reply in replies) {
ref
.read(readStateProvider.notifier)
.markContextRead(msgContextKey(reply.id), reply.createdAt);
}
});
return null;
}, [threadHeadId, readState.isReady, visibleReplyReadKey]);
}