Skip to content

Add the bb mobile app (Expo / React Native, iOS first) - #1988

Merged
SawyerHood merged 6 commits into
mainfrom
bb-mobile-3-app
Aug 20, 2026
Merged

Add the bb mobile app (Expo / React Native, iOS first)#1988
SawyerHood merged 6 commits into
mainfrom
bb-mobile-3-app

Conversation

@SawyerHood

@SawyerHood SawyerHood commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Stack layer 3 of 4 for the bb mobile app (stack #1990). Prerequisites: #1986 (@bb/client-core) and #1987 (core prep). Next layer: #1989 (push notifications).

What was wrong

bb has no native phone client. The PWA works on a phone but is bounded by mobile-web limits (keyboard/viewport hacks, no push, no background reconnect, no secure credential storage, no camera/photo attachments, WebKit style-recalc stalls). Plan, decisions, and research: plans/bb-mobile-expo.md, plans/bb-mobile-research/; execution log with per-phase verification: plans/bb-mobile-progress.md.

What changed

New apps/mobile (@bb/mobile, Expo SDK 57 / React Native 0.86 / Expo Router / NativeWind v5, iOS first; ~81k lines of app code, ~20k of unit tests, 26 Maestro flows). Everything else in this PR is toolchain/docs; no server or web behavior changes.

  • Connectivity: server profiles in SecureStore; Direct mode (LAN / Tailscale / simulator) and bb connect (machine enrollment by QR/code → desktop-session cookie in the native jar, renewal, re-pair); per-profile SDK + realtime manager (ping/pong, watermark reconnect, AppState suspend/resume) + QueryClient with realtime invalidation; global mutation error toasts.
  • Screens: drawer sidebar + home thread list (organize/sort, pinned, unread glyphs, long-press actions), search, archived, compose with pickers (project, provider, model+reasoning, permission mode, service tier, environment incl. reuse/worktree/branch/path, machine), new project + project settings + machine setup, thread detail (FlashList timeline with every row kind via a renderer registry, native markdown, native diffs, ANSI, lightbox, unread divider, ToC, prompt-stack cards), prompt area (composer with mentions/typeahead/attachments/voice/drafts, send/queue/steer/stop, queued messages, pending-interaction banner incl. approvals, questions, ask-user-question and secret-request forms, context banner with git/PR/parent/children, message/thread/git action sheets, fork/handoff), workspace panel (Info, Diff, Files + previews, terminal = bundled xterm WebView with RN-owned socket + accessory bar, synced thread tabs), settings (general, appearance palettes from theme.css, experiments, providers, usage, machines list/detail/add, updates, plugins management + marketplaces, skills library/registry, haptics), deep links (bb://, universal link mapping, thread-open signal), outbound share.
  • Design system: generated native theme tokens (scripts/generate-native-theme.ts from theme.css, drift-tested), primitives (sheets, action sheets, toasts, etc.), Inter + Fira Code, hugeicons.
  • Toolchain: Metro resolves @bb/* from TS source (source condition scoped to workspace packages), pnpm patch for expo-modules-jsi@57.0.4 on Xcode 26.2, scoped lightningcss override for NativeWind v5, turbo/eslint/prettier wiring, .github/workflows/mobile-e2e.yml (label mobile-e2e / nightly; Release build + Maestro) and mobile-runner-probe.yml, eas.json profiles, docs (docs/platform-support.md, docs/repository-overview.md).
  • Early access: the bb-side pairing surfaces (Settings → Remote access → Add mobile device, bb connect machine-code) are behind the mobileApp experiment from Prepare core, cloud, and test surfaces for the bb mobile app #1987; the app's own Settings → Experiments screen lists that toggle too (it is shared server state).
  • Not in this layer: push notifications (Push notifications for the bb mobile app, end to end #1989). Not yet: Android, plugin frontends (SPA-in-WebView planned), inbound share (needs a native rebuild), TestFlight (needs an EAS account).

How you verified

  • pnpm exec turbo run typecheck lint test --filter=@bb/mobile — 794 tests / 117 files, 0 lint findings.
  • Every phase was built by parallel agents and independently re-verified; the full Maestro regression (26 flows) passed on the iPhone 17 Pro simulator (iOS 26.3) against the seeded harness backend (tests/integration/mobile-e2e/backend.ts) and the connect stub gate; results table and screenshots list in plans/bb-mobile-progress.md. Connect enrollment was also exercised against the real bee.getbb.app.
  • On this layer specifically (no push): phase1-shell, phase4b-send, phase5-links flows re-run and pass.
  • Known gap carried in the log: on bb connect profiles the left-edge drawer swipe can also press the home row under the touch (Direct profiles are fine).

AGENT GENERATED: by Claude Opus 5

@bb-slop-cop bb-slop-cop Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 SLOP COP 🚨 · review

I am the SlopCop. I started a review of this mobile application pull request.

I will review security, code quality, performance, architecture, duplication, and the available end-to-end paths. I will post the combined findings when the review finishes.

@SawyerHood
SawyerHood force-pushed the bb-mobile-3-app branch 2 times, most recently from 17b4caf to 1b2321f Compare August 20, 2026 02:12
@SawyerHood SawyerHood added the mobile-e2e Run the iOS simulator Maestro flows on this PR label Aug 20, 2026
@SawyerHood
SawyerHood marked this pull request as ready for review August 20, 2026 02:49
Base automatically changed from bb-mobile-2-core-prep to main August 20, 2026 03:12
SawyerHood added a commit that referenced this pull request Aug 20, 2026
Stack layer 1 of 4 for the bb mobile app (stack #1990). No prerequisite;
the next layer is #1987.

## What was wrong

The native mobile app (`apps/mobile`, layer 3) needs the web app's
platform-neutral client logic — sidebar grouping and sorting, thread
read state, prompt draft and submission policy, mention triggers,
timeline merge/paging helpers, renderable-patch rules, the terminal
WebSocket transport, panel tab schemas, file preview, localhost link
rewriting, route builders. Today these live inside `apps/app/src` next
to DOM and react-router code, so a React Native app cannot import them.

## What changed

- New `packages/client-core` (`@bb/client-core`): the pure modules moved
out of `apps/app` unchanged, plus a `test/no-dom.test.ts` guard that
walks `src/**` and fails on
`window`/`document`/`localStorage`/`navigator`/react-router/`@/`
references.
- `apps/app`: every old path is a one-line re-export shim, so web
imports and tests keep working; the few files that mixed DOM and pure
code (`route-paths.ts`, `fixed-panel-tabs-state.ts`,
`useThreadTimelineController.ts`,
`localhost-link-rewrite-preference.ts`, `terminal-websocket-url.ts`,
`TimelineFileDiffBlock.tsx`, `secondaryPanelTabState.ts`) keep the
web-only parts and import the rest from client-core. 19 tests moved with
their modules.
- `TerminalWebSocketTransport` treats an undefined
`socket.bufferedAmount` as 0 (React Native's WebSocket never sets it),
with a regression test.
- `@bb/client-core` depends on `@bb/domain`, `@bb/server-contract`,
`@bb/core-ui`, `@bb/thread-view`, `@bb/fuzzy-match`,
`@bb/desktop-contract` (pure), `zod`. No React.

No behavior change in the web app is intended.

## How you verified

- `pnpm exec turbo run typecheck lint test --filter=@bb/client-core
--filter=@bb/app` — client-core 236 tests, app 2955 tests / 392 files,
lint 0 errors (the 144 warnings pre-exist on main).
- `packages/client-core/test/no-dom.test.ts` guards the DOM-free rule;
the transport test fails without the `bufferedAmount` fix.

Part of the bb mobile app plan (`plans/bb-mobile-expo.md`, lands in
#1988).

> AGENT GENERATED: by Claude Opus 5

---------

Co-authored-by: Claude <noreply@anthropic.com>
SawyerHood added a commit that referenced this pull request Aug 20, 2026
Stack layer 2 of 4 for the bb mobile app (stack #1990). Prerequisite:
#1986 (`@bb/client-core`). Next layer: #1988 (the app).

## What was wrong

A native client needs a few small, additive pieces the server, cloud,
SDK, and test harness do not have today: a way to tell the server it is
a mobile client, SDK types that survive React Native's global
`Response`/`FormData` declarations, a shared contract for the two
plugin-rendered pending interactions so the phone can render them
natively, a deterministic approval path in the fake provider for e2e, a
harness that can run on a fixed port behind a stub gate, a way to mint a
connect pairing code for a phone, and app-link association files so
universal links can work.

## What changed

- `packages/config` + `apps/server`: request-side `RequestAppSurface`
(`desktop | web | mobile`) for `x-bb-app-surface` + telemetry; the
server config union stays `desktop | web`. Test added.
- `packages/sdk`: `SdkResponseLike` structural constraint in
`response.ts`/`transport.ts` (Hono client responses stay assignable
under RN globals); `projects.sidebarBootstrap()`; public-types test
updates.
- New `packages/plugin-interaction-contracts`: the ask-user-question and
secret-request payload/resolution schemas moved from the two plugins'
private `contracts.ts`; both plugins re-export them (their tests
unchanged).
- `packages/agent-runtime` fake provider: `approve:<kind>` control token
(`command | file_change | permission_grant | plan`) emits an approval
interactive request; adapter decodes/encodes approval payloads; 5 tests.
- `tests/integration`: harness `serverPort`/`bindHost` options;
`mobile-e2e/backend.ts` (seeded long-lived harness) and
`connect-stub.ts` (TLS stub gate in front of the harness) + scripts +
turbo tasks.
- `plugins/connect`: "Add mobile device" QR/code in Settings → Remote
access and `bb connect machine-code [--json]` (uses the existing
`createMachineCode` RPC; 409 machine-limit surfaced). Guide template,
bb-cli SKILL, `docs/multiple-devices.md`, `docs/configuration.md`
pairing section updated per `docs/cli-guide-and-skill.md`.
- **Hidden behind the new `mobileApp` experiment** (off by default;
Settings → Experiments → "Mobile app", or `bb settings experiment
mobileApp true`) until the app is generally available. `@bb/domain`
gains the key; the connect plugin reads it through its loopback SDK
(`bb.sdk.system.config()`) on every call — a new `mobilePairing` rpc
tells the panel whether to render the section, and `createMachineCode` /
`bb connect machine-code` refuse with a pointer to the toggle. No new
plugin-SDK API. With the experiment off, a paired install sees no visual
change from this PR.
- `packages/connect-client`: pairing helpers used by the phone.
- `apps/connect` (gate) + `apps/web` (apex) + `packages/connect-db`:
serve `/.well-known/apple-app-site-association` and
`/.well-known/assetlinks.json` before the session gate (static JSON
only; Android fingerprints from `ASSETLINKS_SHA256_FINGERPRINTS`, empty
if unset). No auth change.

No daemon contract change → `HOST_DAEMON_PROTOCOL_VERSION` untouched.

## How you verified

- `pnpm exec turbo run typecheck lint` for config, server, sdk,
plugin-interaction-contracts, ask-user-question, secrets, connect
plugin, agent-runtime, integration-tests, connect-client, templates,
@bb/connect, @bb/web, connect-db, cli — pass (the only failure is the
pre-existing `conversation-outline-parity.test.ts` typecheck on main
from #1657).
- Tests: sdk 96, agent-runtime 413, connect plugin 88, templates 43,
connect/connect-db/connect-client, server telemetry + public route tests
(342), `@bb/integration-tests test:smoke` 28 — all pass.

Part of the bb mobile app plan (`plans/bb-mobile-expo.md`, lands in
#1988).

> AGENT GENERATED: by Claude Opus 5

---------

Co-authored-by: Claude <noreply@anthropic.com>
SawyerHood and others added 5 commits August 19, 2026 20:13
`apps/mobile`: a native phone client for a bb server over Direct URLs or bb
connect pairing. Home thread list + drawer, search, archived, compose with
pickers / mentions / attachments, the thread screen (timeline with every row
kind, unread divider, table of contents; send / queue / steer / stop;
approvals, questions, plugin forms; message / thread / git action sheets;
fork / handoff; context banner), deep links (`bb://` + universal links),
the workspace panel (Info / Diff / Files / Terminal), and the settings
screens (General / Appearance / Experiments / Haptics, providers, usage
limits, Machines, Updates, Plugins, Skills). Built on `@bb/client-core`,
`@bb/sdk/browser`, `@bb/thread-view`, and the pending-interaction contracts.

Toolchain: the `expo-modules-jsi` Xcode 26.2 patch + pnpm overrides, mobile
eslint rules and ignores, prettier ignores, `@bb/mobile#dev` / `#test`
turbo tasks, the `Mobile E2E` and `Mobile Runner Probe` workflows (Maestro
flows on the iOS simulator against the integration harness backend), the
platform-support and repository-overview docs, and the mobile plan /
progress / research notes.

Push notifications (the Expo push client, Settings → Notifications rows,
and the server side) arrive in the next PR of this stack; the flows and
READMEs here note where they plug in.

Co-Authored-By: Claude <noreply@anthropic.com>
Mirror the new `mobileApp` toggle (added on the core-prep layer) in the
app's Settings → Experiments list, include the key in the Phase 7 e2e
settings reset (PUT /settings/experiments is exhaustive under zod 4), and
note the gate in the README and platform docs.

Co-Authored-By: Claude <noreply@anthropic.com>
- Files: project-file HTML previews show source like the web; only the
  CSP-sandboxed raw routes load in the preview WebView (docstring states
  the invariant; tests added)
- Connection: replace literal NUL bytes with escapes so the connector and
  Markdown.tsx diff as text; add a circuit breaker to the connect-mode
  auth-failure -> re-mint -> refetch cycle (cap, error state, cooldown)
- Realtime bridge: mirror the web registry — no PR refetch on file-watcher
  work-status/git-refs changes, PR refresh on turn/completed, thread record
  only on metadata/background-activity changes; evict the observer-less
  diff patch cache on reconnect
- Session: the desktop-session cookie domain must domain-match the server
  host before it is installed (iOS only substring-checks)
- Dev spike routes redirect home unless e2e/dev mode; `bb://dev/*` and
  `bb://e2e/*` scheme links resolve to `/` in release bundles
- Markdown links: allow-list http(s)/mailto/irc(s)/xmpp like the web; other
  schemes are inert
- Machines/Updates: stop using the phone's compiled daemon protocol
  version as the server's; read it from GET /install/version
- Thread composer mounts once the thread record is loaded, so drafts are
  never keyed on a placeholder project id
- Root .prettierignore skips Expo prebuild output; Maestro installs from a
  pinned, checksummed release zip instead of `curl | bash`

Co-Authored-By: Claude <noreply@anthropic.com>
`expo run:ios` requires a development signing identity whenever the
entitlements contain `com.apple.developer.associated-domains` — even for a
simulator build — so the first Mobile E2E run failed with "No code signing
certificates are available to use" once the app claimed universal links.

Build with `--device generic --output build-output` (generic iOS Simulator
destination, no signing setup) and install the product with `simctl`.
DerivedData stays in its default location, so both native caches still hit.

Co-Authored-By: Claude <noreply@anthropic.com>
The health-check loop used `kill -0 $!`, but `$!` is the `pnpm exec turbo`
wrapper: on the macOS runner it exits while turbo and the backend keep
running, so the loop declared "backend exited early" one tick before the
server answered /health (the uploaded backend.log shows it had printed
"ready" and was only stopped by the cleanup step).

Give up only when the wrapper and the `mobile-e2e/backend.ts` process are
both gone, and fail explicitly when /health never answers in the window.

Co-Authored-By: Claude <noreply@anthropic.com>
Two problems stacked on top of each other. The server answers /health as
soon as it listens, before it has seeded the project and the four threads
the flows drive, so /health is not a readiness signal. And the details JSON
line that *is* the signal never reached the log, because turbo groups task
output in CI and only flushes it when the task ends — which a persistent
task never does.

Stream turbo's output and wait for that line (verified locally with CI env
set: it appears ~26s in, while the log stayed empty with grouped output).

Co-Authored-By: Claude <noreply@anthropic.com>
@SawyerHood
SawyerHood merged commit e01681b into main Aug 20, 2026
13 checks passed
@SawyerHood
SawyerHood deleted the bb-mobile-3-app branch August 20, 2026 03:43
SawyerHood added a commit that referenced this pull request Aug 20, 2026
…obile (#1995)

## What was wrong

#1993 removed the `claudeCodeMockCliTraffic` experiment key from
`@bb/domain` while #1988 (the mobile app) was in flight. After both
merged, `main` fails `@bb/mobile` typecheck:
`apps/mobile/src/screens/settings/ExperimentsSettingsScreen.tsx` still
lists the key, and `apps/mobile/e2e/scripts/phase7-settings-reset.js`
still PUTs it to `/api/v1/settings/experiments` (the experiments record
is exhaustive under zod 4, so the reset would 400 and the nightly Mobile
E2E would fail).

## What changed

- Removed the row from the mobile Experiments screen.
- Removed the key from the e2e settings-reset payload.

## How you verified

- `pnpm exec turbo run typecheck lint test --filter=@bb/mobile` on
`main` + this change: all green (119 test files). Typecheck fails on
`main` without it.
- `git grep claudeCodeMockCliTraffic` leaves only historical
migration/test references.

> AGENT GENERATED: by Claude Opus 5

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mobile-e2e Run the iOS simulator Maestro flows on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant