Portal the non-blocking reconnect banner into each pane container so WebGL
compositing cannot paint terminal text through it, raise z-index above
pane-local chrome, and use a fully opaque card background.
* feat(agent-rows): opt-in conversation-name labels for agent rows
Sidebar worktree-card and dashboard agent rows always show the last
message sent to the agent, so rows relabel on every turn and a 'continue'
prompt becomes the row's name. Add an opt-in Agents setting that labels
rows with the conversation name instead, resolved with the tab bar's
precedence: manual rename, quick-command label, OpenCode session title,
generated title (behind its existing setting), then the agent-set live
title. Live titles count only when they carry a real name - status
decoration is stripped, and pure status, identity-echo, spinner+cwd, and
placeholder titles fall back to the last-message label. Subagent child
rows keep their own descriptions.
Locale note: sync:localization-catalog also restored parity for keys
already missing on main (add-host, sleep-worktree copy).
* fix(agent-rows): read the live tab so late renames and titles surface
Row data patches live entries in place and keeps the tab snapshot from
row creation, so a rename or agent-set title landing after the row was
built never reached the conversation-name resolver. Select the current
tab from the store in the hook; retained rows without a live tab keep
the snapshot fallback.
* fix(agent-rows): reject status labels and bound lookup work
Reject native provider/status titles and Windows/UNC cwd frames so conversation-name mode falls back to the user's last message instead of relabeling rows with identity or path text. Keep default-off and subagent rows off the tab map, and share a WeakMap-backed tab index across mounted rows to avoid repeated linear scans on store writes.
* feat(agent-rows): make conversation names the default
Always prefer a usable conversation name for sidebar and dashboard agent rows, falling back to the last message when the resolver rejects a title. Remove the preference, settings UI, search copy, and catalog entries. Keep subagent labels unchanged and reject generic Terminal N placeholders so partial tab snapshots cannot hide meaningful prompts.
* chore(i18n): sync catalog after main merge
* fix(agent-rows): preserve same-tab child labels
* fix(agent-status): resume finished agents after macOS logout instead of a bare shell (#9454)
After the daemon-retirement fix (#9826) recovers terminals across a macOS
logout, a running Claude Code (or other resumable TUI agent) that had
finished its turn came back as a BARE SHELL — the user had to open a new
tab and `claude --resume` in every worktree to get logged back in.
Root cause: setAgentStatus writes a persisted 'live recovery' sleeping
record (the cold-restore --resume anchor) for a live resumable agent, but
drops it once the turn is 'done' — except for Pi, which was special-cased
(retainsPiRecoveryIdentity). A finished Claude/Codex turn leaves the TUI
alive and resumable at its prompt just like Pi, so an idle agent lost its
anchor; a cold restore after an abrupt app death (logout) then found
nothing to resume and dropped to a bare shell.
Fix: generalize the Pi-only recovery-identity retention to every resumable
TUI agent (isResumableTuiAgent), gated on a valid resume argv, so a finished
agent keeps its persisted providerSession anchor and the already-shipped
cold-restore --resume path brings it back logged in. Extends an existing
pattern; no new subsystem.
Reproduced + validated live in Electron via the login-session-death seam:
idle Claude -> daemon retire -> app relaunch previously bare-shelled; now
spawns 'claude --resume <session>' and restores the conversation logged in.
* test(agent-status): update sleep-hygiene assertions for retained live recovery anchor (#9454)
* fix(editor): preserve PDF zoom when file content reloads
PdfViewer rebuilt the pdf.js document on every content change and always
reset to page-width, so external PDF updates wiped the user's zoom. Keep
a fit-width vs absolute preference across reloads for the same file path.
Closes#10165
* refactor(editor): dedupe PDF zoom handlers into shared callbacks
Collapse six copies of the step-scale-and-record-preference logic (three
directions duplicated across the keyboard handler and toolbar callbacks)
into a single stepZoom callback; the keydown handler now reuses zoomIn/
zoomOut/zoomReset so keyboard and toolbar can't drift apart.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* perf(startup): defer full worktree scan until after session restore
* fix(startup): normalize worktree recovery session keys
* fix(startup): preserve persisted workspace hydration
* fix(startup): keep SSH repos in pre-hydration worktree fetch
Round 1 review fixes for the selective pre-hydration fetch:
- The prefetch filter gated on LOCAL_EXECUTION_HOST_ID, which excluded
SSH repos. Old full-scan code populated worktreesByRepo for disconnected
SSH repos via the local metadata fallback (no network), so hydrateTabsSession
/ editor / browser preserved their chrome. Excluding SSH dropped that chrome
at hydration and setHydrationSucceeded made the loss durable. Include SSH by
excluding only runtime-owned repos (which hydrate via placeholders instead).
- collectWorktreeHydrationRepoIdsFromSession never collected activeRepoId, so a
repo referenced only by active-repo state (no active worktree, no tabs) was
not prefetched, breaking hydrateWorkspaceSession's main-worktree fallback.
* fix(startup): re-prune visit timestamps after deferred full worktree scan
Round 2 review fix. The selective pre-hydration fetch only populates
session-referenced repos, so the one-shot pruneLastVisitedTimestamps at
startup no longer sees non-session repos and keeps their deleted-worktree
entries (the prune defers unloaded repos to preserve SSH focus recency).
The old full pre-hydration scan had populated all local repos before the
prune. Re-run the prune after the deferred fetchAllWorktrees so stale
lastVisitedAtByWorktreeId entries are dropped once every repo is
authoritative; disconnected SSH stays non-authoritative and is kept.
* fix(startup): exclude unbounded history maps from hydration repo enumeration
Round 3 review fix. collectWorktreeHydrationRepoIdsFromSession collected
repo ids from lastVisitedAtByWorktreeId and defaultTerminalTabsAppliedByWorktreeId,
which accumulate one entry per worktree ever focused / given default tabs.
On the long-lived large profile this fix targets (#9441), those maps
reference nearly every repo, so the 'selective' pre-hydration fetch
degenerated back toward the full all-repos scan the fix removed.
Split the keyed-field list: repo enumeration now scans only chrome-bearing
fields (open tabs/editors/browser), while folder-key detection keeps
scanning all fields. The history maps hydrate unfiltered regardless, and
the deferred re-prune reaps their stale entries, so no data or recency is
lost. Also pin the round-2 re-prune ordering in the routing test.
* fix(startup): prevent competing full worktree scans
* fix(startup): keep selective hydration scans bounded
* fix(startup): ignore stale tab selection markers
* fix(startup): keep deferred worktree recovery selective
* test(windows): dismiss startup overlays before terminal setup
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(status-bar): seed Resource Manager closed badge from daemon inventory
The closed chip counted tab/layout PTY wake hints (inflating terminal
count) and never fetched memory until the popover opened (showing "—").
Cache listSessions for the badge, seed memory on session ready, and drop
the wake-hint closed selector.
* fix(status-bar): update session inventory ref in an effect
CodeRabbit/React Doctor flagged mutating sessionInventoryRef during
render; keep the write in useEffect after commit.
* fix(status-bar): follow daemon session lifecycle events
* perf(status-bar): skip inventory scans for known PTYs
* perf(status-bar): bound resource inventory refreshes
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* feat(source-control): show current branch without evicting Create PR
#9787 added the current-branch identity to the Source Control header but
did it by replacing the Create PR button's toolbar slot, so #10032 reverted
the whole thing. Create PR is the primary entry point into the
stage→commit→push→generate-PR flow, so it can't be traded away.
Restore the branch identity as its own row above the toolbar so it coexists
with the Create PR button (Option 2 layout). Detached HEAD renders in the
same identity row via DetachedHeadBadge (re-adds its tabIndex/aria-label),
replacing the separate below-toolbar badge row.
* style(source-control): match branch identity text to the 'vs main' base ref
Same font-mono / 10.5px / foreground-90 / underline treatment so the current
branch name reads visually consistent with the base ref in the context row.
* style(source-control): drop branch identity underline
Keeps the 'vs main' font/size/color match but no underline — the label isn't
clickable, so the underline read as a false affordance.
* test(source-control): harden identity-row detached + no-identity contracts
Add a stable data-testid to the identity row so the no-identity case proves no
row renders, and assert the detached badge's accessible label + focusability.
Addresses CodeRabbit review on #10215.
Improves clarity and consistency throughout mobile pairing, settings,
and permission descriptions. Makes the distinction from Tailscale
more explicit where relevant. Updates all translated locales.
* feat(diagnostics): name what grew in renderer OOM crash reports
Renderer OOMs are the dominant crash class (heap pinned at the ~3.5GB V8
ceiling in crash-channel reports) but renderer_memory breadcrumbs carry only
heap totals, so reports say "it grew" without saying what.
Add a one-shot renderer_memory_highwater breadcrumb at 60%/80% of the heap
limit carrying leak-diagnosis counts: DOM node census, terminal element
count, and per-subsystem counts from a new contributor registry. The store
registers the first contributor, reporting its 20 largest top-level
collections. Counts only, capped per contributor; zero work on the 60s
sample path while below threshold.
* fix(diagnostics): retain renderer heap profiles
* test(agent-status): remove subagent row order race
* fix(diagnostics): bound aggregate heap profile work
* fix(diagnostics): bound heap profile contributor calls
* fix(types): avoid overloaded stat return inference
* fix(diagnostics): fully bound heap profile registry
* chore(skills): refresh release snapshot manifest
* fix(diagnostics): retain browser counts in heap profiles
* fix(checks): stop the transient "PR already exists" card before the PR loads
The Checks panel briefly showed the terminal "Pull request already exists"
card (Open Review only) on a cold mount, and only rendered the real PR panel
after a manual refresh.
Root cause (regression from #9428): the eligibility probe resolves
`existing_review` from cache faster than the renderable PR hydrates, and the
new exhaustive selector ranks `existing_review` as a terminal safety blocker
above every loading state. So during the fetch window the panel settled on the
terminal card instead of a self-updating loading state. The auto-fetch also
used a lazy SWR priority, so the PR landed slowly unless the user forced a
foreground refresh.
Fix:
- Selector: while a PR fetch is queued/in-flight and we have positive-but-not-
yet-renderable review evidence (existing_review or positive_unresolved), show
the self-updating "Checking status" state instead of the terminal card. It
flips to the review once details land. A concurrent branch blocker still owns
the copy.
- Fetch: when a review is known to exist but no renderable PR is cached, escalate
the auto-fetch to a foreground `active`/80 request (matching manual refresh) so
it resolves promptly.
Reuses existing i18n keys; adds unit coverage for both parts.
* fix(checks): bound unresolved review foreground refreshes
* fix(types): exclude absent stat overloads
* fix(checks): preserve refresh precedence and budget
* fix(checks): avoid redundant foreground PR refreshes
Fold unresolved-review promotion into the existing refresh effect so cached evidence produces one dispatch instead of an SWR plus active pair. Keep non-GitHub evidence on the budgeted path to avoid forced GitHub work for GitLab and other providers.
* fix(checks): scope foreground refreshes by provider
Inline setup/onboarding terminals (skill installs, feature tips) and the floating terminal use host-agnostic synthetic worktree ids with no worktree/repo row. Since #9994, connectPanePty gated their transport on resolveWorktreeOperationRouteResult, which fails closed ('missing') for unknown ids — so the Orca CLI skill Update button, every other inline setup terminal, and the floating terminal showed "Workspace identity is ambiguous across hosts" instead of running.
Route them through the shared resolveTerminalWorktreeRoute (which already exempts the floating terminal and folder workspaces), and add the missing ephemeral-setup exemption so setup terminals follow the single active runtime (remote skill installs land there) or run locally when none is focused. Genuinely unknown/stale repo-backed worktrees still fail closed, preserving #9994.
Adds terminal-worktree-route unit tests and connectPanePty regression tests (proven to fail without the fix).
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): restore a dark-background contrast floor
Fully disabling xterm minimumContrastRatio on dark backgrounds (#9599)
left near-background body text unreadable — Antigravity paints #262b30
on #1e242a (~1.1:1). Keep light backgrounds at WCAG-AA 4.5 and use a
milder dark floor (3) so dark-on-dark body text is lifted without the
full light-bg correction strength.
Fixes#10104
* fix(terminal): extend dark-bg contrast floor to preview + mobile terminals
The dark-background minimumContrastRatio floor (#10104) is applied per
`new Terminal()` construction site. Beyond the live pane, agent output also
renders in the dashboard popout preview and the mobile WebView, which were
still at the floor-1 default, so Antigravity output stayed unreadable there.
- AgentTerminalPreview: gate via resolveTerminalMinimumContrastRatio
- mobile WebView: port the gate as resolveTerminalContrastFloor (Chrome-74 JS)
- tests: builtin-catalog guard + mobile vm-harness coverage
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix(quick-open): guide rg install after fallback budget errors
* fix(quick-open): show local host wording for local install guidance
The install-rg guidance component hardcoded 'on the remote', so the new
local fallback path told local users to install ripgrep 'on the remote'
— wrong for the exact case #9627 targets. Parse the location out of the
message and render the matching wording; add the local locale string and
a render test that guards against the 'on the remote' regression. Also
harden the reason capture against a stray ')' in the error text.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
- Tighten combobox row padding (py-2 -> py-1.5) in the Project and Run-on
lists so items aren't so vertically loose.
- Use a Monitor glyph (not a server) for the local machine in Run-on.
- Pin 'Add host' as a compact single-row footer with one clean divider,
matching the Project picker's 'Add a new project' footer (removes the
taller two-line in-list row and its extra separator).
- Give the smart name field a solid bg-background so it matches the other
inputs on light mode instead of reading translucent over the dialog.
Co-authored-by: Orca <help@stably.ai>
Credits remote terminal output only after parse or intentional discard, with bounded adaptive windows, fair draining, recovery cleanup, and RTT/parser benchmarks.
* fix(terminal): resume output after recovery resync
* fix(terminal): harden recovery resync boundaries
* fix(terminal): prevent double rendering on frame-drop resync
After an untagged resync snapshot, the client resets to the snapshot's output
high-water. Buffered output generated while serializing is already included in
the snapshot, so replaying the entire buffer afterward renders those bytes
twice. Trim to output after the snapshot seq.
Tagged snapshots feed side consumers and need all buffered output unchanged.
Add exponential backoff for truncated recovery retries to avoid stampeding
flooded servers.
* feat(dashboard-popout): size the terminal dialog's PTY to the dialog grid
The popout agent-terminal dialog rendered the pane's serialized frame at its
original cols/rows and CSS-scaled it down to fit. The dialog now claims the
PTY grid for its own box through the remote-desktop viewer registry: the PTY
reflows to the dialog's dimensions (crisp, unscaled), the main-window pane
parks at the held grid like any remote viewer hold, and closing the dialog
releases the claim so the pane reclaims its geometry. A phone-driven PTY
keeps the floor; the dialog then falls back to the scale-to-fit rendering.
Any grid change under a live preview stream (fit landing, host reclaim,
phone takeover) now pushes a resync so the dialog repaints from a snapshot
at the new grid — this also fixes garbled dialogs when the pane resized
while a dialog was open.
* fix(dashboard-popout): harden terminal grid claims
* perf(dashboard-popout): bound terminal preview resize work
* fix(agents): scope Settings agent list and quick-launch menu to the remote-server host
With a paired Remote Server as the Active Server, Settings → Agents and the
tab-bar + quick-launch items always ran agent detection on the local client's
PATH, so a Windows client showed its own agents while worktree-create
correctly listed the server's.
- Extract TabBar's ssh/runtime/local owner resolution into a shared
useAgentDetectionTargetForWorktree hook and use it in QuickLaunch, which
previously resolved only SSH connections and fell back to local for
paired-runtime worktrees.
- Scope AgentsPane detection (and its Refresh button) to the Active Server,
with an "on <server>" badge showing which host the list came from.
Enable/disable/default toggles remain client-side settings.
- Split runtime detection into store/slices/runtime-detected-agents.ts and add
refreshRuntimeDetectedAgents: preflight.refreshAgents over the relay
(login-shell PATH re-read), falling back to preflight.detectAgents on
servers that predate the refresh RPC, keeping the last known list when the
runtime is unreachable.
* fix(agents): avoid redundant runtime refresh fallback
* fix(agents): dedupe SSH agent refreshes
* fix(agents): preserve remote host boundaries
* fix(agents): prevent remote detection refresh races
* fix(agents): harden remote detection failures
* fix(agents): keep unresolved detection off local host
* fix(agents): keep cold remote ownership unresolved
* perf(startup): overlap catalog/session disk reads with the worktree scan
App.tsx hydrated repos, project-groups, folder-workspaces, worktrees, then
session-get strictly serially, even though only worktrees depends on repos.
Once repos is loaded, run fetch-worktrees, session-get, and the (internally
ordered) local project-group/folder-workspace catalog chain concurrently so
the two disk reads hide behind the O(repos) worktree git scan (the startup
long pole). list-runtime-session-hosts now overlaps the repo scan too.
Ordering preserved: repos before worktrees/session; project-groups before
folder-workspaces; hydrate-session-stores still runs only after all settle.
fetchAllWorktrees({hydrationPurge:'defer'}) returns before its folderWorkspaces
read, so it needs no catalog ordering at startup.
Co-authored-by: Orca <help@stably.ai>
* perf(startup): don't serialize the worktree scan behind host discovery
Address CodeRabbit review: awaiting runtimeHostsPromise before the Promise.all
made fetch-worktrees + the catalog chain wait on list-runtime-session-hosts, even
though only session-get needs the host ids. Chain session-get off the host promise
inside the Promise.all instead, so the worktree scan and catalog reads start
immediately and the host-list IPC only gates session-get.
Co-authored-by: Orca <help@stably.ai>
* test(startup): assert concurrent hydration graph in source-order guard
The #18 startup change runs worktrees/session-get/catalog concurrently in a
single Promise.all, so the guard's old serial folders<worktrees assertion (and
its session-get slice terminator) no longer describe the code. Assert the real
invariants: UI hydrates before any local read, the catalog chain stays ordered,
worktrees+session start after repos, and all three are joined in one Promise.all.
Co-authored-by: Orca <help@stably.ai>
* fix(startup): join concurrent hydration with allSettled so recovery can't race in-flight tasks
The concurrent worktrees/session/catalog join used fail-fast Promise.all, so a fast
rejection from one branch dropped into the catch/recovery path (which reconnects
terminals and flips readiness) while a sibling hydration task was still in flight and
mutating catalog/worktree state — a race the old serial flow could not hit. Use
Promise.allSettled and surface the first rejection only after all three settle, so
recovery still triggers but nothing is left writing to the store. Guard test updated.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
Adds a keyboard command that opens the "Send notes to an agent" picker for
the active worktree's AI diff-review notes, enabling a fully keyboard-driven
review flow. Unbound by default; users assign it in Settings → Keyboard
Shortcuts.
- New `sourceControl.sendReviewNotes` command (scope global, unbound). Set
`conflictGroup: 'editor'` so Settings warns on collisions with editor chords
(e.g. Add Review Note), not just global ones.
- Dispatched from App.tsx's existing global capture handler so it respects the
terminal-shortcut policy, the shortcut-recorder guard, and defaultPrevented.
- Store thunk `openDiffNotesSendMenuForActiveWorktree` reveals Source Control
and requests the notes send menu open; no-op when there are no unsent notes.
- Menu opens via a nonce-based store request consumed on mount, TTL-bounded so
a request the menu never consumed can't reopen it on a later remount.
Co-authored-by: Orca <help@stably.ai>
The type-aware switch-exhaustiveness lint rule requires explicit cases
for 'current', 'duplicate', and undefined; they fall through to the
existing generic skipped message, so behavior is unchanged.
Co-authored-by: Yuris Auzins <zuz666@users.noreply.github.com>
Grok transcripts carry no timestamps. Previous logic excluded these rows
from matching, leaving pending sends and launch prompts unmatched and
causing the seeded bubble to appear rank-pinned at the list tail — which
reads as conversation reordering.
Now pending sends, launch prompts, and their pruning rules treat null
timestamps as matching-eligible, allowing echo suppression and proper
cleanup of delivered messages.
Update desktop experimental settings, mobile settings/onboarding, i18n
(en/zh/ja/ko/es), and user-visible error strings. Keep internal APIs and
identifiers as nativeChat.
Coalesce identical and sub-pixel fallback overlay measurements so ResizeObserver and xterm fit cannot sustain a render feedback loop, while preserving precise committed geometry.
Adds regression coverage for stable measurements, sub-pixel jitter across integer boundaries, and genuine resizes.
Allow the existing "Open in" entries to launch a configured VS Code
launcher against an SSH-backed worktree via Remote-SSH:
code --remote ssh-remote+<authority> <remote-path>
- Split the blanket SSH/runtime block into a capability model: file
managers and non-VS Code launchers stay local-only (disabled with
"Local only" metadata); a recognized VS Code command is enabled and
forwarded with connectionId over a typed object IPC.
- Main process stays authoritative: rejects active/owned runtimes,
resolves the SshTarget from the persisted Store, derives the authority
(config alias, or username@host on port 22, or ssh-alias-required on a
non-default port), validates POSIX/Windows absolute remote paths without
local stat/normalize, and rejects non-VS Code and compound commands
before spawn.
- Authority and remote path are passed as separate argv; getSpawnArgsForWindows
remains the cmd/bat shim boundary and fails closed on metacharacters.
- Same capability rules across the worktree menu, Explorer overflow, and
the source-control entry context menu.
Refs STA-2386
Closes#9999
* perf(web): pause runtime heartbeat while hidden
Co-authored-by: Orca <help@stably.ai>
* fix(web): preserve inbound-liveness baseline across a hidden heartbeat re-arm
The visible re-arm rebaselined lastInboundFrameAt=now, so a socket that went
silent while the window was hidden looked freshly-heard-from and its death was
masked for another full idle window (~25s). Move the fresh-connect baseline into
startHeartbeat (the real 'we just connected' moment) and have the visible re-arm
only reset the tick clock + clear an in-flight probe, preserving lastInboundFrameAt
so the next visible tick probes a stale connection promptly and closes if unanswered.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* feat(agent-status): show question glyph for needs-you state everywhere
Replace the amber attention dot with the dashboard's MessageCircleQuestion
chat glyph for the waiting/permission "needs you" state across all
surfaces: the agent dashboard cards, in-app dashboard rows, the sidebar's
Agent Dashboard quick-indicator counts, the worktree-level status dot, and
the shared agent-row/terminal-tab indicators.
On the dashboard card the header glyph is suppressed when a question
summary pill is present so "needs you" reads once, not twice.
* test(agent-status): assert amber question glyph, not amber dot
The needs-you unification replaced the amber dot with the amber
MessageCircleQuestion glyph, so update the remaining state assertions in
DashboardAgentRow, WorktreeCardStatusSlot, and TerminalTabLeadingIcon to
match (lucide-message-circle-question + text-amber-500).
* test(agent-status): cover needs-you glyph surfaces
* fix(worktrees): refresh local worktrees in the sidebar while a remote runtime is active
When a remote runtime is active, a local `worktrees:changed` event for an
unbound repo was dropped by the renderer guard in useIpcEvents. Worktrees
created outside Orca for that repo (e.g. `orca worktree create` from a CLI or
automation flow) therefore stayed invisible in the sidebar until an app
restart, even though their sessions were already running.
The guard existed because an unbound repo's list fetch routes to the active
runtime (settingsForKnownRepoOwner's unbound fall-through), so refreshing with
local worktree ids could query — and purge against — the remote host.
Instead of dropping the event, pin the refresh to the local host
(forceLocalOwner): fetch the worktree list against the local owner and merge
additively. The merge is host-scoped and the deletion-purge is skipped on this
path, so it only ever adds local-host worktrees and never overwrites the active
runtime's worktree state. A genuinely-removed local worktree is reclaimed by
the next unguarded full refresh.
* test(e2e): regression — CLI-created worktree visible while a remote runtime is active
Drives the real `orca worktree create` path: the CLI RuntimeClient calls
`worktree.create` over the app's socket, registering a managed worktree and
firing the `worktrees:changed` IPC the renderer listens for. Stages a remote
runtime as active by injecting `activeRuntimeEnvironmentId` into the renderer
store, so no real remote host is needed. Fails on the prior behavior (the
worktree never appears while a runtime is active) and passes with this fix.
* fix(worktrees): pin local lineage refresh during runtime activity
Co-authored-by: Orca <help@stably.ai>
* review: trim comments to house style, normalize queue coalescing to booleans
* review: sweep rename-grace expiry before early returns in worktrees:changed handler
* review: document accepted workspace-space gap, drop imprecise 'additive' wording
* fix(worktrees): route duplicate local repo events locally
* fix(worktrees): tag local worktree events at origin, gate purge skip on runtime overlap
* test: pin origin-based forceLocalOwner with a no-runtime local event assertion
---------
Co-authored-by: brennanb2025 <brennankbenson@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
The workspace-options filter row used the Workflow icon while the
Automations nav item and page use CalendarClock. Match them so the
filter clearly maps to automation-created workspaces.
The iOS MJPEG and Android scrcpy device streams are gated only on the pane
being the active tab (isActive, PR #7382). When the emulator tab is frontmost
but the whole Orca window is hidden/minimized/occluded/display-asleep, the
full-fps pipeline keeps running: main-process socket read + JPEG/H.264 decode
+ IPC + renderer decode. Renderer background-throttling (#9395) cannot stop it
because the pipeline is IPC-push driven from main.
Gate showStream additionally on window visibility via a new occlusion-safe
hook that honors the terminal stale-visibility latch (so a display-sleep
occlusion wedge can't freeze the emulator on a black frame) and delays the
visible->hidden park by 500ms so a quick Cmd+Tab round-trip doesn't renegotiate
the device stream.
Co-authored-by: Orca <help@stably.ai>
react-markdown's <Markdown> has no internal memoization: it rebuilds the whole
unified remark->rehype->highlight->katex processor and re-parses the document on
every render. MarkdownPreview re-renders on internal state that does not affect
the rendered output — most visibly, every keystroke in Find (query/match-index
state) — so a large doc re-ran the full parse + syntax-highlight + KaTeX pass per
keypress, making Find laggy.
Hoist the two fully-static plugin arrays to module scope (a fresh array identity
per render would defeat the memo) and render the body through a React.memo'd
MarkdownBody keyed on content + components. The pipeline now re-runs only when the
rendered content or the components map actually changes; Find/review-pulse/copied-
note re-renders skip it. The components map was already memoized, so its identity
is stable across those re-renders.
Behavior unchanged: 106 existing MarkdownPreview tests pass.
Co-authored-by: Orca <help@stably.ai>
* feat(mobile-pairing): surface unpaired-device auth failures instead of silent 4001 loops
Desktop: when a phone repeatedly fails direct-transport E2EE auth with a
token missing from the device registry (pre-v1.4.106 pairing-path bug left
desktops that regenerated their registry rejecting paired phones forever),
throttle to one notification per session and show an actionable toast
pointing at Settings -> Mobile to re-pair.
Mobile: map a bare 4001 close onto the existing auth retry budget (the
encrypted e2ee_error is undecryptable when the desktop keypair changed, so
the close code is the only surviving signal) instead of looping the generic
reconnect forever, and make the auth-failed verdict say 'Pairing invalid -
re-pair with your desktop' instead of a bare 'Auth failed'.
* fix(mobile-pairing): handle stale keys and startup notification races
* fix(mobile-pairing): isolate auth notification failures
* fix(mobile-pairing): keep recovery alert actionable