- Add shouldRunSetupForCreate to hooks mock in orca-runtime tests
- Fix send() helper to use rest args, avoiding extra undefined in IPC calls
Co-authored-by: Orca <help@stably.ai>
The mobile-fit-override code calls safeFit with ManagedPane (from
getPanes()), but safeFit was typed to require ManagedPaneInternal.
Widen the parameter type since safeFit only needs fields on ManagedPane.
Co-authored-by: Orca <help@stably.ai>
Trigger on mobile-v* tags instead of main pushes. Builds a release APK,
uploads as artifact, and creates a GitHub Release with the APK attached.
Manual dispatch still available for on-demand builds.
Co-authored-by: Orca <help@stably.ai>
The full Gradle build is heavy for every PR push. Keep only typecheck +
lint as the PR gate; run the APK build on main pushes and manual dispatch.
Co-authored-by: Orca <help@stably.ai>
- BottomDrawer: shift drawer up when keyboard opens using Keyboard events,
add ScrollView for content overflow
- NewWorktreeModal: replace FlatList with plain map to avoid nested
VirtualizedList error
- Home: replace Server icon with Monitor, hide IP from cards (show in
action sheet), add connection status subtitle, update hero copy
- About: remove redundant link labels, use neutral text color for URLs
Co-authored-by: Orca <help@stably.ai>
Remove duplicate onPtyData call in pty.ts that caused every daemon-backed
PTY data event to be dispatched twice through dataListeners, doubling all
terminal output sent to mobile clients.
Also: clean up registerSubscriptionCleanup to evict stale listeners on
reconnect, add regression tests, fix CI lint/type errors, add mobile UX
improvements (worktree creation, settings, about screen).
Co-authored-by: Orca <help@stably.ai>
- Add shared BottomDrawer component with pan gesture tracking via reanimated
- Drawer follows finger on swipe down, dismisses past threshold or on velocity
- Rubber-band resistance on upward pull for tactile feel
- Migrate all 5 modal components to use BottomDrawer
- Replace plain text QR instructions with numbered steps with icons
Co-authored-by: Orca <help@stably.ai>
Use performAndroidHapticsAsync on Android which uses HapticFeedbackConstants
instead of the Vibrator API. This fixes haptics not working on Android and
removes the VIBRATE permission requirement.
Co-authored-by: Orca <help@stably.ai>
- Add missing randomUUID import in pty.ts
- Add getForegroundProcess to RuntimePtyController test mocks
- Add terminalFitOverrideChanged to RuntimeNotifier test mocks
- Fix vi.fn() type assertions in e2ee-integration tests
Co-authored-by: Orca <help@stably.ai>
- Extract send helper in attach-main-window-services to stay under max-lines
- Remove unused startup parameter in useIpcEvents
Co-authored-by: Orca <help@stably.ai>
- Add expo-haptics medium impact feedback on all long-press interactions
(terminal tabs, worktree cards, host cards)
- Add "Sleep" option with Moon icon to worktree action sheet, calling
worktree.sleep RPC method
- Change send button from bright blue (accentBlue) to subtle dark
(bgRaised) background with textSecondary icon color
Co-authored-by: Orca <help@stably.ai>
Replace the disabled TLS cert pinning approach with application-layer
E2EE using tweetnacl (Curve25519 ECDH + XSalsa20-Poly1305). The
WebSocket stays plain ws:// but all RPC traffic is encrypted after
a one-round-trip handshake.
Desktop: generate persistent Curve25519 keypair, E2EE channel module
handles handshake state machine and transparent encrypt/decrypt.
Mobile: ephemeral keypair per connection for forward secrecy, expo-crypto
PRNG polyfill for Hermes compatibility.
Pairing schema updated from v1 (certFingerprint) to v2 (publicKeyB64).
Mock server updated with E2EE support. 24 tests covering crypto,
channel handshake, and integration.
Co-authored-by: Orca <help@stably.ai>
Add module-level Map to persist which terminals have been phone-fitted,
so navigating away and back auto-refits them instead of reverting to
desktop dimensions. Also adds fitPendingRef to prevent duplicate fits.
Co-authored-by: Orca <help@stably.ai>
Replace native WebView scroll/zoom with custom touch handlers that
work correctly with the CSS scale transform:
- Single-finger scroll with scale-compensated sensitivity and momentum
- Pinch-to-zoom (1x-5x) centered on pinch point, with pan when zoomed
- Tap/click suppression to prevent cursor jumping
- Row fill: resize xterm viewport to fill WebView height (display-only)
- Disable native zoom (user-scalable=no) since custom handler replaces it
Co-authored-by: Orca <help@stably.ai>
Add end-to-end mobile-fit override system that lets a phone client resize
a PTY to phone dimensions and restore to desktop dimensions on disconnect
or user request.
Key changes:
- Runtime: resizeForClient() manages override lifecycle with ownership,
clamping, and auto-restore on client disconnect. Always resizes PTY
directly on restore instead of relying on fragile renderer chain.
- RPC: terminal.resizeForClient method for mobile clients via WebSocket.
- Renderer: safeFit() respects active overrides, desktop banner with
Restore button, override change listener with rAF + fallback safety net.
- Mobile: fit-to-phone with CSS scale zoom, auto-fit only for terminals
created during the mobile session (not existing desktop terminals).
- Tests: 14 unit tests + 3 integration tests for override lifecycle.
Co-authored-by: Orca <help@stably.ai>
Use the WebView's native viewport scaling and pinch-to-zoom instead of
custom CSS transforms and touch handlers. The terminal renders at
desktop dimensions with initial-scale=0.5 for a readable default, and
the browser's built-in zoom handles pinch correctly (center point,
scroll directions, no swipe conflicts).
Co-authored-by: Orca <help@stably.ai>
React Native's built-in WebSocket rejects self-signed TLS certs with no
way to pin by fingerprint. Use plain ws:// for now — per-device token
auth still prevents unauthorized access. TLS will be re-enabled once the
Expo native module for certificate pinning is built (Phase 1 checklist).
Co-authored-by: Orca <help@stably.ai>
The hand-rolled X.509 certificate generation produced malformed certs
that OpenSSL and Node's HTTPS server couldn't parse, silently preventing
the WebSocket transport from starting. Replace with openssl CLI call
which is available on all target platforms.
Also enable WebSocket transport by default so mobile clients can connect.
Co-authored-by: Orca <help@stably.ai>
Adds the Orca Mobile companion app (Expo SDK 55 / React Native 0.83) with:
- QR code pairing flow: scan orca://pair# URL, validate auth, save host profile
- WebSocket RPC client with auto-reconnect, exponential backoff, streaming support
- Three screens: host list, worktree list, terminal session with live output
- Mock WebSocket server for development without a running Orca desktop instance
- Desktop-side: TLS certificate management, device registry, mobile IPC handlers,
WebSocket transport layer, and pairing offer encode/decode with Zod validation
- Unit tests for WebSocket transport and pairing codec
Co-authored-by: Orca <help@stably.ai>
Electron <webview> elements run in a separate process, so clicks inside
them don't dispatch pointerdown on the renderer document. Radix
DropdownMenu relies on document pointerdown for outside-click detection
and therefore misses webview clicks, leaving the menu stuck open.
Listening for window blur catches the moment focus leaves the renderer
(including into a webview) and closes the menu.
Co-authored-by: Orca <help@stably.ai>
On close/quit (especially auto-updater relaunch via quitAndInstall),
Electron/OS emit resize/move/unmaximize while the window is torn down.
The debounced persistence was capturing those near-minimum bounds, so
the next launch came up at minWidth x minHeight.
- Freeze bounds persistence on window 'close' and app 'before-quit'
(updater strips the window 'close' listener before quitting, so
the app-level latch is needed as a second line of defense).
- Release the freeze on will-prevent-unload so aborted Cmd+Q keeps
saving bounds.
- On restore, discard saved bounds at/below the min dimensions to
recover users who already persisted corrupt bounds.
Co-authored-by: Orca <help@stably.ai>
Before removing a <webview> that currently owns focus, blur the active
element and refocus the window. Without this, macOS hands activation to
the previously-active app (e.g. Slack) because the focused webContents
is gone with no replacement, pulling the user out of Orca on worktree
delete.
Co-authored-by: Orca <help@stably.ai>