Skip to content

feat(mobile): add iOS Home Screen shortcuts for New chat, New DM and the Bestie - #9360

Merged
brow merged 4 commits into
mainfrom
mobile-home-quick-actions
Nov 12, 2026
Merged

brow merged 4 commits into
mainfrom
mobile-home-quick-actions

Conversation

@brow

@brow brow commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

Long-pressing the Buzz icon on iOS now offers three quick actions:

  • New chat opens a new solo chat (the same draft as New › New chat).
  • New DM opens the New DM picker.
  • Message opens the DM with the current community's Bestie, using the agent's display name (for example "Message astra"). It's hidden when the community has no Bestie, and it updates when the Bestie, its name or the community changes.
Before After
before after

How it works

Home publishes the items through a small native bridge (buzz/home_screen_shortcuts), which sets UIApplication.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

  • Added tests: a Home journey covers the published titles (including the Bestie's name), a cold tap that waits for the app to resume and for New chat to be available, a Bestie tap made with another route open, a New chat tap while a draft is open, a New DM tap replacing an open picker, a waiting tap dropped once another screen opens, no Home rebuilds on lifecycle changes, and clearing the items when Home goes away. A launcher test covers a slow Bestie lookup, and a New chat test covers a shortcut tapped while the first message is still sending. I also broke the code that runs the shortcut and confirmed the journey fails.
  • Full mobile suite and flutter analyze pass. CI doesn't compile Swift on drafts, so NativeAddMembersSheet.swift was compiled with a local flutter build ios --simulator build.
  • iPhone 17 Pro simulator (iOS 27), signed in to a Builderlab community: from a cold launch, New chat opened the new-chat draft. Then, with the app warm, New DM opened the picker and Message replaced it with the Bestie DM. The recording stays in Buzz because it shows real account data.

buzz-review-completed

🤖 Generated with Claude Code

square-tomb and others added 4 commits November 11, 2026 11:44
…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]>
@brow
brow marked this pull request as ready for review November 12, 2026 01:16
@brow
brow requested a review from a team as a code owner November 12, 2026 01:16
@brow
brow enabled auto-merge (squash) November 12, 2026 01:16
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 12, 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-12T01:18:44.404898Z f820c98 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.

@brow
brow deployed to codex-review November 12, 2026 01:16 — with GitHub Actions Active
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 12, 2026
@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

🔐 Codex Security Review

Status: review required for the current range.

The current range is 85c7156085d902f5d5fd59e193263b109c6d5ba0...f820c9807def512c7a2f75021b8f6e73e69f6d52.
A new review must complete for this exact range. When manual authorization
is required, a user with write access must comment exactly
@buzz-security-review f820c9807def512c7a2f75021b8f6e73e69f6d52 to authorize a new review.
Any previous review applies only to its recorded range.

@wesbillman wesbillman 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.

🤖 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 null from takePending, 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 ?? []

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.

🤖 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

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.

🤖 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,

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.

🤖 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 []));

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.

🤖 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.

@brow brow closed this Nov 12, 2026
auto-merge was automatically disabled November 12, 2026 04:13

Pull request was closed

@brow brow reopened this Nov 12, 2026
@brow
brow enabled auto-merge (squash) November 12, 2026 04:13
@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 12, 2026
@brow
brow merged commit 27f0fe7 into main Nov 12, 2026
58 of 59 checks passed
@brow
brow deleted the mobile-home-quick-actions branch November 12, 2026 04:20
ravarora2 pushed a commit that referenced this pull request Nov 12, 2026
…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>
brow added a commit that referenced this pull request Nov 12, 2026
…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]>

This branch was successfully deployed

No deployments
codex-review — f820c980 Deployed Nov 12, 2026 by brow via Run Codex Security Review #14561
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants