Clearing pendingRestoreRef on both early-return paths prevents a stale
armed restore from firing on a later measurement tick with an unchanged
restoreSignal, which could re-trigger scroll writes and flicker.
- Skip marking no-op writes since they emit no scroll event and could
claim a later user scroll within the mark-matching epsilon
- Distinguish browser clamp (viewport can't reach target) from real
user takeover using in-range origin and direct-input checks
- Sync scrollOffsetRef/anchor bookkeeping on our own programmatic
writes so divergence checks don't misread them as user scrolling
- Arm pending restore before skip guards so a restore owed during
active input retries once input settles instead of being dropped
- Guard the rAF-deferred restore against a user scroll that
disarmed it between scheduling and execution
- Wall-clock "recent direct input" heuristics misclassified scroll events
during main-thread jank, causing stale anchor restores to fight active
wheel scrolling and produce visible flicker.
- Replace timing-based classification with explicit programmatic scroll
marks: every self-initiated scroll write (virtualizer corrections,
anchor restores, scrollToIndex) is marked so listeners can distinguish
it from genuine user input, including clamped landings from shrunken
scroll ranges.
- Gate anchor restore on a structural restoreSignal (row add/remove/
collapse, remount, layout flips) instead of every measurement tick, so
restores only run when something actually changed.
- Extract restore/listener logic into dedicated modules
(virtualized-scroll-anchor-listener.ts, virtualized-scroll-anchor-restore.ts,
programmatic-scroll-marks.ts) for testability.
- Leave WorktreeList on the legacy wall-clock suppression for now, with a
TODO to migrate once the new approach is validated for the sidebar.
PR #5071 (36277801e) accidentally dropped the LinearAgentSkillSetupPrompt
modal from WorktreeCard, orphaning the component. Restore the exact
wiring: render on the active worktree when it has a linked Linear issue.
Also surface the decoupled orca-linear agent skill on the Linear task
provider settings card: install state via useInstalledAgentSkillNames,
copyable install/update command resolved for the agent runtime, and a
remote-setup note when a runtime environment is active. The legacy-aware
update-command selection moves into a shared lib module so the sidebar
prompt and the new CTA stay in sync.
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
* Consolidate mobile source control into a single tabbed hub
Unify the changes list, pull request details, and commit history into
a single multi-segment panel. This improves navigation and state sharing
across different lenses of a worktree's source control.
- Add a segmented control to switch between Changes, PR, and History
- Introduce a persistent branch status card with an integrated PR chip
- Redirect standalone PR and history routes to the new unified hub
- Extract reusable UI and logic for the history list and PR summary
* Keep mobile source control tabs mounted to preserve view state
* Keep PR and History segments mounted (using display: 'none' when hidden) to preserve fetch, scroll, and expand states during tab switches.
* Decouple the History list from blocking on Git status loading.
* Support deep linking directly into the history tab of the main panel instead of using a standalone route.
* Enable retrying failed loads by reviving the transport loop if parked.
* Fix PR chip accessibility label and comment check.
* Optimize and integrate mobile PR view within source control hub
- Lazy-load heavy PR comments and descriptions (Phase 2) only when the
PR tab is active, using fast metadata (Phase 1) for the branch chip.
- Unmount the PR body when inactive to avoid unnecessary comment tree
re-renders and preserve WebView resources during commit text editing.
- Implement soft-refresh on HEAD advancement to keep the ready UI
visible while re-fetching checks post-commit.
- Display the "Aborting..." label only when a merge or rebase abort
is actively in flight.
- Memoize the git history list and skip branch identity RPCs when
gating the dock icon.
* Improve mobile git views and concurrent rendering safety
- Pass the `origin` parameter through history and PR redirect routes.
- Move source control panel ref updates to `useEffect` to prevent side
effects during concurrent renders.
- Resolve commit file changes to empty if disconnected to avoid a stuck
loading spinner.
- Standardize PR sidebar header button styling and accessibility labels.
* Resolve PR repo probe without active branch to avoid forever spinner
Previously, checking if a repository is a GitHub remote required an
active branch. In a detached HEAD or mid-rebase state (where the branch
is null), the probe never resolved, leaving the PR panel on a forever
spinner.
Decouple the repository probe from the branch presence so the panel
can correctly display the "Current branch unavailable" state. Also,
hide the PR status chip when no branch is active to avoid a spinner
on the chip.
* fix: propagate hook-only agent status to Remote Orca Server clients
On a headless Remote Orca Server, agent-status hooks (OSC 9999) updated the
retained row map but never republished PTY-backed session snapshots — only
terminal *title* changes did. Paired desktop/web/mobile clients therefore
kept a stale agent state (e.g. opencode working/idle) until relaunch, and
even title-driven updates carried an empty prompt and no agent identity
because the snapshot builder only used the title heuristic (#7970).
- retainAgentRowSnapshot reports client-visible changes (state, prompt,
agent type, tool, interactive prompt, interrupted) so handlePtyData can
republish snapshots on hook-only transitions without fanning out a
rebuild per repeated same-state hook ping.
- buildPtyMobileAgentStatus prefers the fresh retained hook payload over
the title-only fallback, so clients see the real state/prompt/agentType
and interactive prompts. The non-agent-title suppression (#1437 stuck
spinners) still wins unless the hook shows a live tool/question signal,
and it now also covers leaf-backed panes with no PTY record.
Co-authored-by: Orca <help@stably.ai>
* fix: refetch remote projects when the client-events stream replays
worktreesChanged/reposChanged emitted during a transport gap are lost, not
queued. A quick drop can replay without flipping the environment
unreachable, so the reachability-transition refetch never runs and a
server-created worktree stays invisible until relaunch (#7970). Request a
debounced project refresh on the replay tag, mirroring the SSH-state
refetch that already rides it.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(preflight): resolve WSL/SSH agent paths past shell aliases
LeanCTX and similar tools wrap claude/codex as interactive shell aliases.
command -v then returns alias text, which fails absolute-path detection and
hides installed agents (#7816).
Prefer bash type -P, then zsh type -p, then command -v for dash/sh fallback
in WSL agent discovery, WSL isCommandOnPath, and remote relay probes.
* fix(preflight): harden alias-safe PATH lookup chain
Require non-empty results between type -P, type -p, and command -v so bash
type -p empty success cannot skip later lookups.
* fix(preflight): resolve agent executables directly from PATH
---------
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
* Improve workspace cleanup list
Co-authored-by: Orca <help@stably.ai>
* Address workspace cleanup review feedback
Co-authored-by: Orca <help@stably.ai>
* Fix workspace cleanup perf findings
Co-authored-by: Orca <help@stably.ai>
* Avoid stale cleanup progress cache
Co-authored-by: Orca <help@stably.ai>
* Complete workspace cleanup perf fixes
Co-authored-by: Orca <help@stably.ai>
* Fix worktree list option forwarding
Co-authored-by: Orca <help@stably.ai>
* Address workspace cleanup review nits
Co-authored-by: Orca <help@stably.ai>
* Fix workspace cleanup removal review findings
- Fail a queued removal that now needs a force the user never approved
(confirm-time approvedCandidates snapshot compared in preflight)
- Reword the 120s removal timeout to say removal continues in background
- Wire suppressPreservedBranchToast into cleanup removals
- Stop statting a repo after the first activity metadata timeout
- Document the WSL 9P best-effort stat gap; drop unused locale key
Co-authored-by: Orca <help@stably.ai>
* Split workspace-cleanup slice test to satisfy max-lines
Rebasing onto latest main pushed the combined store-slice test over the
800-line cap. Extract shared fixtures into a test harness and split the
suite into scan-progress and removal-preflight files instead of adding a
forbidden max-lines suppression.
---------
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
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>
* Harden layout validation, watcher lifecycle, and connection robustness
- Throw instead of silently skipping when the packaged daemon-entry is
missing, preventing layout regressions from passing build checks.
- Terminate idle parcel-watcher processes to reclaim native handles and
avoid crash-prone native node module teardowns on shutdown.
- Bind the persisted WS fallback port first to prevent orphaning active
mobile pairings when the preferred port becomes free again.
- Cap concurrent disk reads for restored dirty tab verification at three
to prevent startup connection bottlenecks on remote SSH workspaces.
* Queue file IDs instead of snapshots in restored conflict scans
This avoids using stale file snapshots (e.g., outdated disk signatures)
if a tab is saved, closed, or re-baselined while waiting in the queue
behind the concurrency limit. The live state is now fetched from the
store and validated immediately before initiating the disk read.
A wedged Tailscale tunnel (known iOS failure mode) produces no AppState
or network-type transition, so no revival nudge ever fires and the
reconnect loop parked permanently at its give-up cap — users had to
toggle Tailscale off/on just to force a transition (#7824).
- rpc-client: past the give-up cap, drop to a 90s trickle dial instead
of parking so the session self-heals once the tunnel recovers.
- host screen: nudge the shared client on focus so opening the host
retries immediately instead of waiting out a backoff/trickle timer.
- connection-health: warning/unreachable verdicts on 100.64/10 or
*.ts.net endpoints now carry a 'check Tailscale' hint, shown on the
home host list and the in-session status line after ~3 failed
attempts.
- troubleshoot: 'Cannot reach <tailnet-ip>' now says to check
Tailscale, adds a dedicated Tailscale section, and stops telling
Tailscale users to disable their VPN (that advice killed their only
route to the host); sections extracted to
troubleshoot-common-issues.tsx to stay under the max-lines cap.
Co-authored-by: Orca <help@stably.ai>
On macOS 26, UNUserNotificationCenter aborts when executables are run
from Contents/Resources because bundleProxyForCurrentProcess returns
nil.
Moving the orca-notification-status helper to Contents/MacOS next to
the main Electron executable ensures proper bundle resolution and
avoids immediate crashes.
* auth v1
* fable review
* lint
* Account menu with org membership management
Default UX is a compact account menu (sign in, organization selection, sign
out) that renders only when cloud auth is configured; adds an organization
members dialog (invite, role, remove) gated on server-side role checks. The
multi-profile switcher UI is preserved behind ORCA_MULTI_PROFILE_UI=1.
Co-authored-by: Orca <help@stably.ai>
* Gate the optional account sign-in UI to dev builds
The account switcher stays hidden in packaged builds while the feature is
in progress. Dev builds still show it when the client env vars are set, and
a dev-only Settings > Dev Tools > Orca Cloud section mirrors it.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
Two onboarding-screen bugs:
- The final "notifications" step blocked click-off/Escape dismissal, unlike
every other step. Remove the notifications-only guard so the skip
confirmation opens on all steps; the footer "Skip to project setup" stays
hidden there since the primary button already hands off to Add Project.
- A skipped "integrations" step (GitHub CLI already installed) still rendered
as a dead, disabled stepper dot the user skipped past on Continue. The
stepper now drops all skipped steps (integrations + Windows terminal)
entirely instead of showing an unreachable dot.
Allowing dismissal on the last step let a click-off race the "Add your first
project" completion handoff (both call closeWith) and double-write onboarding
state / double-fire telemetry. Make closeWith idempotent with a first-wins
latch. Also map the displayed step index through resolveStepIndex so a
momentarily-skipped resume step can't flash "1 of N".
Verified: onboarding unit tests, full onboarding e2e spec (rewritten
notifications test locks in the new dismiss behavior), typecheck, lint, and
live Electron.
The warm-reattach mouse-leak e2e (added in #7893) awaited the readiness
marker unconditionally, so it hard-failed on sandboxed runners whose PTY
echoes input but never execs a shell. Wrap the gate in a skip guard that
mirrors the existing pane-manager guard: run the assertions where the
shell executes, and skip gracefully otherwise instead of a false red.
Co-authored-by: Orca <help@stably.ai>
* feat(onboarding): state-aware macOS notification permission step
The Set up notifications step showed a one-size-fits-all 'Open Mac
Settings' button that simultaneously fired the macOS permission prompt
and opened System Settings — two competing system UIs, with System
Settings unnecessary for the common fresh-install case.
Electron exposes no API to read macOS notification authorization, but
scheduling outcomes do reveal it: a silent probe notification's 'show'
event means permission is granted, 'failed' means delivery is blocked.
A new notifications:probeDelivery IPC runs that probe (cached via
passive delivery evidence and a persisted confirmation flag), and the
onboarding card now renders the real state:
- fresh install: the probe itself pops the native Allow dialog the
moment the step opens; the card flips to 'Notifications are enabled'
automatically when the user clicks Allow (silent 2.5s re-probes)
- blocked: amber card with an Open System Settings deep-link, which
also self-heals once the user flips the toggle
- granted: green confirmation card
The test-notification button now feeds the same card instead of the
ambiguous 'if no banner appeared…' toast during onboarding.
Co-authored-by: Orca <help@stably.ai>
* fix: don't log expected probe rejections while polling for permission
Co-authored-by: Orca <help@stably.ai>
* fix: amber warning styling + single stable dev bundle id for notifications
- Blocked card now uses the app's shipped amber idiom (tinted surface with
amber title/body) instead of white-on-amber-wash, which read muddy in
dark mode; macOS permission card split into its own module to stay under
the max-lines budget.
- Dev instances previously minted a unique macOS bundle id per
branch x Electron version, registering a new Notification Settings entry
every time ('Orca: <branch>' rows piling up forever) and pointing the
settings deep-link at ids System Settings can't resolve. All dev
instances now share com.stablyai.orca.dev: one Notification Center
entry, one permission grant covering every dev build.
Co-authored-by: Orca <help@stably.ai>
* fix: tighten macOS permission card copy
Body copy was one long sentence; now a single short instruction with
'Updates automatically.' as a separate dimmer line. Also repairs locale
catalog parity for keys introduced by commits rebased into this branch.
Co-authored-by: Orca <help@stably.ai>
* fix: drop 'Updates automatically.' line; ad-hoc sign dev app copies
The extra line read as confusing filler — the cards now carry one short
instruction each.
Dev Electron copies had broken code signatures (the Info.plist identity
edits invalidate the ad-hoc seal), which macOS punishes by refusing
Notification Center registration outright: every dev notification failed
with UNErrorDomain error 1, the app never appeared in System Settings >
Notifications, and the settings deep-link had nothing to land on. The dev
runner now ad-hoc re-signs the copied bundle after the plist edits
(bundleLayoutVersion bumped so stale unsigned copies are recreated).
Verified end-to-end: runner-built copy passes codesign --verify --deep,
probe delivery returns delivered, the onboarding card flips green in dev,
and the deep link opens the dev app's own notifications pane.
Co-authored-by: Orca <help@stably.ai>
* fix: drop confusing copy line; session-only permission evidence
Removes the 'Updates automatically.' line from both permission cards.
Also drops the persisted notificationDeliveryConfirmed flag: OS-level
permission changes between sessions, and a stale positive rendered a
false green card. Delivery evidence is now session-scoped only.
Documented detection ceiling (verified empirically on macOS 26): while
the permission dialog is unanswered — and when notifications are toggled
off in System Settings after being authorized — macOS accepts requests
and silently swallows them, with no public API (Notification Center
delivered-history and legacy ncprefs both included) able to distinguish
that from real delivery. 'failed' remains definitive for unsigned builds
and dialog-level denials.
Co-authored-by: Orca <help@stably.ai>
* feat: real macOS notification permission readout via native helper
Electron has no API for UNUserNotificationCenter authorization, and every
observable fallback lies: scheduling succeeds (and getHistory lists the
notification) even while macOS silently swallows display because the
permission dialog is unanswered or notifications were toggled off in
System Settings. The onboarding card therefore showed 'enabled' after the
user disabled notifications.
Adds native/notification-status-macos: a tiny Swift binary that prints
the app's real authorization status. It runs from inside the app bundle
(NSBundle resolves the bundle by walking up from the executable) and
embeds the app's CFBundleIdentifier in a __TEXT,__info_plist section so
every codesign --force pass — electron-builder's signing or the dev
runner's ad-hoc deep sign — derives the identifier macOS keys
notification records to. Spawning it from the app returns authorized /
denied / not-determined exactly matching System Settings.
notifications:probeDelivery now prefers this readout (authoritative,
silent), firing at most one dialog-trigger probe per session while the
decision is pending, and falls back to the previous delivery-probe
heuristics when the helper is unavailable. The card polls the readout
silently in every state, so toggling Allow notifications in System
Settings flips the card within a poll — both directions, verified live.
Test notifications also consult the readout so 'delivered' is no longer
claimed for swallowed notifications.
Packaged builds ship the helper via extraResources and sign it in
afterPack like the computer-use helper; dev copies compile it on demand
(swiftc, non-fatal when missing) with the shared dev bundle id.
Co-authored-by: Orca <help@stably.ai>
* feat: in-app fallback for swallowed notifications + permission card in Settings
- Dispatch now consults the authorization readout before creating a
native notification: when macOS would silently swallow it (denied or
prompt unanswered) it returns reason 'blocked-by-system' instead of
piling invisible notifications into Notification Center. The terminal
notification path surfaces that as a once-per-session in-app toast
with an Open System Settings action. Mobile fan-out is unaffected.
- Settings > Notifications now shows the same live permission card as
onboarding (moved to components/notifications/), polling the readout
so System Settings changes reflect within seconds, and the test
button updates it inline.
- Test sends that are blocked at the OS level now show the
settings-pointing failure toast instead of a generic error.
Co-authored-by: Orca <help@stably.ai>
* fix: hide macOS permission card while Orca notifications are disabled
A green 'Notifications are enabled' card next to a disabled Enable
Notifications toggle read as a contradiction — the card now renders (and
the readout polls) only while Orca's own notifications setting is on.
Also single-flights the authorization helper so simultaneous agent
completions share one readout process.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>