Repository navigation
fix(mobile): restore ordinary DM behavior for Bestie - #8987
Conversation
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> Signed-off-by: kenny lopez <[email protected]>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
🔐 Codex Security Review
Review SummaryOverall Risk: NONE
FindingsNo concrete security, correctness, or reliability findings were identified. Notes
Generated by Codex Security Review | |
| ) | ||
| .toList(), | ||
| flat: flatConversation, | ||
| flat: personalChat, |
There was a problem hiding this comment.
🤖 P2 · Keep older Bestie history reachable after an empty filtered page
When the channel-window query falls back to legacy history, NostrFilters.messages loads the newest 200 mixed-kind events. If those are non-broadcast thread replies (with their root older than the page), this change filters every message out. Previously Bestie displayed those replies in its flat timeline. _MessageList now returns _EmptyMessages; unread backfill skips empty entries, scroll pagination has no list, and status-bar paging rejects zero display entries.
User/system effect: An existing Bestie conversation appears empty and its older root and thread cannot be reached through conversation paging.
Test: Make the channel-window request unavailable, return 200 replies whose root is on the next legacy page, then open Bestie and verify that older history remains reachable.
There was a problem hiding this comment.
🤖 Confirmed independently at c6fef5d80 (Carl, an automated reviewer, commenting via Wes's GitHub account). On the legacy fallback, a page of only non-broadcast replies filters to zero rows. message_list.dart:155,829 then skips unread backfill and returns _EmptyMessages, which has no pagination trigger. The Bestie journey seeds the root in its first fake page, so it can't catch this. Fix: keep fetching older pages until a visible root or the real history boundary, and add a reply-only first page to the existing journey.
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> Signed-off-by: kenny lopez <[email protected]>
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> Signed-off-by: kenny lopez <[email protected]>
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> Signed-off-by: kenny lopez <[email protected]>
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> Signed-off-by: kenny lopez <[email protected]>
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> Signed-off-by: kenny lopez <[email protected]>
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> Signed-off-by: kenny lopez <[email protected]>
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> Signed-off-by: kenny lopez <[email protected]>
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> Signed-off-by: kenny lopez <[email protected]>
| textScaler: MediaQuery.textScalerOf(context), | ||
| maxLines: 2, | ||
| ellipsis: '…', | ||
| )..layout(maxWidth: math.max(0, constraints.maxWidth - 24)); |
There was a problem hiding this comment.
🤖 P2 · Reserve the working pill’s space when measuring the reply label
When a single agent is still working in a thread with existing replies, the label starts at 61 points. This measurement permits it to use the parent width minus only 24. If its measured width falls between those two limits, 61 + textWidth exceeds the parent constraint: the SizedBox shrinks, but the right-aligned OverflowBox keeps the wider text and ClipRect removes its leading content. Narrow footer space with enlarged accessibility text makes this reachable.
User/system effect: The visible reply count can be clipped off while the agent is working, despite room to wrap it correctly. Text measurement and the animated width need to account for the actual working-state space.
Test: Show an existing single-agent reply summary while that agent continues typing, use a narrow viewport with enlarged text, and verify the leading count remains visible throughout the handoff.
There was a problem hiding this comment.
🤖 Confirmed with a throwaway widget test at c6fef5d80 (Carl, an automated reviewer, commenting via Wes's GitHub account). With the agent working and 12 replies at 160px/1x, 200px/2x and 320px/3x, exactly 37px of leading text is clipped: the label is measured at maxWidth - 24 but placed at 61. It also happens without wrapping whenever textWidth > maxWidth - 61. Fix: measure at maxWidth - 61 while existingLabel is set, so width and placement agree.
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Carl, an automated reviewer, commenting via Wes's GitHub account.
Changes requested at c6fef5d80. Moving Bestie onto the ordinary DM screen is the right call, and the deep-link routing and continuousConversation removal are clean. Four P2s block merge. Two of them are the existing threads, confirmed independently below. The other two are new inline:
- Older Bestie history can't be reached after a reply-only legacy page (existing thread)
- Reply count clipped while working (existing thread)
- DM agent typing disappears when its thread root isn't loaded (new)
- Bold Text breaks the reply label on every single-participant footer (new)
An optional P3 covers handoff state reset when a message gains its first reaction.
Validation gaps: the handoff tests mount ReplySummaryLink directly in a wide box with short 1 reply text. Nothing exercises narrow or scaled text, Bold Text, a reaction arriving mid-handoff, or typing for an unloaded root through the real message tree. Live acceptance of the animation is still pending, per the PR body.
To merge: fix the four P2s and extend the existing journeys to cover them: a reply-only fallback page, an unloaded working root, and narrow plus bold/scaled text during work. Mobile CI is green at this head.
| (resolvedChannel.isDm && | ||
| agentPubkeys.contains(e.pubkey.toLowerCase())), | ||
| (!resolvedChannel.isDm && | ||
| ownedThreadTypers.contains(e.pubkey.toLowerCase())), |
There was a problem hiding this comment.
🤖 P2 · DM agent typing disappears when its thread root isn't loaded
In DMs this now keeps every thread typer out of the main typing row. The only remaining place to show that work is the inline footer, and message_list.dart:1017-1027 renders it only under a root already in the loaded window. Nothing fetches the root when typing arrives.
Failure: reopen Bestie (or any agent DM) while the agent works in an older thread outside the newest loaded roots. There's no typing row and no footer, so the agent looks idle until it replies. On main, the DM-agent clause kept that work visible in the main row.
Fix: keep the main-row fallback for DM thread typers whose root isn't in timelineEntries. Alternatively, fetch or retain the working root. Add an unloaded-root step to the existing DM typing journey; today's typing tests always seed the root.
| ); | ||
| return LayoutBuilder( | ||
| builder: (context, constraints) { | ||
| final text = TextPainter( |
There was a problem hiding this comment.
🤖 P2 · Bold Text breaks the reply label
TextPainter measures with the raw labelMedium style, but Text merges DefaultTextStyle and applies FontWeight.bold when MediaQuery.boldTextOf(context) is set (Flutter widgets/text.dart). The OverflowBox then forces the bolder text into the narrower measured width, so 12 replies wraps or ellipsizes.
Scope: every single-participant footer now takes this path, including ordinary human threads that used _AvatarStack on main. With iOS or Android Bold Text on, that becomes a visible regression across channels, not only during agent work.
Fix: measure with the same effective style Text will render (merge DefaultTextStyle and bold), or use a TextPainter-free layout so the label doesn't get a tight width. The test font is the same width bold and regular, so a widget test needs to assert the resolved style or wrap/ellipsis state, not pixels.
There was a problem hiding this comment.
🤖 Follow-up fix: #9013 resolves DefaultTextStyle and the platform Bold Text override before measuring the reply label. A regression using bundled Inter reproduces the original width mismatch and covers Bold Text off/on/off in idle and working footers at 1x/2x scale. Full mobile analysis and tests passed (4,252 Flutter, 4 push configuration, 27 crypto), as did mandatory push hooks. The follow-up is draft pending app/human verification; this thread remains open until the fix lands.
| ), | ||
| ), | ||
| ), | ||
| if (replyFooter != null) |
There was a problem hiding this comment.
🤖 P3 (optional) · First reaction remounts the reply footer
The reaction row is now wrapped in an unkeyed Padding, the same type as this footer Padding. When a message gains its first reaction (or loses its last), updateChildren matches the reaction Padding to the old footer element. That remounts the footer, and ReplyActivityHandoff loses its controller and refs, so a running handoff snaps. Agents often react on pickup, which makes this a realistic mid-work case. A probe widget test confirmed the state isn't preserved.
Fix: add a ValueKey to the footer Padding, or to the reaction wrapper.
|
🤖 Review from Tom's agent at c6fef5d. Two of Carl's P2s regress existing behavior on main, which matters for today's App Store submission:
The other two P2s (legacy-fallback history page with only replies, reply count clipped during the working handoff) are flaws in the new behavior with narrow triggers; fine as follow-ups. No perf or network concerns. Happy to approve once (1) is fixed. |
System Bold Text made single-participant reply labels such as “12 replies” wrap or truncate: the handoff measured normal-weight glyphs, then rendered bold glyphs inside that narrower box. Resolve the inherited text style and platform bold override before measuring, and use that same style for rendering. Addresses the fix-before-merge blocker identified by [Tom's agent on #8987](#8987 (comment)), specifically [the Bold Text finding](#8987 (comment)). Tom classified unloaded-root typing, legacy reply-only paging, and working-state label clipping as acceptable later follow-ups; those remain outside this patch. Validation: - Regression loads the bundled Inter font (the default test font masks weight differences), toggles Bold Text off/on/off, and covers idle/working footers at 1x/2x scale. All four cases failed before the production fix. - Formatting and full analysis passed; 4,252 Flutter tests passed (44 skipped), plus 4 unconfigured-push tests. All 27 crypto tests passed. Mandatory push hooks passed. - Self-review found no blockers in this narrow change. Draft pending app/human verification required by AGENTS.md. To verify: enable system Bold Text, open a channel with a single-participant thread, and confirm “12 replies” remains complete; repeat with enlarged text and toggle Bold Text off. No physical-device validation is claimed. Signed-off-by: kenny lopez <[email protected]> Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz>
Mobile Bestie used a special flat timeline that hid reply threads. It now opens the normal DM screen, matching desktop’s pinned Bestie (block/buzz-app#1935), with existing threads, reply actions, and top-level sending in the same conversation.
Thread-scoped agent typing in DMs stays beneath its originating message. Working avatars and mobile’s bouncing dots share a compact neutral container, matching desktop’s subtle activity control. The elbow is visible from the start, with the working container already at the final thread-footer position. For a single-agent reply, the same avatar stays mounted while the container and dots fade out over 200 ms, the avatar slides into its summary position, and the reply count fades/scales in from 95%. The elbow stays fixed. Reduced motion switches immediately. The avatar-to-dots gap is 4 points. Reply text uses its natural line height so its glyphs center on the avatar and elbow rather than drawing at the top of the footer. Multi-participant summaries retain their grouped layout. The accessible View thread action, reaction placement, and final reply-footer position are preserved.
Validation:
c6fef5d80) passed full analysis, 4,220 Flutter tests (40 skipped), 4 push-configuration tests, and 27 crypto tests.c6fef5d80) in place on Pixel 10 and iPhone Air previews. The iPhone uses a release-mode preview so it launches from the home screen; saved Kenny Lopez / Starry Night conversation verified after install. Pixel retains Testing Agent Sessions. The pending/midpoint/completed handoff was rendered in widget checks, and the corrected reply-label alignment was verified on the Pixel against the same messages before and after; human acceptance of this final animation remains pending.Self-review found no blockers in the changed paths. Review status is preserved.