Skip to content

Repository files navigation

bb-plugin-ios-notifications

Push notifications for bb on your iPhone — and any other device with a Web Push browser. When a thread finishes its turn, fails, or sits waiting on your input, your phone buzzes with a notification that deep-links straight back to the thread.

bb's web UI is already an installable PWA (manifest, standalone display, maskable icons) but ships no service worker, so the browser has nowhere to deliver a push. This plugin supplies one — plus everything around it: smart suppression while you're watching, an app icon badge, collapse keys so an offline phone doesn't get buzz-bombed, and an offline shell for a flaky tunnel.

Install

bb plugin install git:https://github.com/vburojevic/bb-plugin-ios-notifications.git@main
bb push status   # confirm it loaded

Set up your iPhone

iOS only delivers Web Push to apps installed on the Home Screen — a Safari tab has no push API at all.

  1. Open your bb in Safari on the iPhone (for example through a bb connect tunnel URL).
  2. Tap ShareAdd to Home Screen and confirm.
  3. Open bb from the new Home Screen icon — push only works inside the installed app.
  4. Go to Settings → Plugins → iOS Notifications and tap Enable on this device.
  5. Tap Send a test notification to confirm the loop works.

The same Enable button works in desktop Safari, Chrome, and Edge, and on Android (installed or in the browser). The bb desktop app is the one place Web Push cannot work — Chromium routes push through Google's FCM and Electron ships no connection to it; the settings panel explains this instead of offering a button that cannot work.

What you get notified about

Event Notification
Thread finishes its turn Thread title + the assistant's last message
Thread fails "Failed — …" + the error, sent with high urgency
Thread waits on you "Waiting for you — …" + the actual question or command needing approval, after a 20s grace period

The needs-input push is the quiet star: a thread blocked on a permission prompt or an AskUserQuestion looks exactly like a working thread from your phone. If the prompt is still unanswered after 20 seconds, you get pushed — answering at your desk within the grace period never buzzes.

Tapping a notification

On iOS the platform does this, not the plugin. A device whose browser supports Declarative Web Push (Safari 18.4+) is sent a payload carrying "web_push": 8030, and the system shows the notification and navigates to its navigate URL when tapped — no notificationclick, no service worker. That is the only mechanism that reliably works in an installed iOS web app, where client.navigate() and clients.openWindow() are both ignored and every worker-driven route is therefore a no-op.

Support is feature-detected by the worker and reported per device, never guessed from a user agent, and the declarative payload needs an absolute URL — so each device also reports which origin it reaches bb on. bb push status shows both:

  57246457006ea18f  iPhone (Home Screen)  [declarative → https://your.tunnel]  (last delivered …)

A device without that support keeps the plugin's own payload and the handoff below.

This is what makes a tap work when bb is backgrounded or fully closed: the platform launches or foregrounds the installed app on the notification's URL itself. Nothing in this plugin has to be running, and nothing it does is allowed to get in the way — the worker's push handler deliberately shows no notification for a declarative payload (showing one would replace the platform's proposal and take the tap back), and the content script's worker bookkeeping is detached from its mount so a cold launch never waits on a network round trip or a worker activation before bb renders.

When the worker has to do it itself

A tap lands on the thread the notification was about, and the page is what puts it there. Inside an installed iOS web app the service worker cannot navigate anything: the system brings the app forward on whatever route it was last showing, and client.navigate() and clients.openWindow() are ignored — which is why taps used to reopen bb on the wrong thread.

So the worker records the tap and hands it over two ways. It messages any running window, which routes itself in place (no reload) and acknowledges; and it leaves the record behind for the page to find on launch and on every resume, which is what covers a cold start and a suspended page whose message arrives late. Both paths are idempotent, expire after a minute, and are claimed by deleting the record, so with several windows open exactly one moves. Where the worker can navigate — Android, desktop browsers — client.navigate() and openWindow() remain as fallbacks for an app bundle too old to answer.

Noise control

  • Viewing suppression — every bb window (including the desktop app) reports which thread it is on, instantly on navigation and focus changes. While any window is visible on a thread, that thread's pushes are skipped everywhere. Needs-input pushes are deferred rather than dropped, so a prompt that outlives your attention still reaches your phone. Backgrounding the app lifts suppression immediately (keepalive reports survive page suspension).
  • Collapse keys — every thread push carries a Web Push Topic, so a phone that was offline gets only the newest notification per thread instead of the whole backlog.
  • Min turn duration — optionally skip pushes for turns shorter than 30s–5m. Never filters failures or questions.
  • Child threads — subagent/worker threads are skipped unless you opt in; hidden threads (workflow agents) are always skipped.
  • Silent when watched on-device — if the receiving device itself has a visible window on the thread, the notification lands silently in the tray.

App icon badge

The app icon badge shows how many threads are currently waiting on your input — on installed PWAs (iOS 16.4+, Android), and on the bb desktop app, whose dock icon supports the same API even though push does not reach it.

The count follows the server, not the last notification. Every bb window already reports which thread it is on — on navigation, on focus changes and every 15s — and the reply to that report carries the count, so answering a prompt at your desk drops the badge on every other device that has a window open, and reading one of three waiting threads leaves two rather than zero. Tapping a notification does not clear it either: the thread you tapped is still waiting until you answer it.

Two limits worth knowing. While the installed app is fully closed, only an arriving push can change its badge — Web Push has no silent delivery, so there is nothing to send. And a badge is at most 15s stale while a window is open, which is the heartbeat of the report it rides on.

Offline shell

bb ships no service worker, so a dropped connection leaves an installed PWA on the browser's blank error page. With offlineShell on (the default), the worker caches the app shell and serves it only when a navigation fails, so you get bb's own reconnecting UI instead. The handler is deliberately narrow: navigations only, network-first, never assets or /api, and it refuses to cache anything that isn't a plain same-origin 200 HTML document containing bb's mount point — a tunnel's sign-in page can never become the shell.

Settings

Setting Default Effect
notifyOnIdle true Notify when a thread finishes its turn
notifyOnFailure true Notify when a thread fails (ignores min turn duration)
notifyOnNeedsInput true Notify when a thread waits >20s on a permission or question
includeChildThreads false Include subagent/child threads
minTurnDuration off Skip pushes for turns shorter than this — idle only
suppressWhenViewing true Skip pushes for a thread some visible bb window is on
openInMobilePanel true Cold taps open the thread in the Mobile plugin's phone view, when installed
offlineShell true Serve the cached app shell when a page load fails
contact https://getbb.app VAPID subject; push services require mailto: or https:

Change them in Settings → Plugins → iOS Notifications or with bb plugin config ios-notifications set <key> <value>. Settings are read per event — no reload needed.

CLI

bb push status              # devices, delivery state, settings, what's being viewed
bb push test [message]      # send a test to every registered device
bb push remove <device-id>  # unregister a device by id

Devices can also be removed from the settings panel.

When a tap misbehaves

An installed iOS app caches both halves of this plugin — the service worker and the app bundle — and can keep running an old pair long after the plugin itself is updated, which looks exactly like a fix that did not work. Two things make that visible instead of a guess.

On the device, Settings → Plugins → iOS Notifications states which bundle and which service worker are actually running. A worker from before 1.0.5 does not answer the version ping, and the panel says so along with the remedy: close bb completely and reopen it.

From the machine, bb push status prints the same thing per reporting window, plus the last dozen notification taps:

plugin revision: 1.0.6
clients:
  iPhone (Home Screen)    app 1.0.6  worker 1.0.6  (thr_abc, visible)
  Mac (browser)           app 1.0.6  worker 1.0.6  (no thread, hidden)
taps:
  06:41:02  tap     /threads/thr_abc  (1.0.6)
  06:41:02  routed  /threads/thr_abc  (1.0.6)

A tap with no routed after it means the worker ran but no page finished the navigation. No tap at all means the tap never reached the worker. Both are answers; neither is visible without this.

How it works

  • Service worker — served by the plugin backend at /api/v1/plugins/ios-notifications/http/sw.js with Service-Worker-Allowed: /, which lets a script under /api/… claim the root scope the app runs in. Handles push, notificationclick, badge updates, and the optional offline-shell fetch fallback.
  • VAPID keypair — generated once on first load, kept in the plugin's kv storage, never rotated (rotation would invalidate every subscription).
  • Subscriptions — one kv row per device, keyed by sha256(endpoint).slice(0, 16). A push service answering 404/410 removes the row; transient 429/5xx errors get one retry.
  • Needs-input detection — the realtime feed flags threads with a pending interaction; the plugin then reads the actual pending interaction (question prompt, command awaiting approval) to build the notification body.
  • Viewing reports — a tiny content script posts {clientId, threadId, visible} to an unauthenticated plugin route (so it also works through a bb connect tunnel, whose origin fails local-auth checks — the tunnel fronts it with its own auth wall). Reports age out after 45s and the map is hard-capped, so the endpoint can't be grown unbounded.

Development

git clone https://github.com/vburojevic/bb-plugin-ios-notifications
cd bb-plugin-ios-notifications
npm install
bb plugin types .    # regenerate types/ from your bb's SDK
bb plugin build .    # bundle server + app into dist/
bb plugin install .  # install as a local path plugin

bb plugin dev . watches sources and hot-reloads the frontend bundle.

License

MIT

About

Push notifications for bb on your iPhone — thread finished, failed, or waiting on you. Web Push + service worker for the bb PWA.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages