* test(e2e): start the board marquee off the board's padding cliff
`selects the full lane across a single large marquee scroll jump` failed the
changed-e2e-specs job with `Received: 0` — no card ever previewed. The CI
trace shows the drag started at (284, 195) and the failure screenshot shows a
native text selection with no marquee overlay, so the board never accepted
the pointerdown.
An element scan across that row shows why: the marquee may only start on
empty board space, and the usable strip is only x 280-298 — the board's own
left edge on one side, the first lane's cards on the other. `selectionBox.x +
4` aims 4px inside that strip, so a layout that rounds a few pixels
differently lands outside the board entirely, where the pointerdown never
reaches the handler and the browser text-selects instead.
Aim at the middle of the measured strip, assert the start point really is
empty board space before pressing, and assert the selection rect appears so a
rejected gesture fails immediately instead of surfacing 15s later as "0 cards
previewed".
Also carried over from the first attempt at this deflake: the lane scroll now
jumps until the virtualizer stops moving the bottom, because a measured card
is much taller than the row estimate and a fixed pass budget commits the
marquee short of the last cards; and the final assertion checks the badge's
text so a short selection reports its count.
* test(e2e): pick the marquee start point from live geometry, not a precomputed one
Round 2 fired the new guard: `marquee start point (290, 195) must be empty
board space` with `onSurface: false` AND `onIgnoredTarget: false` — so
elementFromPoint returned something outside the board entirely, even though
(290, 195) sits inside the measured 280..299 strip. Aiming at the middle of
the strip is still aiming at a point computed before the probe runs, and the
sheet, sidebar and lane fill keep resizing that strip afterwards.
Stop precomputing the point. Read the geometry and scan for an empty point in
the same DOM turn, walking a grid across the strip between the board's left
edge and the first card, over the lane's top rows only — the marquee anchors
its range in content space, so a start below the first card would drop it from
the 102. Take the first point the board itself reports as empty, and require
two consecutive probes to agree so a frame mid-relayout cannot win.
Keep the precondition guard, now reporting the live geometry and the elements
that blocked each rejected point. If no empty point exists at all, skip with
that reason instead of failing on a layout the test cannot drive.
* test(e2e): start marquee from an explicit empty lane
* test(e2e): find visible board space for marquee start
* test(e2e): quarantine unstable marquee hit test
* feat(ssh): add SSH config host picker for add-host form
Users can now click 'Fill from ~/.ssh/config…' to browse available SSH
config hosts in a picker, select one, and have the form automatically
prefill with resolved connection details (hostname, port, username, auth).
Previously, an 'import' button provided bulk sync on this form—confusing
and unhelpful when everything was already synced. That action is now
available as a secondary 'Add all' option in the picker.
* fix(ssh): import filter preservation and label fallback
- Reuse search loader on import completion to preserve active filter inside generation guard
- Fall back to hostname when manual host has no label, not empty string
- Make alias duplicate detection case-insensitive to match config picker behavior
- Validate host availability when restoring project group selection
- Add aria-selected attribute to picker options for accessibility
* fix(ssh): harden config picker import, alias folding, and host targeting
Review findings on the ~/.ssh/config picker + bulk add:
- Guard config-host resolution with a generation counter so a late resolve
cannot overwrite a later pick or a form the user backed out of; freeze the
other rows while a pick resolves.
- Stop "Add all N" from re-adopting deleted hosts — it now imports without
reAdopt, matching the new-host count it advertises. Settings → Import keeps
the explicit re-adopt path.
- Fold SSH aliases through a shared normalizeSshConfigAlias for import
ownership, delete tombstones, reclaim, picker search, and the save-time
duplicate check, which now occupies configHost *and* label like the picker.
- Persist GSSAPIAuthentication only when a parsed Host entry asks for it, not
when `ssh -G` merely echoes the /etc/ssh system default.
- Fail closed with unavailable/setup-not-found when an explicit
projectHostSetupId names a non-actionable host instead of silently creating
the workspace on a sibling host.
- Cache the parsed config for the picker session (refresh on open/retry) so
filter keystrokes no longer reparse and Include-expand the file, keep the
filter usable during loads, add a Retry on load errors, explain an empty
Identity file after a config fill, and drop the always-false aria-selected.
* refactor(ssh): centralize host result limit and extract folder group val
Move SSH_CONFIG_HOST_RESULT_LIMIT to shared types so the renderer's limit message
cannot drift from the host's query limit. Extract findActionableFolderProjectGroup
to avoid repeating the folder-host-availability check across the composer hook.
* fix(ssh): pass -F to ssh -G when HOME differs from passwd home
In E2E tests and sandboxes, isolated HOME can differ from the system
passwd home. OpenSSH resolves the default config via getpwuid (passwd),
while Node's loadUserSshConfig uses os.homedir() (HOME-aware). Pass -F
to explicitly specify the config path when they diverge, so ssh -G and
the picker resolve the same file.
* fix(ssh): verify config host exists before resolving with ssh -G
When a user edits ~/.ssh/config and removes a host, the import picker
should not fall back to ssh -G's echoed response (which treats any alias
as valid). Check the reloaded config file before resolving.
- Force reload config on each resolve to catch user edits post-open
- Reject aliases not in the current config before calling ssh -G
- Add test for deleted alias edge case
- Fix workspace-target fallback to honor explicit host selection
* fix(ssh): let tombstoned aliases be re-picked in the config picker
Allow users to reclaim a deleted SSH host by re-picking it from ~/.ssh/config. Tombstoned aliases now appear in the picker with a "Removed from Orca" badge and remain pickable, but don't count toward "Add all" operations — ensuring passive import never resurrects a deleted alias while still giving the user a recovery path.
* fix(ai-vault): support session scanning in SSH worktrees
Add relay-native aiVault.listSessions scanning that discovers agent
sessions on SSH hosts. Includes fallback to filesystem crawl for
legacy relays, full cancellation support, result validation, and
scan coalescing to reduce redundant work.
* fix(ai-vault): scan sessions in SSH worktrees with coordinated cancellat
- Extract batching logic to `mapRemoteScanBatches` for reuse and proper cancellation checkpoints
- Move `AiVaultScanCoordinator` from relay to main to handle concurrent same-key requests with individual cancellation signals
- Report scope path truncation consistently across relay and SSH fallback paths
- Gracefully degrade relay handler on unsupported platforms instead of aborting startup
- Refactor issue display to separate blocking errors, scope notices, and skipped transcript counts
* fix(ai-vault): stabilize SSH session scan CI
Swallow async WSL relay stdin EPIPE so the live hook-relay shard no longer
fails after all tests pass. Merge main, resolve scan/relay conflicts, and
align cancellation/host-issue reporting with IPC expectations.
* fix(ai-vault): harden session scan cancellation, relay timeouts, and preemption
Thread the abort signal through every scan and parse path so superseded or
cancelled scans stop promptly instead of parsing every remaining transcript
for a caller that already left. Replace the fragile message-text relay
timeout check with a typed error code so unrelated errors carrying the
phrase "timed out after" no longer suppress the filesystem fallback. Fix
scan coordinator preemption so a forced Refresh in one window no longer
re-enters as a spurious cancellation in another. Add a host-leg cache for
the all-hosts view and cap filesystem concurrency so a single slow remote
home cannot stall the whole merge.
Co-authored-by: Orca <help@stably.ai>
* fix(ai-vault): use stable React keys for scan issue banners
Drop array-index keys so react-doctor/no-array-index-as-key passes.
Uniqueness comes from host, kind, agent, path, and message.
* fix(ai-vault): SSH session scanning with configurable depth limits
Implement depth-aware caching and proper scan boundaries to make SSH session
scanning reliable in worktrees. Users can now select between faster (250
sessions) and comprehensive (unlimited) history scans. The scanner:
- Deduplicates scans across relay, host leg, runtime, and renderer layers
- Reuses larger scans to serve smaller depth requests
- Properly bounds in-scope discovery per-limit
- Fixes timeout enforcement when SSH providers ignore abort signals
* Move sessionLimit ref update to useLayoutEffect
Keep render pure for React Doctor by deferring ref updates to
a layout effect, which still executes before render-dependent
effects that consume the ref.
* fix(adhoc): stamp version prefix from main, not the feature branch
Adhoc builds check out arbitrary refs whose package.json often lags
version bumps (e.g. 1.4.165-rc.0 while main is 1.4.168-rc.1). Hourly
always builds main so it already tracks the product line; adhoc now
resolves the base version from origin/main (or ORCA_ADHOC_BASE_VERSION)
so branch builds share that prefix.
* Revert "fix(adhoc): stamp version prefix from main, not the feature branch"
This reverts commit a26a18eb3fd83f7e7d2db9a6a7c3e02e0f79089a.
* fix(ai-vault): fix scoped backfill and coordinator race conditions
Resolve race where the last waiter leaving could abort an already-settled scan (add `settled` flag). Redesign scoped session backfill to keep searching through newer files until the scope reaches its requested session quota instead of stopping at the candidate limit; out-of-scope files no longer consume the scope budget. Centralize scan limit normalization and fix error classification for cancelled scans using the proper helper instead of checking Error.name. Disambiguate cache keys using JSON and add cancellation check after scope discovery phase.
---------
Co-authored-by: Orca <help@stably.ai>
Bound exclusive host navigation to a generation-aware latest-wins
single-flight so bulk open and switch fan-out stay responsive on large
remote fleets. Add freeze repro harnesses and navigated settlement.
* fix(terminal): expand variables in Windows PATH
* fix(terminal): preserve expanded Windows PATH at spawn
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* Open SSH host add/edit form in modal dialog
Form moves from inline to a viewport-stable modal (STA-3067) so fields stay
accessible with long host lists. Includes sticky header/footer, dirty-state
protection against outside click, and session-aware Advanced state reset on
cancel/reopen.
* fix: add missing SshTargetForm localization keys
Sync en.json catalog for modal title/description strings so
verify:localization-catalog passes in static analysis.
* fix: translate SshTargetForm modal strings in es/ja/ko/zh
Add non-English catalog entries for the new modal title and
description keys so localized UIs match English.
* Prevent SSH form double-submit and fix dismissal detection
Adds a saving state to prevent concurrent saves when a user double-clicks
the submit button. Fixes outside-click dismissal by correctly tracking form
state across re-renders using refs. Extracts session termination logic to
a reusable module.
* fix: stop mutating formRef during render in SshTargetForm
React Doctor fails the static-analysis gate when refs are written during
render. Sync form into formRef in an effect so render stays pure.
* feat(voice): allow selecting a microphone for dictation
Persist a preferred audioinput device in Voice settings and pass it into
getUserMedia, falling back to the system default when the device is gone.
* fix(voice): resolve mic preference by label and detect mid-capture loss
Drop Chromium's 'default'/'communications' aliases from the picker — pinning
one behaved exactly like system default and silently defeated the setting.
Resolve a stored preference against the live device list before capturing:
a unique label match heals an id that Chromium re-salted, a known-missing
device skips the doomed getUserMedia attempt that clipped the first words,
and an unreadable list no longer reads as "unplugged".
Surface the input ending mid-dictation instead of feeding silent zeros, add
a permission affordance so the picker is not empty before mic access, and
toast the fallback once per preference rather than once per utterance.
Co-authored-by: Orca <help@stably.ai>
* add e2e tests
* add e2e tests
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
Swap the worktree-name emoji picker from emojibase-data's `github` shortcode preset to `emojibase`, which carries both `flag_kr` and `south_korea` style flag names, and drop the hand-maintained `kr` entry that patched around the gap. Filter skin-tone aliases so they neither crowd the suggestion list nor clobber base-emoji branch names.
Search now matches anywhere in the shortcode, ranked exact > prefix > word-start > substring, so `:korea` surfaces both Koreas.
Emoji-derived branch names now prefer spelled-out aliases: flags use country names (japan, germany, south-korea) and cryptic stubs are skipped (thumbsdown over no, victory over v).
Remove one-off incident docs and committed test-results noise, move
dev/repro/bench tools under tests/tools, and relocate i18next config
into config/ so the GitHub root scrolls to the description faster.
* fix(preflight): route landing banner through the runtime-aware preflight slice
Landing called window.api.preflight.check directly, which always probes the
local client. The preflight slice is the only caller that consults
getActiveRuntimeTarget and forwards to preflight.check on the active runtime
environment, so while connected to a remote runtime the landing banner
reported the client machine's git/gh state instead of the server's.
Delegate to refreshPreflightStatus and derive the issue list from
state.preflightStatus. This also drops Landing's duplicate probe: the slice
dedupes concurrent and forced checks, so the mount/focus/poll paths now share
one in-flight request with the rest of the app.
* fix(preflight): refresh landing status across runtime sessions
* test(preflight): cover paired runtime session races
* test: make landing preflight oracle behavioral
* fix(preflight): scope runtime session invalidation
* test(preflight): cover headed runtime switching
* test(preflight): isolate runtime status toast
---------
Co-authored-by: Marty <marty@localhost>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(remote): unthrottle host renderer while serving a paired client
A paired desktop host left in the background could not open or close
agent sessions for its remote/relay client: the action stalled and
eventually failed with the host-side "Timed out waiting for terminal
surface after creation" (10s) error, while an already-live terminal's
keystrokes stayed fast.
Root cause: creating/closing a session routes through the host
renderer's setTimeout-coalesced graph sync to publish the terminal
surface, but the host window runs with Electron background throttling
(the hidden-window default, reaffirmed on macOS). When the window is
backgrounded/occluded, those renderer timers are throttled to a crawl
and the surface publication misses the 10s deadline. Live keystrokes are
unaffected because PTY I/O flows through the main process, never the
renderer.
Keep the authoritative renderer unthrottled while at least one remote
client is connected and restore the throttled power-saving default once
the last one disconnects. Connect/disconnect are driven from the shared
MobileSocketWiring onReady/onClose, so both direct-WS and cloud-relay
clients are covered; headless serve has no window and is a safe no-op.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor(remote): tidy renderer-throttle comment and test per review
Address automated review nits on #11581:
- Trim the module-level rationale comment to the non-obvious contract,
matching the repo's concise-comment guideline.
- Drop the dead `detachedThrottle` variable from the reapply test; the
detached-target scenario is already covered by the lazy-resolution
test, so the case now asserts only what it exercises.
No behavior change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(remote): scope paired terminal publication throttling
Keep headed paired terminal creation and close renderer-owned so host inventory, input routing, ACK recovery, and cleanup retain the established lifecycle. Hold a reference-counted background-throttle lease only while the renderer publishes a paired operation, and epoch-fence async resolution so renderer reloads reject before any request or PTY spawn. Preserve headless main ownership and prevent paired clients from falling back to a local terminal.
* test(e2e): verify minimized host terminal repaint
* fix(remote): preserve paired terminal inventory through graph gaps
---------
Co-authored-by: fanyunqian.1 <fanyunqian.1@bytedance.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* Fix Create PR preparation with unavailable lookup
* test(source-control): align dirty+unavailable intent expectation
Create PR preparation is allowed when review lookup is unavailable; only final create stays fail-closed. Update the local-blocker snapshot test to match.
* Keep Create PR intent running when hosted review lookup fails
A failed or timed-out hosted-review eligibility lookup no longer aborts
Create PR intent mid-run. Local prep (stage/commit/push) continues, the
branch-ahead refresh is deferred until after eligibility resolves, and the
final create preflight still fails closed to prevent duplicate reviews.
Also gate generated PR title/body on eligibility and thread the provider
through the intent run token so an unavailable lookup falls back to the
inferred remote host.
* fix(source-control): align dirty+unavailable intent expectation
Local preparation (stage/commit changes) is safe without review-lookup authority; remote actions stay blocked. Prevents dirty trees from dead-ending at sync-first when lookup is unavailable.
* fix(source-control): distinguish loading state from unavailable lookup
Require head branch presence in shouldAttemptCreateHostedReviewForIntent to
separate real unavailable-lookup results from loading placeholders, which
share the same outcome/reason pair but lack a branch name.
* test(activity): drive portal readiness latch release with explicit rAF
Wall-clock setTimeout waits for requestAnimationFrame were flaky under
CI load (shard 15/16), leaving status stuck at loading instead of ready.
* Distinguish expected absence from git errors in remote removal
Why: swallowing all errors silently masks genuine git failures.
Check presence explicitly instead, so setup/teardown can still
skip when origin is absent while letting real errors surface.
* Virtualize workspace board lanes and defer card render for instant open
* fix(review): harden kanban virtualization interaction edges
Cancel deferred card mount on close, re-apply marquee preview after remounts,
query drag styles from live DOM, drop re-exports/casts, and cover edge cases.
* Remove virtualizer from effect deps to avoid unnecessary re-runs
* Fix stale closure in kanban area-selection and card-drag handlers
- Refresh area selection measurements on pointer up to avoid stale cache
- Read worktree IDs ref directly in drag handler to close stale closure
* Remove itemIds ref for correct virtualizer layout memoization
The card list kept itemIds in a mutable ref that was manually synced on every render, so the layout registration effect never re-ran when the lane contents changed, leaving stale measurements. Read itemIds directly and declare it as an effect dependency so layout registration stays in sync with the items.
* fix(browser): scope Cmd/Ctrl+F find to the focused split (#11348)
The browser pane's renderer-path Find handler is a window-global
capture-phase keydown listener, but it armed on `isActive` (the active
tab within its own group) rather than on whether its split holds focus.
In a terminal+browser split, the browser was therefore `isActive` even
while the terminal held keyboard focus, so it swallowed Cmd/Ctrl+F and
opened find-in-page in the browser instead of find-in-terminal.
Thread a focused-split signal (`isFocused`) from BrowserPaneOverlayLayer
— derived from `activeGroupIdByWorktree` — down to the Find handler and
gate the listener on it. This mirrors how terminal leaves already gate
global shortcuts via `focusedGroupId` in TabGroupSplitLayout. Floating
browser panels omit the prop and fall back to `isActive`, preserving
their behavior. The IPC path (webview guest focused) is unchanged; it
only fires when the guest genuinely has focus.
Not platform-specific: the chord resolves through `keybindingMatchesAction`
(Mod -> metaKey on macOS, ctrlKey elsewhere), so the same path is fixed on
macOS, Linux, and Windows.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(browser): preserve Find before split focus settles
* fix(browser): handle stale focused split IDs
* fix(browser): route guest Find to source page
* test(browser): wait for split address bar
* test(browser): focus split before Find routing
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* Revert "fix(terminal): avoid flash while restoring parked terminals (#10871)"
This reverts commit 5a6a9e0b28.
Reverted for terminal rendering regressions (flashing, lost content).
Conflict resolution preserves the forwardRef signature from #10433 and
drops the parked-presentation gating #11016 fed with its effective set.
Co-authored-by: Orca <help@stably.ai>
* Revert "fix(terminal): limit pre-paint WebGL resume to macOS (#10794)" and "fix(terminal): stop switch bold flash and Windows lag (#10692)"
This reverts commits 4681edb520 and
8f5a45401f.
#10794 was itself a partial revert of #10692, so both are reverted
together: the Windows retained-WebGL LRU and the macOS pre-paint
(layout-phase) visibility transition that survived it. Terminal
visibility resume returns to passive disposal and recreation on every
platform, and the WebGL context ceiling returns to a flat 128.
Co-authored-by: Orca <help@stably.ai>
* Revert "fix(terminal): release an abandoned synchronized-output frame on reveal (STA-2694) (#10907)"
This reverts commit 97cb32c1cc.
---------
Co-authored-by: Orca <help@stably.ai>