* refactor(mobile-web): extract the page render harness The shell double, the CSP/bridge constant readers and the bundle server were private to mobile-web-app-render.test.mjs, so a second check against the same page had no way to reach them. Moved as-is into a module both can import; the double also gained a `replies` map so a check can answer one method and leave the refusal in place for everything else. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): give reanimated mapper hooks a dependency array The bottom drawer never slid onto the screen in the web shell page: `progress` animated to 1 and `withTiming` reported finished, but the sheet kept the translateY of the animation's first frame and sat one viewport below the fold, with its invisible backdrop swallowing the next touch. Cause, bisected in the browser: `useAnimatedStyle` reads its mapper inputs from `updater.__closure` (hook/useAnimatedStyle.js), which only Reanimated's Babel plugin writes. The page is bundled by esbuild, which runs no Babel, so `__closure` is undefined; with no dependency array either, `inputs` is empty and `startMapper` registers a mapper that listens to no shared value. It runs once and never again. Reanimated does throw for exactly this, but behind `__DEV__`, which the bundle builds out, so the page reports nothing. The rAF loop stopping after one write is the observable end of it. Not a WebKit fault. Headless Chromium parks the sheet the same way (translateY(843) vs WebKit's translateY(841)), so the earlier JavaScriptCore-vs-V8 reading does not hold, and the pin added here runs on both engines rather than on Chromium alone. WebKit is downloaded in the mobile_web_app job for it. Every mapper-backed call site takes the same array, not just the drawer's: RightDrawer and DragReorderList are the same defect on the same bundler. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): census the reanimated hooks that need a dependency array The drawer pin covers MountedBottomDrawer only, and the failure mode is silent: a new `useAnimatedStyle`, `useAnimatedProps` or `useDerivedValue` without an array animates once on the phone's native build and freezes in the web page, with no error on either. Parsed rather than grepped so a call spanning lines, or one whose second argument is not an array, is still seen. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile-web): state motion-on as the drawer pin's precondition Under `prefers-reduced-motion: reduce` Reanimated finishes `withTiming` in one frame, so a mapper that only ever runs once still writes the final translateY and the pin goes green on the broken build. Measured: the unfixed bundle under reduced motion lands at translateY(0) with the sheet on screen in both engines, which is also what the Android emulator does with animator scale off — the same single write, not a healthy animation. The context now says no-preference and the page is asked to confirm it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): census useAnimatedReaction, whose deps are its third argument Same fallback as the other three (hook/useAnimatedReaction.js:26-34), so the same silent freeze applies. Its shape is not the same: the array is argument three, behind `prepare` and `react`, and both callbacks run inside the one mapper it starts, so both count as updaters. Indexing it like the others would have read the `react` callback as the array. No call site today; this is the gate for the first one. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): require the dependency array to list every value the updater reads An array proves a call was written, not that the mapper listens to everything it reads. On web `inputs` becomes exactly that array (hook/useAnimatedStyle.js:338-341), so a value read but not listed is a value the mapper never hears about: the updater stops re-running when only that one changes. Same freeze as no array at all, in one prop rather than all of them. Reads only. The first fixture caught this check counting `opacity.value = v` as a read, which it is not -- a written value is an output, and demanding it in the array would be noise at every `useAnimatedReaction`. Assignment targets and increments are excluded; a value both read and written is still required. Verified against the tree by dropping `translateY` from the bottom drawer's array, which the census names at mounted-bottom-drawer.tsx:286. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): resolve the hook through the file's imports, not by spelling Matching the callee's text both missed and invented. `useAnimatedStyle as useAS` and `Reanimated.useAnimatedStyle` are the same hook wearing another name and went unchecked; a local helper that happens to be called `useDerivedValue` is not this hook and would have been flagged. Each local name is now resolved through the file's imports from `react-native-reanimated`, named, aliased or namespace member. A second argument that is not a literal array now counts as present rather than missing: the hook only needs an array to exist, and this file cannot see what a hoisted `const deps = [...]` holds, so completeness covers literal arrays only. Resolution can fail closed, which would read exactly like a clean tree, so the census now asserts it saw the calls before asserting none are missing. Checked against the tree by dropping `translateX` from RightDrawer's array, which it names at RightDrawer.tsx:156. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile-web): select the drawer sheet by name, not by its corner radius The pin walked up from the handle to the first ancestor with a 16px top radius, so it found the sheet through a styling token. Change that radius and the pin reports `sheet: false` -- a red naming the selector rather than the animation it exists to watch, on a change that broke nothing. The sheet now says what it is. `testID` on the RN side renders as `data-testid` on web (react-native-web createDOMProps/index.js:832), which is the one line of product change this needs. Re-verified after retargeting: still red on both engines with the dependency arrays removed (translateY 843.271 chromium, 841.447 webkit), green with them. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * docs(mobile-web): say why the motion option must precede navigation Reviewer follow-up on the reduced-motion guard. The context option and the `goto` order are both load-bearing, and nothing in the file said so: Reanimated reads `matchMedia('(prefers-reduced-motion: reduce)')` once into a module-level const at import (ReducedMotion.js:8-10), so a `page.emulateMedia()` after navigation would leave the assertion passing over a value already latched true. Comment only. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile-web): name the drawer pin's precondition instead of asserting past it CI's Linux WebKit failed this pin at `matrix(1, 0, 0, 1, 0, 844)` -- exactly the viewport, the mount-time value, not a first-frame 843.x. Nothing animated there, so the pin was reporting a parked sheet without being able to say whether the mapper was subscribed. Two different faults, one message. `requestAnimationFrame` separates them and sheet writes do not. `withTiming` schedules a frame per step (valueSetter.js) whether or not a mapper listens, so frames across the window mean the shared value moved; the assertion now names that. Counting sheet writes as the precondition inverts the diagnosis: measured on the broken build, "written more than once" fires first and calls the defect this pin exists to catch an engine that does not animate. Sheet writes stay, as a second statement of the subject and as context in the transform failure, which now reads "1 style write(s) on the sheet across 30 frame(s)" on the broken build. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile-web): wait for the drawer to arrive, not for a clock The pin paused a fixed 1s after the sheet opened and then read the transform, which makes it a race on a loaded runner: a healthy engine that is merely slow reads as parked, and the red names the transform rather than the wait. It now waits for the settled transform, times out at 15s, and asserts on whatever it found either way, so a genuinely parked sheet gives the same red with the timing assumption removed. On the broken build that red now reads "1 style write(s) on the sheet across 3635 frame(s)", which says the fault in one line. Aimed at CI's Linux WebKit red rather than proven against it: eight container runs on the Playwright Linux image never reproduced that failure. See the report for what the container did and did not show. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile-web): stop asserting on the sheet's style-write count The count cannot carry an assertion in either direction. Measured under `--cpus=0.35` in Playwright's Linux image, a healthy page starved of frames reaches translateY(0) in a single write, because `withTiming` covers the whole 180ms in one step when one step is all the frames it gets. "Written more than once" would have redded that page, which is a CI runner under load -- the exact situation this pin keeps meeting. So the transform is the only subject, `requestAnimationFrame` during the window is the only precondition, and the write count is context in the failure text. Also worth recording against the CI log: exactly `matrix(1, 0, 0, 1, 0, 844)` is reproducible here on the broken build, as the single mapper run landing at progress 0. It is the mapper's signature as much as a dead engine's, so it does not on its own say which failed -- the frame and write counts now printed beside it are what separate them. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): count a value read under a unary operator as a read `isWriteTarget` took any prefix-unary parent for a write, so `!hidden.value`, `-offset.value`, `+x.value` and `~x.value` were dropped from the reads the dependency array has to list. A style that gates on `!hidden.value` would have passed the census while its mapper never listened to `hidden` -- the exact freeze this file exists to catch, hidden by the check meant to catch it. Only `++` and `--` mutate, so the prefix branch is narrowed to those two. Postfix needs no narrowing: `++` and `--` are the whole set there. Red-first with a negation fixture and a unary-minus fixture; the increment fixture holds the other side, that a value only incremented is still not required. Found by a review bot on #21592. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
Orca
中文 · 日本語 · 한국어 · Español · Français · Português
The AI Orchestrator for 100x builders.
Run Codex, ClaudeCode, OpenCode or Pi side-by-side — each in its own worktree, tracked in one place.
Download Orca
Features
Also in the box:
- Quick open — Search across worktrees, files, agents, commands, and repo context without leaving your flow.
- Account switcher & usage tracking — See Claude and Codex usage and rate-limit resets, and hot-swap accounts without re-logging in.
- Rich repo previews — Preview Markdown, images, PDFs, and repo docs in the workspace.
- Computer Use — Let agents operate desktop apps and visible UI when a workflow needs real interaction.
- Notifications and unread state — Know when an agent finishes or needs attention, then mark threads unread to come back later.
- And many, many more — we ship daily, so this list is perpetually behind. The changelog is the real feature list.
Supported Agents
Works with any CLI agent — if it runs in a terminal, it runs in Orca.
Claude Code
Codex
Grok
Cursor
GitHub Copilot
OpenCode
MiMo Code
Amp
OpenClaude
Antigravity
Pi
oh-my-pi
Hermes Agent
Devin
Goose
Auggie
Autohand Code
Charm
Cline
Codebuff
Command Code
Continue
Droid
Kilocode
Kimi
Kiro
Mistral Vibe
Qwen Code
Rovo Dev
+ any CLI agent
Install
Desktop — macOS, Windows, Linux
- Download from onOrca.dev
- Or grab a build directly: macOS Apple Silicon · macOS Intel · Windows (.exe) · Linux AppImage · All builds
- Running
orca serveon a headless Linux server? See the headless Linux server guide.
Or via a package manager:
# macOS (Homebrew)
brew install --cask stablyai/orca/orca
# Arch Linux (AUR) — or stably-orca-git to build from source
yay -S stably-orca-bin
Mobile Companion — iOS, Android
Pair with your desktop app to monitor and steer your agents from your phone.
- iOS: Download on the App Store or join TestFlight
- Android: Download APK 0.0.50 · Install guide
Community & Support
-
Discord: Join the community on Discord.
-
Twitter / X: Follow @orca_build for updates and announcements.
-
WeChat: Scan to join the Orca community WeChat group 8. Group 8 may be full; if so, scan the Group 9 QR code instead.
-
Feedback & Ideas: We ship fast. Missing something? Request a new feature.
-
Privacy: See the privacy & telemetry docs for what anonymous usage data Orca collects and how to opt out.
-
Show Support: Star this repo to follow along with our daily ships.
Developing
Want to contribute or run locally? See our CONTRIBUTING.md guide.
The relay that pairs the mobile app with a desktop host is also in this repository under
cloud/, with a separate pnpm workspace and setup guide.
Signed Builds
Windows code signing sponored/provided by SignPath.io, certificate by SignPath Foundation.
License
Orca is free and open source under the MIT License.












