Commit Graph
11496 Commits
Author SHA1 Message Date
Neil efaaf51136 Update AGENTS.md 2026-07-23 19:24:28 -07:00
0bb755151c fix(workspace-board): sync Linear on context-menu Move to Status (#10176)
* fix(workspace-board): sync Linear on context-menu Move to Status

The board's right-click "Move to Status" only wrote the local
workspaceStatus and silently dropped the Linear sync that drag-and-drop
performs. Thread an onAssignWorkspaceStatus callback from the drawer
through the kanban card chain into WorktreeContextMenu so the menu
funnels through the same local-first + Linear-sync path
(moveWorktreesToStatus) as drag-and-drop. Outside the board (sidebar
list) the menu keeps its local-only behavior.

* test(workspace-board): guard context-menu Move to Status routing

Extract the context-menu status-assign routing into a pure
planWorkspaceStatusAssignment helper (behavior-preserving) and unit-test
it, so the board Linear-sync vs sidebar local-only branch — the exact
path #10175 regressed on — cannot silently flip back unnoticed. Covers
board-sync-forwards-all-ids, local-only-writes-only-changed, and the
same-status no-op case.

Addresses code-review finding: the added drawer tests exercised the
sync wiring via a mocked LaneGrid but never the menu's routing branch.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: ElNelyo <ElNelyo@users.noreply.github.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-23 19:23:46 -07:00
1b01385872 fix(worktree): reconcile symlinked creates by Git identity (#10266)
* fix(worktree): match created worktrees through symlink roots

On immutable Linux, /home is often a symlink to /var/home. git worktree
list reports the realpath while Orca still holds the /home request path,
so creation failed with "Worktree created but not found in listing".

After local worktree add, fall back to realpath when string comparison
misses. Keep WSL listings on string comparison only (host realpath is not
authoritative there).

Closes #10170

* test(worktree): harden symlink reconciliation authority

* fix(worktree): reconcile creation by Git branch identity

* test(worktree): reproduce symlink-root listing with real Git

* test(worktree): cover cross-platform reconciliation

* fix(worktree): keep reconciliation main-only

---------

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-23 19:12:54 -07:00
Brennan Benson e4202093c0 fix(terminal): re-verify cached macOS login-preflight rejections (#9973)
* fix(terminal): re-verify cached macOS login-preflight rejections

A conclusive PAM rejection was cached for the process lifetime, so one false
verdict (the probe runs over pipes, not a PTY) disabled the login(1) TCC
attribution wrapper for a daemon that survives app quits and updates for
weeks — reintroducing the every-invocation AppData prompts #7003 fixed.
Rejections now re-verify after 30 minutes; accepted verdicts still cache
for the process lifetime.

Refs #9756

* fix(daemon): replace hosts with stale login preflight cache

Protocol 26 shipped the process-lifetime PAM rejection cache. Preserve its live sessions as a legacy generation, but route fresh terminals through protocol 27 so updating actually loads the expiring-cache fix.

Refs #9756

* fix(terminal): validate rejected login probes under a PTY
2026-07-23 19:12:13 -07:00
Mark Xian e3adb20917 fix(agents): include OMP terminals in cold session restoration (#8991)
Preserve OMP session identity and exact resume paths across cold restoration, AI Vault, mobile, WSL/SSH, and host-authority routes. Add mixed-version fallback and completed-session recovery coverage.
2026-07-23 19:05:22 -07:00
Neil 9181e2b6c1 perf(renderer): batch dashboard orchestration selection (#10149) 2026-07-23 19:01:25 -07:00
Neil a2b1185672 perf(mobile): trust healthy session tab streams (#10134) 2026-07-23 19:01:04 -07:00
Neil 943f9a077b perf(daemon): cache droppable stream membership (#10121) 2026-07-23 19:00:43 -07:00
Neil c8a251279a perf(pty): make delivery pressure tracking constant time (#10113) 2026-07-23 19:00:25 -07:00
Neil d95eae5ead perf(workspace): cancel local worktree scans (#10107) 2026-07-23 19:00:00 -07:00
NeilandOrca eec9014198 fix(terminal): hide SSH error toast under the reconnect banner (#10246)
* fix(terminal): hide SSH error toast under the reconnect banner

The z-50 TerminalErrorToast was stacking over the non-blocking SSH reconnect
banner with the raw ssh:connect failure. Suppress the toast while the banner
owns recovery and clear matching toast text so it cannot flash after reconnect.

* fix(terminal): strip only SSH-owned lines from aggregated terminal error

onPtyError newline-joins multiple PTY errors into one string, so the prior
startsWith() classification misfired on aggregated errors: an unrelated error
before the SSH failure left the stale ssh:connect text to flash after reconnect,
and an SSH-first error discarded any unrelated error. Classify per line and drop
only reconnect-owned lines, keeping the rest. Closes CodeRabbit's aggregation edge.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-23 18:59:24 -07:00
Neil 645ed9a3be fix(ssh): hold premature transport 'connected' until the relay is ready (#10157)
An SSH connect whose relay deploy fails permanently (e.g. a dev build
missing the platform relay package) drove an unbounded reconnect loop.
doConnect creates the relay session ('idle'), then connectionManager.
connect() drives the raw transport to 'connected' before the relay
establishes. That premature 'connected' was forwarded to the renderer,
which treats it as "host fully up": applySshConnectionStateChange bumps
tab.generation for stranded remote panes -> remounts TerminalPane -> the
pane connect gate re-invokes window.api.ssh.connect -> restarts the whole
cycle forever (and spams connected-gated reads that fail with "Remote
connection dropped").

Hold the premature 'connected' at 'deploying-relay' in onStateChange until
the relay session is 'ready'. doConnect still broadcasts the authoritative
'connected' directly after establish() succeeds. Gate on connectInFlight so
the hold is scoped to a live connect and never wedges a stray transport-blip
'connected' on a session left 'idle' by a relay version mismatch.
2026-07-23 18:59:03 -07:00
Brennan Benson 4e27e8e339 test(main): deflake worktree-watch non-ENOENT readdir test with atomic EACCES injection (#10261) 2026-07-23 18:58:26 -07:00
Jinwoo HongandOrcaWin fd72a89260 fix: bound Relay desktop setup handshakes (#10260)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-23 18:46:38 -07:00
NeilandOrca 52cef48fd7 fix(tab-bar): render tab close shortcut as text, not key caps (#10248)
Co-authored-by: Orca <help@stably.ai>
2026-07-23 18:40:27 -07:00
NeilandOrca aab112933e Revert "fix(memory): bound OOM-prone accumulators (#10179)" (#10255)
Co-authored-by: Orca <help@stably.ai>
2026-07-23 18:35:31 -07:00
Brennan Benson 6eb70d8370 fix(main): stop unhandled rejections from silently killing the app during startup restore (#9970)
* fix(main): log unhandled rejections durably and stop them killing the app (#9441)

Main had no unhandledRejection handler — Node's default killed the process
with a clean exit code and no macOS crash report, which is exactly the
silent startup-restore exit in #9441. Rejections now record a durable crash
breadcrumb (flushed to the trace sink) and the app stays alive; uncaught
exceptions still terminate but record the same breadcrumb first so field
exits stop being undiagnosable.

* fix(main): harden process error guard diagnostics

* fix(main): cap fatal-error records to stop a rejection storm stalling main

One broken resource can reject hundreds of concurrent restore chains, and
each durable record does a synchronous trace flush. Cap records at 20 per
60s window and carry the suppressed count into the next recorded event.

* fix(main): never rate-limit the fatal uncaught-exception breadcrumb

A rejection storm that exhausted the 60s record window would suppress the
one main_uncaught_exception record taken right before the re-throw kills
the process, recreating the undiagnosable death from issue #9441. Scope
the fixed window to main_unhandled_rejection only; the uncaught path
records at most once per install (handler removes itself, EPIPE/EIO never
record), so it cannot storm.

* fix(main): reset fatal-error window on backward clock jumps
2026-07-23 18:31:02 -07:00
Brennan Benson 4569a4fe71 fix(window): defer the repaint size nudge and skip it on macOS 26 (#10253)
The show/restore repaint jiggle mutated the window frame synchronously
inside AppKit's window-state dispatch. On macOS 26 (Tahoe), windows are
scene-backed and a frame update sent re-entrantly from scene-update
handling can self-deadlock the main thread in FrontBoardServices
(kernel-confirmed spindump: performAsyncAndWait on the callout queue the
main thread was draining).

- Defer the setSize nudge to a fresh event-loop turn so it never runs
  inside the show/restore dispatch.
- On Darwin 25+ (macOS 26) skip the nudge entirely and rely on
  webContents.invalidate(); the nudge works around a pre-Tahoe
  black-surface compositor bug.
2026-07-23 18:28:52 -07:00
Brennan Benson 14c99f56c1 fix(win-e2e): survive the agent-CLI feature-wall modal in the crash-survival harness (#9992)
* fix(win-e2e): survive the agent-CLI feature-wall modal in harness driving

The modal renders off an async capability check and can be up at (or appear
right after) the harness's first interaction, swallowing every click until
closed — both crash-survival attempts on #9446 timed out exactly there, while
the app itself was healthy. Add its 'Close' button to the overlay dismiss
list, dismiss overlays before ensureTerminal's first probe, and retry the
first New-workspace click through a dismissal pass instead of hanging the
full 60s.

* fix(win-e2e): scope modal dismissal and bound retries

* fix(win-e2e): keep overlay retries inside composer

* fix(win-e2e): seed blank terminal selection

* fix(win-e2e): avoid input during terminal restore

* fix(win-e2e): bound whole create path by caller timeout

createWorkspaceFromSeededRepo gave the composer wait and create-button retry
fresh fixed 15s windows, so the create path could overrun the caller's
timeoutMs. Thread one shared deadline through every step and cover a fast
first click followed by a blocked submission.
2026-07-23 18:04:51 -07:00
Brennan Benson 00cda62ca9 fix(worktree): stop terminal removal fence error flashing on delete (#10240)
* fix(worktree): stop terminal removal fence error flashing on delete

Deleting a worktree kills its PTYs for the filesystem teardown, then runs
git worktree remove (~1s). During that window a doomed pane races a fresh
respawn that main correctly fences with TerminalRemovalInProgressError, but
the renderer surfaced that internal fence verbatim as a pane error banner
until the worktree unmounted.

- startFreshSpawn: skip the respawn when the pane's own worktree is being
  deleted (isDeleting) — no shell should spawn into a directory being removed
  and the pane is about to unmount.
- reportError: swallow the removal fence at the single pane-error sink so it
  never reaches the banner, covering the parent-removal-fences-child case the
  own-worktree skip cannot see.
- Share the fence messages between main (thrown) and renderer (recognized) via
  worktree-removal-fence-error.ts so the thrown text and predicate can't drift.

* test(worktree): assert onError callback captured before invoking

Optional invocation let the fence-suppression test false-pass if the
transport onError wiring broke; require the callback so a broken wire
fails loudly. Addresses CodeRabbit review on #10240.
2026-07-23 18:04:15 -07:00
Brennan Benson bded1fb2c0 fix(app): bound the wake/quit paths implicated in the phone-session-ended freeze (#9447) (#9853)
* fix(app): bound the wake/quit paths implicated in the phone-session-ended freeze (#9447)

- relay-transport: waitForClose now times out (5s) so a half-open post-sleep
  socket can't wedge runtimeRpc.stop()
- will-quit: race teardown against a 20s deadline so app.quit() always runs
  (Force Quit was the only escape when any teardown member never settled)
- terminal-fit-restore: local restoreTerminalFit invoke gets the same 15s
  bound as the remote path so the held-fit modal buttons can't pin disabled

* fix(app): close wake recovery timeout gaps

* fix(relay): drop late frames after forced teardown

* fix(relay): fence detached socket callbacks

* fix(app): close timeout resource gaps

* fix(relay): detach retired mobile transports

* fix(types): exclude absent stat overloads

* fix(runtime): expire wedged terminal restore dedupe

* fix(runtime): keep restore retries on one reclaim

* chore(skills): refresh bundled skill manifests

* fix(window): fence quit acknowledgements by request

* fix(relay): bound revoked device socket cleanup
2026-07-23 18:03:43 -07:00
87c59dd27d fix(claude-accounts): quote resolved claude path for Windows shell spawn (#10237)
* fix(claude-accounts): quote resolved claude path for Windows shell spawn

runClaudeCommand spawns the resolved claude command with shell:true on
Windows, but spawn concatenates the command into the cmd.exe line without
quoting. When the CLI resolves to a path containing spaces (e.g.
C:\Users\First Last\AppData\Roaming\npm\claude.cmd), cmd.exe splits at the
first space and account add fails with:

  'C:\Users\First' is not recognized as an internal or external command

Quote the command the same way claude-pty.ts and quoteWindowsCmdArg
already do for other Windows spawns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(claude-accounts): own Windows cmd invocation

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-23 17:44:43 -07:00
github-actions[bot] 43f626574b Update README downloads badge 2026-07-24 00:34:31 +00:00
Neil f26ea6a4a5 fix(terminal): keep SSH reconnect banner above the xterm canvas (#10242)
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.
2026-07-23 17:07:27 -07:00
Brennan Benson 34e0233cf5 feat(agent-rows): label sidebar and dashboard rows with conversation names (#9989)
* 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
2026-07-23 17:07:25 -07:00
Jinjing a9d21b4d38 docs: update Android APK to 0.0.32 (#10245) 2026-07-23 17:04:01 -07:00
Brennan Benson b97e8c4e27 fix(agent-status): resume finished agents after macOS logout instead of a bare shell (#9454) (#10238)
* 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)
2026-07-23 17:01:53 -07:00
a34eb872c1 fix(editor): preserve PDF zoom when file content reloads (#10168)
* 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>
2026-07-23 16:57:23 -07:00
Brennan Benson fbd5c61f4d perf(terminal): bound and de-synchronize cold-restore replay on the main process (#9990)
* fix(terminal): bound cold restore replay

* fix(terminal): bound cold restore replay slices

* perf(terminal): avoid rescanning cold restore payloads
2026-07-23 16:56:01 -07:00
Brennan Benson ee87bb38dd fix(session): preserve tab/editor/browser chrome when a repo's startup scan degrades (#9991)
* fix(session): preserve chrome after degraded repo scan

* fix(session): retain terminal-free degraded workspaces
2026-07-23 16:30:49 -07:00
CroseryandBrennan Benson 90255ab112 [Bug]: Defer full worktree scan until after session restore (#9446)
* 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>
2026-07-23 16:29:59 -07:00
Brennan Benson ef985ed800 fix(codex): re-land TUI settings promotion with anchored baseline (#10213)
* Reapply "Preserve Codex [tui] settings across managed CODEX_HOME remirrors (#9475)" (#10085)

This reverts commit 09756dfaff.

* fix(codex): anchor settings baseline migration
2026-07-23 16:08:46 -07:00
gatsby74andBrennan Benson d4387b17d9 fix(status-bar): Resource Manager closed badge terminal count + RAM seed (#9387)
* 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>
2026-07-23 16:07:59 -07:00
Brennan Benson 06f6e3bed2 fix(mobile): make image send retries safe (#10228) 2026-07-23 16:04:05 -07:00
kazu-42 4a09ede8b1 fix(codex): classify rate-limit windows by duration (#10136)
Co-authored-by: kazu-42 <91968271+kazu-42@users.noreply.github.com>
2026-07-23 15:58:01 -07:00
Brennan Benson babf1ff9eb fix(codex-accounts): stop account mutations blocking on the quota refresh (#10225)
Switching, adding, re-authing, or removing a Codex account awaited
refreshForCodexAccountChange before resolving the IPC call. Since the
per-account CODEX_HOME rollout (#9501) that probe runs against a cold
home (10s RPC + 15s PTY fallback, 25s WSL) and can queue behind an
in-flight global usage fetch, so the switcher sat unresponsive for tens
of seconds and a fresh login looked stuck on the loading screen.

Worse, in addAccount the awaited refresh sat inside the login cleanup
try/catch: a refresh rejection after the account was durably committed
deleted the just-created managed home, leaving a registered account
with no home ("account never connects").

Run the refresh as best-effort background work instead. Its synchronous
prefix still flips usage to "fetching" before the first await, so the
switcher updates instantly and usage fills in via the normal
rate-limit state pushes; a probe failure is logged and can never
trigger managed-home cleanup.

Fixes #10141
2026-07-23 15:54:02 -07:00
Brennan Benson 94d3db4a24 feat(source-control): show current branch without evicting Create PR (#10215)
* 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.
2026-07-23 14:35:58 -07:00
OrcaWin 0da5380a49 fix: recover Relay broker startup after transient failures (#10147) 2026-07-23 14:31:15 -07:00
OrcaWinandOrcaWin 801ff57e83 fix(mobile): unblock iOS releases (#10224)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-23 14:23:44 -07:00
NeilandJinjing 9500ca7a65 fix(mobile): show attached images in native (rich) chat (#10135)
* fix(mobile): show attached images in native (rich) chat

Attaching an image in the mobile native chat did nothing visible — it reused
the terminal attach flow, which pastes a bracketed host path into the hidden
terminal, so there was no composer preview and nothing in the transcript.

Give native chat the desktop model instead:
- pick + upload shows a removable thumbnail chip in the composer (no early paste)
- on submit, images ride along: Ctrl+U clear -> bracketed paste(s) -> settle ->
  text + Enter (idempotent on retry)
- the optimistic echo carries the local preview URIs and the message renderer
  draws image-ref blocks as real thumbnails when the URI is loadable, so the
  sent photo appears in the conversation immediately
- image-only echoes reconcile by ordinal against user turns after their tail
  (ignores agent replies / paginated history / the 'unknown' ack-loss path)

Terminal chat attach is unchanged (both flows consolidated behind
useMobileSessionImageAttachments). Adds unit coverage for pick+upload, the
ride-along byte order, chip render/remove, and echo reconciliation.

* test(mobile): interactive native-chat image proof (real hooks, click-driven)

Replace the hand-fed component render with an interactive harness that mounts
the real MobileNativeChatComposer/Message + useMobileNativeChatImageAttachments +
drafts under react-native-web and drives the actual flow via clicks. Only the two
OS boundaries are faked: the photo picker and the paired-host RPC socket.

Screenshots (mobile/docs/native-chat-image-attachment/) are produced by real
clicks, not props:
- attach -> real upload pipeline -> chip appears, nothing pasted yet
- send -> real ride-along emits Ctrl+U clear, bracketed image paste, text+Enter
  (shown in the live byte trace) and the sent bubble renders the photo thumbnail

* fix(mobile): scope native-chat image attachments by active tab

Images are now scoped to the tab that initiated the pick, so switching tabs
during upload cannot ride an image into another terminal. Chips stay with
their original tab, and only the active scope's images send with text.
Improved error handling with user-facing toast messages for disconnection
and send failures.

* test(mobile): add image attachment tab-scoping and error tests

Add comprehensive test coverage for tab-scoped attachment behavior,
error handling when transport fails or lease is gated, and edge cases
like attaching images during an in-flight send. Extract baseArgs and
update helpers to reduce boilerplate across test cases.

* fix(mobile): show attached images in native rich chat

Images attached in the mobile native (rich) chat now display as:
- Removable composer chips while composing
- Thumbnails in the sent user bubble after sending (desktop parity)

Implements proper image echo reconciliation by distinguishing
image-source marker turns from text echoes, so an image send isn't
cleared by an unrelated text echo. Adds scope isolation to prevent
chips and drafts from leaking between tabs, and detects tab switches
during the image-paste settle window to abort the send.

Fixes Android tap-target positioning for the image removal badge and
clears stale terminal input after failed pastes to avoid gluing
fragments onto the next message.

* rm stubs

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-07-23 13:25:50 -07:00
OrcaWin 84968dfd9e Fix duplicate paired hosts in worktree run target picker (#10218) 2026-07-23 13:01:28 -07:00
Jinjing 5dc6799c48 Rename 'local network' to 'LAN' across the UI (#10216)
Improves clarity and consistency throughout mobile pairing, settings,
and permission descriptions. Makes the distinction from Tailscale
more explicit where relevant. Updates all translated locales.
2026-07-23 12:48:26 -07:00
Brennan Benson eb2508e7f3 feat(diagnostics): name what grew in renderer OOM crash reports (#9984)
* 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
2026-07-23 12:22:33 -07:00
Brennan Benson 7ba946e39e fix(checks): stop the transient "PR already exists" card before the PR loads (#10008)
* 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
2026-07-23 12:11:36 -07:00
Neil 49d0fdadfa fix(terminal): stop the orphan sweep from deleting reconnecting terminals (#9911) (#10155) 2026-07-23 12:10:03 -07:00
Jinjing 46683f0f55 fix(mobile): keep name input continuous during source picker transition (#10145)
* fix(mobile): keep name input continuous during source picker transition

Refactor create-workspace flow to maintain input continuity: single TextInput
morphs from form slot to docked position above the keyboard while results
reflow above it. Prevents field unmount/remount and keeps user focus on the
input as it transitions. Add keyboard inset resolution utilities and fill-mode
bottom drawer support for stable frame heights during result reflowing.

* fix(mobile): keep name input continuous during source picker transition

Extract drawer navigation into a custom hook and add `interactive` prop to
BottomDrawer to pin the form sheet under the source picker. The form stays
visible and laid out but non-interactive during source selection, revealing
its original height when the picker dismisses. Fix bottom-drawer height
calculation to never exceed the space above the keyboard.

* fix(mobile): reset drawer state when create-workspace modal closes

Prevent a queued transition timer from landing after the modal closes and
leaving stale drawer/pin state for the next open.

* fix(mobile): disable source field focus during drawer transitions

Prevent the workspace-name field from reopening the source picker when
the drawer closes. The drawer's dismiss restores focus to the field,
which re-fires onFocus and reopens the drawer. Gate the field's
focusability with an interactive prop so it only accepts focus when
the form sheet is active.
2026-07-23 12:09:30 -07:00
github-actions[bot] e55e15d44b Update README downloads badge 2026-07-23 18:43:10 +00:00
Neil 8f40ddf328 fix(memory): bound OOM-prone accumulators (#10179) 2026-07-23 06:22:56 -07:00
github-actions[bot] 434f433287 Update README downloads badge 2026-07-23 12:48:59 +00:00
NeilandOrca 8a23618310 fix(terminal): route host-agnostic setup and floating terminals instead of failing them closed (#10151)
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>
2026-07-23 02:33:27 -07:00