The rpc-client has always emitted a detailed connection lifecycle log
(dials, timeouts, close codes, handshake steps, retries) via onLog, but
only the pairing screen wired it up — for long-lived host connections
everything went to console.log, invisible to users. Debugging reports
like #7824/#6928 meant asking reporters for facts the app already knew.
- connection-log-buffer: bounded (200/host) module-level ring buffer with
referentially-stable snapshots for useSyncExternalStore; survives
client swaps and provider remounts.
- client-context: wire onLog for every shared host client.
- connection-log screen: live per-host log (reuses the pairing
ConnectionLog component), host picker, and a Copy Diagnostics button
that bundles app/platform versions, endpoint (flagged if Tailscale),
state, attempt count, last-connected, and the event log into one
shareable blob.
- troubleshoot: 'View connection log' entry point.
Co-authored-by: Orca <help@stably.ai>
scheduledNotificationsByHostAndNotificationId (mobile-notifications.ts)
retained one entry per scheduled desktop notification. The key embeds
notificationId, which carries a per-completion timestamp
(buildAgentNotificationId), so every agent-task-complete inserts a new,
never-reused key. Entries are removed only when the desktop sends a
matching dismiss — which a remote mobile user (not sitting at the
desktop) frequently never receives — so the module-level map grew for
the app's whole lifetime. Small per entry, but genuinely unbounded.
Fix: bound the map to the 256 most-recent SETTLED entries (never evict
one mid-schedule). A settled entry only retains a small identifier used
for later programmatic dismissal, which is unnecessary for long-past
completions, so eviction has no user-visible effect.
Also FIFO-cap RootLayout's handledNotificationIdsRef tap-dedup Set
(RootLayout never unmounts, so it otherwise grew one id per tapped
notification forever).
Test (red->green): with the cap at 1, scheduling a second notification
evicts the first, so a later dismiss for the evicted id is a no-op while
the retained one still dismisses; without the cap the old entry survives.
## Summary
- add mobile Settings → Browser → Open terminal links
- route terminal HTTP(S)/OSC 8 link taps to either the streamed Orca browser or the phone browser
- keep existing file-path tap behavior intact
## Verification
- pnpm --dir mobile test
- pnpm --dir mobile lint
- pnpm --dir mobile exec tsc --noEmit
- git diff --check
* release: v1.4.48-rc.0 [rc-slot:2026-06-05-15]
* release: v0.0.1-rc.0 [rc-slot:2026-06-06-03]
* fix(mobile): respect Android device rotation lock instead of forcing rotation
The app rotated on Android even when the system auto-rotate lock was on.
Expo's top-level `orientation: "default"` emits `android:screenOrientation="unspecified"`,
which auto-rotates on many devices regardless of the lock, and the
`react-native-screens` `orientation: 'all'` screenOption mapped to FULL_SENSOR,
which forces rotation outright.
react-native-screens exposes no orientation value that honors the rotation lock
(even 'default' calls setRequestedOrientation(UNSPECIFIED) at runtime, overriding
the manifest). The Android value that respects the lock is "fullUser", which must
come from the manifest. So:
- Add an `android-respect-rotation-lock` Expo config plugin that sets the main
activity to `android:screenOrientation="fullUser"` (honors the lock; allows all
orientations when unlocked, matching iOS).
- Remove the runtime `orientation` screenOption so the manifest value governs.
iOS behavior is unchanged (UISupportedInterfaceOrientations still lists all four).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>