Repository navigation
feat(mobile): add iOS Home Screen shortcuts for New chat, New DM and the Bestie - #9360
Conversation
…the Bestie Long-pressing the app icon offers New chat, New DM and Message <Bestie>, named after the current community's Bestie and hidden when there is none. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Tom Brow <[email protected]>
Move the shortcut wiring into a leaf widget so lifecycle changes and Bestie reloads rebuild only it, skip republishing an unchanged menu or one built from a failed Bestie read, pop to Home before waiting on a queued action, and fence the Bestie lookup so a second open or a newer screen can't stack the conversation. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Tom Brow <[email protected]>
Return to Home once per tap and drop a waiting tap when another screen opens. A replacement member picker keeps the shared method channel handler, and the native coordinator presents it after the previous picker finishes closing. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Tom Brow <[email protected]>
Returning Home for an app icon shortcut now stops at a route whose pop is vetoed, such as a New chat whose first message is still sending, and drops the tap instead of discarding that message. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Tom Brow <[email protected]>
Codex Review Summary: ✅ CompletedThis 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
|
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Carl, an automated reviewer, commenting via Wes's GitHub account.
Verdict: no blockers. Reviewed at f820c980 across native iOS/lifecycle, Flutter navigation/state, and a simplicity/security pass. The shortcut title is display-only: dispatch allowlists three fixed types and re-reads the current Bestie, so an agent name can't steer a recipient. The shared opener, lookup lock and post-frame guards each earn their place; I didn't find a materially simpler equivalent.
Four low-severity inline notes. The one I'd most like addressed before merge is the afterClose wedge in NativeAddMembersSheet.swift, because it turns a rare race into a silent, session-long failure of New DM and Add members.
Validation gaps
- CI for this head is still queued. The last completed run (
b6973f74) had Mobile and Mobile Swift green. - The Home journey stubs the Bestie opener and returns
nullfromtakePending, so cold-launch retrieval and shortcut to Bestie navigation are covered only by the simulator run in the description and the separate launcher test. Not asking for more tests; just noting that the journey doesn't prove those seams. - All Court findings are from static traces. None were reproduced on a device.
Exit criteria: head CI green, plus either a guard for the afterClose wedge or device evidence that the dismiss completion always fires.
| session = nil | ||
| self.navigation = nil | ||
| picker = nil | ||
| afterClose = afterClose ?? [] |
There was a problem hiding this comment.
🤖 Low, plausible: this can wedge the member picker for the rest of the session. afterClose only goes back to nil in the dismiss completion. If dismiss is called while the sheet is still animating in, or mid interactive swipe-down, UIKit logs "Attempt to dismiss … while a presentation or dismiss is in progress", ignores the call and never runs the completion. One way to hit it: Dart dispose() sends dismiss right away when a shortcut's popUntil removes the host route. After that, every later present is queued at line 31 with no reply, so _present waits forever. New DM and Add members then open nothing and show no toast. Before this change the same race only orphaned one picker.
Suggested guard: skip the queue state when navigation.isBeingPresented || navigation.isBeingDismissed, and flush or clear afterClose in presentationControllerDidDismiss too. Device evidence that the completion always fires would also settle it.
| guard Self.types.contains(item.type) else { return false } | ||
| let type = item.type | ||
| pending = type | ||
| channel?.invokeMethod("open", arguments: type) { [weak self] result in |
There was a problem hiding this comment.
🤖 Low: a single tap can reach Dart twice. handle both buffers pending and sends open. If the engine is running but Dart hasn't set its handler yet (cold launch before Home mounts, or the app is backgrounded during splash), the framework's channel buffer holds the open and drains it when listenForHomeScreenShortcuts installs the handler. Meanwhile takePending can return the same type before the handled reply clears it. If Home runs the first copy in between, the second copy pops the screen it just opened and runs the shortcut again. The window is narrow.
Simpler fix: use one delivery path. Add a listening flag that takePending sets, and only call open once it's true.
| (shortcut: HomeScreenShortcut.newDm, title: 'New DM'), | ||
| if (bestiePubkey != null) | ||
| ( | ||
| shortcut: HomeScreenShortcut.messageBestie, |
There was a problem hiding this comment.
🤖 Low, optional: the Bestie item names an agent but carries no identity. iOS keeps the menu after the process is killed. If the Bestie changes on another device, or a tap lands during a community switch before the menu is republished, "Message Alice" opens Bob's DM. Putting the Bestie pubkey in userInfo, passing it back with open, and falling back to Agents when it doesn't match the current Bestie would make the title and the destination agree.
| useEffect(() { | ||
| unawaited(listenForHomeScreenShortcuts()); | ||
| // Signing out removes Home, and its shortcuts with it. | ||
| return () => unawaited(publishHomeScreenShortcuts(const [])); |
There was a problem hiding this comment.
🤖 Low, optional hardening. Teardown clears the published items, but the Dart handler and the global pendingHomeScreenShortcut (shared/shortcuts/home_screen_shortcuts.dart:19) stay alive. A tap that was still waiting here, for example on a Bestie that was still loading, survives sign-out and would run in the next account's Home. I couldn't find a sign-out path that actually reaches this, so this isn't a defect claim. Clearing pendingHomeScreenShortcut.value here is a one-line guard. Cold-launch taps aren't affected, because they're collected when this host mounts.
Pull request was closed
…small * origin/main: fix(db): seek replaceable heads by channel (#9147) buzz-db: check events created_at floor in app writers (phase 1) (#8325) fix(mobile): keep a thread tail behind the keyboard unread (#9295) fix(mobile): restore header cues and per-channel identity check for #9369 (#9370) fix(relay): treat empty filter sets as match-nothing in SQL (#9342) buzz-cli: add --before and --before-id to messages search (#9382) feat(relay): agents cannot write in DMs except with their owner (#9371) fix(relay): bound each cross-node delivery attempt (#9305) feat(mobile): add iOS Home Screen shortcuts for New chat, New DM and the Bestie (#9360) perf(mobile): stop downloading whole videos for posterless stills (#9367) Enforce single-community relay mode (#9149) fix(relay): match #a tag filters in SQL before LIMIT (#9339) Add participant avatars to mobile conversation headers (#9369) fix(mobile): stop asking for an API token when the channel list is refused (#9327) chore(release): release Buzz Relay version 0.3.0 (#9362) Fix gallery video selection, thumbnails, and swipe dismissal (#9356) Signed-off-by: dessert <7d616e7e143e5d408159614dccff1371350a1c1bd36c8de838a45ece5aca0aca@buzz.block.builderlab.xyz>
…9388) Follow-up to review notes on [#9360 Home Screen shortcuts](#9360). These are fixes for rare races, with no visible change. - **The member picker can't wedge.** UIKit ignores a dismiss that arrives mid-transition (the sheet still animating in, or during an interactive swipe-down) and never runs its completion. The picker coordinator queues every later present behind that completion, so New DM and Add members then opened nothing for the rest of the session. Dismiss now waits for the transition to finish, then dismisses whatever is still presented. - **A shortcut tap reaches Dart once.** The native bridge both buffered a tap and sent it, so a tap during cold launch could arrive twice and run again after Home had already handled it. Now a tap is buffered only until Dart first takes the pending one, and after that it is sent directly. - **Signing out drops a waiting tap.** Removing Home clears a tap that is still waiting (for example on a Bestie that is still loading), so it can't run in the next account's Home. Not included: carrying the Bestie's pubkey in the shortcut item, so a stale "Message Alice" falls back to Agents rather than opening the current Bestie. It's optional, and it would change the bridge's payload shared with the lock screen widgets in [#9373 lock screen widgets](#9373). ### Validation - The Home shortcut journey gains a step: a tap waiting while the app is inactive is cleared when Home goes away. The step fails with the teardown guard removed. - Automated simulator run (iPhone 17 Pro, iOS 27, signed in to a Builderlab test community), driving the app icon's quick actions on the Home Screen: - New DM from a conversation opens the picker. New DM again, with the picker already open, replaces it. New chat over the picker closes it and opens a draft, and New DM after that opens the picker again (it doesn't get stuck). - Message (Bestie) opens the Bestie conversation. - Cold launch through New chat opens exactly one New chat screen. Back returns to Messages. buzz-review-completed Signed-off-by: Tom Brow <[email protected]> Co-authored-by: Tom Brow <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
Long-pressing the Buzz icon on iOS now offers three quick actions:
How it works
Home publishes the items through a small native bridge (
buzz/home_screen_shortcuts), which setsUIApplication.shortcutItems. A tap goes through the scene delegate. The bridge keeps the latest one until Dart has handled it, the same pattern push-notification taps use, so cold launches work too. Home runs it only once the app is active and the destination is ready. When a tap arrives, Home pops back to the first route once, so a shortcut never opens beneath a Flutter sheet or a conversation that was already open. A screen that refuses to close (for example a New chat whose first message is still sending) stays, and the tap is dropped. The one exception is the native iOS New channel form, which isn't a Flutter route: a shortcut tapped with it open runs underneath it. If a screen is opened while a tap is still waiting for its destination, the tap is dropped. A New DM tap while the New DM picker is open replaces the picker. Opening the Bestie DM now goes through one shared function, so the Home launcher button and the shortcut run identical code. A second open while the DM is still being looked up does nothing. Signing out clears the items. The shortcut wiring lives in its own leaf widget, so Home doesn't rebuild for it.Behavior change for the existing Home Bestie launcher: if you open another screen while it's still looking up the DM, the DM no longer opens over that screen.
Behavior change for the native member picker (New DM, and Add members in channels): a picker reopened while the previous one is still closing now opens once the dismissal finishes, instead of failing with "Cannot open the member picker".
Validation
flutter analyzepass. CI doesn't compile Swift on drafts, soNativeAddMembersSheet.swiftwas compiled with a localflutter build ios --simulatorbuild.buzz-review-completed
🤖 Generated with Claude Code