Commit Graph
8454 Commits
Author SHA1 Message Date
github-actions[bot] 9df0488ec0 release: v1.4.182 v1.4.182 2026-08-13 16:52:55 +00:00
Jinjing ace07c9ceb Add golden E2E tests for fresh terminal and shell commands (#14302)
* test(e2e): add golden tests for fresh terminal and shell commands

Adds regression tests for terminal initialization in fresh profiles and shell command execution to the golden test suite, integrated across Linux, macOS, and Windows CI.

* test(e2e): bracket shell command output between markers

The echoed command can wrap or be clipped by the buffer tail. Bracket output between begin and end markers to reliably identify real output, and strip ANSI escape sequences that interfere with parsing.
2026-08-13 09:50:58 -07:00
Jinjing 49d5d3486d fix(ci): skip missing golden scripts on older release tags (#14267)
Cut Release is dispatched from main but checks out the tagged tree.
Cherry-pick tags such as v1.4.182-rc.1 do not define
test:e2e:windows-fresh-startup-golden, so the Windows golden job failed
with ERR_PNPM_NO_SCRIPT. Run tag-optional goldens with --if-present.

(cherry picked from commit dd63d35d09)
2026-08-13 09:49:46 -07:00
Jinjing 8f51f6a666 test(e2e): stabilize triaged release failures (#14242)
* test(e2e): stabilize triaged release failures

* rm file

(cherry picked from commit 1d2b8c7f42)
2026-08-13 09:46:50 -07:00
Jinjing 25e7abf355 Fix fsync of read-only files on POSIX (#14235)
* fix(files): fsync read-only files on POSIX

* test(e2e): add golden E2E tests for POSIX profile index fsync

Validates that profile index files are properly persisted on POSIX systems, including with restrictive umask settings. These are release-blocking golden tests for Linux and macOS.

* test(terminal): wait for fish child ownership before stdin write

Fish 4.8 withdraws DECSET 2031 before spawning the child, so the
shell-contracts harness could send hello into an intermediate prompt
and hang waiting for CHILD-READ. Wait for the child's CHILD-READY
marker and answer split DA1/CPR/OSC queries across chunk boundaries.

* test(e2e): verify profile index persists to disk with restrictive umask

Strengthen the POSIX fsync test to verify the rebuilt index is actually
written to disk and has correct permissions under a restrictive umask,
not just cached in memory.

(cherry picked from commit 7c93aed6dc)
2026-08-13 01:30:30 -07:00
Neil b0f72abe95 fix(windows): restore fresh-profile startup after durable fsync (#14173)
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: DHTheOne <238933622+DHTheOne@users.noreply.github.com>
Co-authored-by: Anton Tupitsyn <70199858+PLUTONYY@users.noreply.github.com>
Co-authored-by: 7loop <48346764+7loop@users.noreply.github.com>
(cherry picked from commit e8044b1b30)
2026-08-13 01:29:27 -07:00
Brennan Benson a0c63c36d2 fix(agent-hooks): stop rewriting typed Prime commands (#13906)
* fix(agent-hooks): preserve typed Prime commands

* test(wsl): enforce Prime bridge exclusion

---------

Co-authored-by: OrcaWin <alpha-eng@stably.ai>
(cherry picked from commit 4b9fb46646)
2026-08-13 01:29:16 -07:00
Jinwoo Hong 45d564e6a8 Fix paired web creation failure handling (STA-4024, STA-4025, STA-4063) (#14100)
(cherry picked from commit de729d6067)
2026-08-13 01:28:56 -07:00
Jinwoo Hong eb6807d0a8 Fix paired remote HTML browser ownership and focus (#13876)
* Fix paired remote HTML browser preview

* Register paired HTML preview reliability gate

* Scope browser worktree selection guard

* Harden paired HTML preview ordering

* Simplify paired HTML preview ownership

* Record final paired HTML preview evidence

* fix(remote): simplify browser preview ownership

* test(remote): refresh HTML preview evidence

* fix(remote): reconcile browser create ordering

* fix(remote): guard browser focus through reconcile

* fix(remote): bound delayed browser focus

* fix(remote): guard browser focus context

* test(remote): record final HTML preview evidence

* fix(remote): keep HTML previews in background

* test(remote): cover browser focus modes

* test(remote): record focused browser contracts

(cherry picked from commit 70ad65fc94)
2026-08-13 01:28:09 -07:00
Jinjing 2fdbe0df76 Fix terminal split source incarnation and rejection cleanup (#14238)
* fix(terminal): fence split source incarnation

* fix(terminal): retire rejected split safely

- Track retired rejected PTYs to prevent synthetic exits from landing after
  split rejection completes.
- Validate splits using persisted incarnation IDs only, allowing restored
  sessions without incarnation maps to work correctly.
- Reduce stop timeout from 10s to 2s to avoid stalling on unreachable hosts.

(cherry picked from commit 00cab82fc0)
2026-08-13 01:27:32 -07:00
Jinwoo Hong 10ab1a718e Fix remote-runtime terminal pane split authority (#13867)
* fix(runtime): preserve remote terminal pane splits

* test(runtime): tighten split authority guards

* fix(runtime): revalidate transient split sources

---------

Co-authored-by: E2E Test <e2e@test.local>
(cherry picked from commit 7a58f72814)
2026-08-13 01:27:31 -07:00
Brennan Benson 1536aa8e9a fix(terminal): repaint a recovered remote pane from the host's retained buffer (#14095)
* fix(terminal): repaint a recovered remote pane from the host's retained buffer

A remote-runtime pane that loses its stream re-subscribes, but the recovery
subscribe only carries new bytes. When the host's push snapshot is empty --
an idle pane, or one whose PTY has exited and is preserved with its buffer --
the transport dropped it and nothing re-armed the restore, so the pane stayed
blank until a visibility flip issued the tagged snapshot request. That is why
switching worktrees once "fixed" it.

Emit onStreamRecovered from the recovery subscribe path only, and mark the
hidden-output restore needed so the pane pulls the buffer the host still holds.
The initial subscribe is untouched: it already carries the host snapshot, and
re-arming there would cost every pane a redundant restore request on open.

* perf(terminal): avoid duplicate recovery snapshot replay

(cherry picked from commit bd12934362)
2026-08-13 01:27:31 -07:00
Brennan Benson e65d371527 fix(terminal): never wipe a painted pane for a snapshot that carries no image (#14099)
* fix(terminal): never wipe a painted pane for a snapshot that carries no image

A hidden-output restore that comes back as a success frame with an empty image
was still applied. The normal-buffer replay opens with \x1b[2J\x1b[3J\x1b[H, so
it cleared screen and scrollback and painted nothing in their place -- turning a
host that had nothing to say into a blank pane.

An empty image is the absence of an answer, not proof the pane is empty, so keep
the frame the user can still see. Retry and banner behavior are unchanged: an
empty image still counts as a successful, non-bannered recovery.

* test(terminal): prove imageless restore preserves painted buffer

(cherry picked from commit a2c9f7e52b)
2026-08-13 01:27:30 -07:00
Jinjing 16b5aa31c2 Fix remote server browser tab fallback behavior (#14194)
* fix(browser): require runtime browser capability

* fix(browser): reuse capability verdict and exclude floating terminals

Cache the runtime browser capability check across multiple remote port opens instead of checking for each port independently. Prevent floating-terminal workspaces from routing to remote browsers.

* fix(browser): pin remote pane link opens to owning runtime

Remote pane link opens must route to the pane's runtime, never the client.
Fails if the workspace has moved to a different host while the pane still
displays a remote page, preventing silent misrouting to a dev server.

* trim comments

(cherry picked from commit 939acf1c2b)
2026-08-13 01:27:30 -07:00
Jinwoo Hong 6dc46ed834 Gate paired web creation actions by provider (#13909)
(cherry picked from commit ebe5125476)
2026-08-13 01:27:21 -07:00
Jinwoo Hong f278939c8d fix(browser): route remote terminal links to owning host (#14117)
(cherry picked from commit e8350f1990)
2026-08-12 19:53:01 -07:00
Jinjing 73ba67e8b6 Allow directly search in google (#13863)
* Allow direct search with configured search engines

Users can now search directly from the tab creation menu using their
configured search engine. Forced search mode (`?` prefix) skips file and tab
matching for guaranteed search. Refactored tab-entry operations into focused
modules for clarity: forced-search parsing, network-safe selection, keyboard
focus, copy strings, empty options, and props types. Search routes through
the same workspace browser tab opening mechanism used for URLs, with safe
title and query presentation that doesn't retain sensitive details.

* Allow direct search with configured search engines

- Permit search and URL navigation while file index loads; require explicit
  selection only when needed, not automatic opening
- Block malformed IPv6 addresses in bracket notation to prevent misclassification
- Support Kagi private-session links via searchUrlOptions
- Improve error handling with accessibility: show error messages in status region,
  disable input during submission, display loading state
- Fall back to local browser tab creation when remote creation fails instead of
  throwing; avoids remote availability blocking local search/navigation
- Add error translations for all supported locales (es, ja, ko, zh)

* Allow direct search with configured search engines

- Extract tab create entry lifecycle to key-driven component remounting, replacing conditional state reset with useEffect cleanup
- Consolidate network tab entry classification and request building into reusable helpers, eliminating duplicate logic
- Simplify owner resolution by inlining logic directly into openWorkspaceBrowserTab
- Replace custom surrogate-pair handling with native String.toWellFormed() for search queries
- Disable explicit URL classification to prioritize search-engine queries over raw URLs

* Allow direct search from quick-open tab bar entry

- Single-token queries keep file matches ranked above search (quick-open intent)
- Multi-word phrases promote search to top, since they cannot be file paths
- Arm network actions once file index fails or text is unambiguous search
- Cache prepared file index to avoid re-processing per keystroke
- Generate specific tab titles (e.g. 'example.com/docs') instead of generic 'Open URL'
- Surface opening workspace when launching browser tabs remotely

* Use readOnly instead of disabled for pending search input

Maintain keyboard focus during submission so arrow/Escape navigation continues to work. Use aria-busy to indicate loading state accessibly. Also fixes button hover styling when disabled and cleans up error message handling in the classifier.

* Treat bare searches as prompts; refine path and IP classification

- Bare search queries (e.g., "?") no longer display as error rows
- Path prefixes with existing matches are no longer blocked mid-keystroke
- Private IPv4 addresses now use http, public addresses use https
- Ambiguous inputs with non-numeric ports fall through to search instead of blocking
- Improve diagnostics by logging failure reasons in openFailure

(cherry picked from commit 55beeebd25)
2026-08-12 19:52:20 -07:00
Jinwoo Hong 08cac4497d fix(browser): disable unsupported FedCM (#14023)
(cherry picked from commit 9a81577e91)
2026-08-12 18:51:33 -07:00
Jinwoo Hong f246338ab4 Use provider-neutral Opus alias in orchestration guide (#14119)
(cherry picked from commit d349f9a972)
2026-08-12 17:13:46 -07:00
Neil 7e5eb85cad fix(terminal): retain alt frames through park reveal (#13882)
* fix(terminal): repaint an alt frame the fit never lands for

#13014 skips a too-wide alt frame on snapshot replay and relies on a post-fit
repaint. For a pane visible but permanently below the 48x24px fit floor,
readProposedTerminalCols is forever undefined, so skipIfTargetUnknown drops the
frame — and the only compensating repaint lives in a safe-fit continuation that
is failed rather than run for such a pane. The frame is lost until the user
widens the pane.

Adds a deferred repaint gated on display:none rather than on target-unknown,
preserving #13014's no-clip guarantee for a pane that later fits narrower.

Does NOT fix artificial-opencode-terminal-load.spec.ts:761. Two open concerns:
the repaint waits out the full 40-frame safe-fit retry budget (~1.3s) against
that spec's own 2s restore budget, and the cold-park reveal path at
pty-connection.ts:8222/8289 still has no deferral net.

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

* fix(terminal): restore the deferred PTY input hop

Inlining forwardPtyInput into the onData handler dropped the
deps.deferPtyInput dispatch, leaving the callback and its type declared
and supplied but never called. Link-click mouse suppression silently
stopped withholding mouse-report frames, so a click in a mouse-tracking
TUI reached the PTY and the link-open fallback never fired.

Covers both branches, which had no test at all.

* fix(terminal): retain alt frames until final fit

* fix(terminal): preserve park intent across effect replay

* test(terminal): satisfy native static analysis

* fix(terminal): consume park intent after layout replay

* fix(terminal): re-read park intent instead of caching it per pane

useTerminalParkMountIntent cached park intent in a ref for the lifetime of
the component. The ref was never load-bearing: React finishes the render
phase before any effect runs, so StrictMode's passive-effect replay already
observes the same render-phase value. What makes the reveal work is reading
at render time rather than at connect time, which a plain call already does.

The ref did go stale. The pane lifecycle effect re-runs on [tabId, cwd] for
the same component instance, so a worktree path or startupCwd change
resupplied mountFollowsTerminalPark: true long after the park reveal, since
the post-replay consume clears the per-run ptyDeps copy and not the ref.
Each spurious run re-armed the pre-paint snapshot prefetch.

Also scope the post-replay consume behind a tested one-shot helper so splits
created after the restored-layout replay keep ordinary reconnect semantics,
and correct two comments that described the removed ref.

---------

Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
(cherry picked from commit ef8eea51af)
2026-08-12 17:13:46 -07:00
Jinjing a49b7fe6e1 P1 STA 3887 design Preview Kitty IME (#13940)
* fix(terminal): carry kitty flags through Preview snapshots and pair rele

Preview was omitting the live kitty mirror from the IME bridge and dropping kitty flags from snapshots, so every commit was evaluated at flags 0. A TUI that negotiated bit-3 (report_all_keys_as_escape_codes) would receive the legacy raw text it declined.

Now the snapshot carries proven kitty flags beside their sequence boundary, the forwarder reads flags once per commit, and bit-1 (report_event_types) commits are paired with exactly one release regardless of keyup/insertText ordering. Snapshot authorities expose only the active screen's proven flags, so an old host's absent field stays unknown rather than downgraded to a manufactured zero.

* fix(terminal): sync kitty flags and IME releases across snapshots

* trim wordinesss

* fix(terminal): settle owed IME release before fresh same-key press

When a keyup is lost and the same key is pressed again, settle the stale
record's owed release instead of discarding it — this maintains correct
IME state during recovery. Also refine Kitty flag propagation to only
carry proven baselines across snapshots, and tighten related comments.

* fix(terminal): gate kitty flags on sequence boundaries

- Remote snapshots only include flags when seq is present
- Daemon uses parsed flags value when defined
- Ensures correct flag ordering in snapshot replay

(cherry picked from commit 5bee7b5ce9)
2026-08-12 17:13:46 -07:00
Brennan Benson ffe2f0b2a9 fix(terminal): require agent identity for guarded sends (STA-4028) (#14092)
* fix(terminal): require agent identity for guarded sends (STA-4028)

Quarter-circle spinner glyphs (U+25D0-U+25D3) started classifying a title
as "working" in #13925, and that status alone authorized guarded agent
sends — which auto-submit with Enter — into any pane whose TUI animates
those generic progress frames.

Keep the glyphs as an activity signal, but stop treating a title whose
only agent evidence is a quarter circle as proof an agent owns the pane:
send authorization now falls through to recognized-agent identity in the
title or a recognized foreground process. Braille-spinner behavior is
unchanged.

* fix(terminal): preserve verified managed busy identity

* fix(terminal): bind busy identity to process incarnation

* chore(test): avoid duplicate terminal gate suite

(cherry picked from commit cb41f3c5f4)
2026-08-12 17:13:39 -07:00
Brennan Benson 735d1a17d9 fix(agent-title): treat Claude Code quarter-circle spinners as working (#13889) (#13925)
* fix(agent-title): treat Claude Code quarter-circle spinners as working

Claude Code 2.1.228 swapped its busy OSC title spinner from braille
(U+2800-U+28FF) to quarter circles (U+25D0/U+25D1). Orca recognized a busy
Claude title only by braille codepoints, so the new frames matched nothing.

The summary-bearing busy frame ("<glyph> Say hi in one word") carries no
"claude" name token, so it resolved to no-status. The tracker's "idle or
permission followed by no-status means the agent exited" rule then fired
mid-turn, confirmPtyAgentExit confirmed it, and the chat surface routed
exitChat -- kicking the tab to the terminal view on every message.

Widen the accepted glyph set via a shared containsAgentSpinnerGlyph helper.
Agent-specific braille frame shapes (Grok, Pi, synthetic Cursor) stay pinned
to their own glyph set.

Fixes #13889

* fix(agent-title): satisfy static analysis and trim scope

(cherry picked from commit 137e724119)
2026-08-12 17:13:27 -07:00
Jinwoo Hong a498b35bad fix(github): fail closed when stack metadata is unavailable (#13866)
* fix(github): fail closed on unavailable stack metadata

* fix(github): validate REST pull request response shape

* fix(github): allow omitted stack metadata

* test(github): cover enterprise stack probe failure

* test(github): cover null ordinary stack metadata

---------

Co-authored-by: E2E Test <e2e@test.local>
(cherry picked from commit 9f0bf39b04)
2026-08-12 17:13:27 -07:00
Jinwoo Hong d6d4a02689 fix(orchestration): close federated-read benchmark socket (#13939)
(cherry picked from commit c45c806220)
2026-08-12 17:13:27 -07:00
Jinwoo Hong 729dd0ca36 perf(orchestration): route federated reads over shared control (#13814)
(cherry picked from commit 09c8597fb7)
2026-08-12 17:13:27 -07:00
OrcaWin 1db692821a fix(wsl): preserve single-letter POSIX terminal cwd (#13859)
(cherry picked from commit e6eec11b9f)
2026-08-12 17:13:27 -07:00
Jinwoo Hong 61864ec1aa fix(terminal): prevent link action clicks reaching child (#13797)
(cherry picked from commit 6acd69c516)
2026-08-12 17:13:26 -07:00
OrcaWin 74d5b0c860 fix(windows): show first window before shell PATH hydration (#13799)
(cherry picked from commit e790266546)
2026-08-12 17:13:26 -07:00
Jinwoo Hong 87a8d13151 fix(artifacts): recover committed cloud mutations (#13796)
(cherry picked from commit 536d17dca3)
2026-08-12 17:13:26 -07:00
OrcaWin 82217a7287 fix(startup): restore Windows PATH before shell changes (#13792)
(cherry picked from commit c96ded8dfd)
2026-08-12 17:13:26 -07:00
OrcaWin a0a654c3a9 fix(renderer): reject malformed cursor and language-pack inputs (#13451)
## What this changes

Two renderer inputs were trusted because their TypeScript types said they were valid. Both are now validated at the boundary that owns them.

**Terminal cursor style.** `normalizeTerminalCursorStyleDefault` preserved any non-null migration-stamped value without checking it against the actual enum, so a runtime value outside `bar | block | underline` survived into `terminal.options.cursorStyle`. It now enum-checks and falls back to `block`. Applied on both read paths (desktop `Store` load, web `getStoredSettings`) and both write paths (`Store.updateSettings`, web `settings.set`), so an unsupported value cannot reach persistence, the `settings:changed` publication, or xterm.

**Plugin language packs.** The renderer stored the `listLanguagePacks()` IPC result without a runtime check, so a non-array response was assigned to state and later crashed its first array consumer on `.find`. Ingress now accepts only an array, drops members failing `isPluginLanguagePackRegistration`, keeps valid siblings in their original order, and logs which failure mode occurred. The registration guard requires `resourceLanguage` to equal `pluginLanguageResourceId(id)`, which keeps a pack with a missing or inconsistent identity out of i18next — that shape reproduces as `TypeError: Cannot read properties of undefined (reading 'includes')` with the guard removed.

Catalog validation is shape-only on this path (`validatePluginLanguagePackCatalogShape`), so revalidating an already-parsed catalog does not allocate a second copy of it. `isCatalogObject` also now requires a plain-object prototype, and the walk rejects repeated or cyclic object references.

## Root cause: partially known

Worth stating plainly, because the fix is defensive rather than causal:

- The non-array pack container and the out-of-enum cursor value are both reproduced directly by tests.
- Two additional field stacks are *consistent with* an invalid `resourceLanguage` and are blocked by this guard, but the reports do not prove that malformed registrations were their source.
- No production writer in current code or history emits an out-of-enum cursor style. The Ghostty config importer (`src/main/ghostty/mapper.ts`) already rejects unsupported `cursor-style` values. The original writer is unidentified.

Every in-tree producer supplies valid data, so these guards are no-ops on the current happy path. They are boundary hardening against a mutation we have not located, not a repair of a known writer.

## Trade-offs

- Malformed registrations are skipped with one warning rather than surfaced in the UI. Valid siblings keep their identity and order. Note the main-process registry already reports per-plugin parse errors, so this path only catches corruption after main has validated.
- Renderer ingress walks each catalog once per lazy load or `contentPacksChanged`. It does not run on render or terminal-output paths, but a maximum-size burst still costs tens of milliseconds synchronously. Pack count and plugin-ID length remain uncapped.
- An unsupported cursor value now renders and persists as `block`, so anyone relying on an undocumented third-party value loses it.
- Loading settings whose cursor style is absent or invalid now marks state dirty and rewrites once, matching the existing `terminalRightClickToPasteDefaultedForPlatform` pattern above it.

## Compatibility

`listLanguagePacks` is local `ipcMain`/`ipcRenderer` only and is not implemented in the web build, so nothing here crosses the remote wire — no RPC parameter, publication schema, stream frame, opcode, or capability changed. Validation is receiver-side at existing boundaries. Nothing is platform-, shell-, PTY-, native-module-, SSH-, WSL-, or worktree-dependent, and the web build normalizes on both read and write. Malformed plugin data is rejected before i18next sees it; no new permission, network path, executable input, or persistence schema was added.

## Verification

Locally on the final head: the 5 touched test files pass (591 tests), plus node and web typecheck, oxlint, and oxfmt. All GitHub required checks pass, including Windows packaging, static analysis, Git and wire compatibility, shell contracts, and all 32 Node 24/26 shards. The path-gated E2E job is skipped after its detector passed.

Scope note: an earlier revision generalized this hardening to `Project.sourceRepoIds` and profile transfer without field evidence. That scope was removed; the diff is cursor and plugin paths only.
2026-08-11 03:18:55 -07:00
Neil 1f2e18acc1 fix(e2e): repair seven specs whose assertions drifted from shipped behavior (#13785)
* fix(e2e): repair seven specs whose assertions drifted from shipped behavior

The E2E suite could not collect at all until #13758, so these seven had been
failing unobserved. Each is a stale test, not a product defect — verified
individually against src/ rather than by making the assertion pass.

- worktree-jump-palette-filter: the palette placeholder gained chats and
  terminals. Hoisted to one SEARCH_PLACEHOLDER const.
- tab-create-entry-file-paths: matched the omnibox by its translated
  aria-label. Switched to aria-controls, which is structural and unlocalized.
- browser-local-https-certificate-trust: once a load settles, the toolbar
  reload button relabels itself "Retry" alongside the failure overlay's own
  Retry, so the slot-wide locator hit two elements and failed strict mode. The
  test only ever passed inside the window where the toolbar still read "Stop".
  Narrowed by visible text; the icon button has none.
- repro-7732-gitlab-checks-job-details: the activity-bar label carries a
  failure suffix ("Checks — Error"), which an exact-name match stops seeing
  precisely when the checks under test fail. Anchored regex, and bounded the
  click so the poll retries instead of hanging on a label that flips mid-action.
- floating-tab-rename: the panel's open flag is persisted, so after the restart
  the helper's blind toggle closed the panel it was about to assert on. Made it
  idempotent.
- github-created-issue-start-prefill: starting an issue now routes through the
  quick-create composer, so the launch command only forms once that is
  submitted. The spec now drives it.
- ssh-config-host-import: P6 waited on "All hosts already in Orca", a string
  that exists nowhere in src/ and never could have matched. P7 required the
  tombstoned host to be absent, but listing it behind a "Removed from Orca"
  badge is deliberate — see the rationale at ssh-config-host-picker.ts:54 and
  the unit test already pinning it. Both retargeted; P7 still proves the host
  is excluded from bulk re-adoption, which is what it was for.

Verified locally: all seven pass. github-created-issue-start-prefill cannot be
verified on a machine whose gh resolves to an Orca terminal-attribution shim,
since hydrateShellPath puts that ahead of the fixture's fake gh; CI has no shim.

Three further failures are NOT addressed here. Adversarial review found the
obvious test-side fix for each would have masked a real product bug, so they
are being fixed in the product instead.

* fix(e2e): configure the issue spec's git remote before Electron launches

The spec added origin inside the test body, after the orcaPage fixture had
already launched the app and added the repo. "New GitHub issue" is disabled
when no repo is task-eligible, and eligibility is decided by the git remote
probe alone: repos.add runs detectRepoIconAndUpstream, and a settled "no
remote" writes gitRemoteIdentity = null, which is the ineligible marker.
Background enrichment then suppresses re-probing that location for five
minutes, so a remote added afterwards can never recover the button.

It passed only when the shared seeded repo happened to already carry an origin
from an earlier spec in the same worker — github-cli-stall-repro adds one,
source-control-create-pr-intent-switch removes one — which is why it passed in
the sharded lane and failed in the changed-specs lane.

Moved to test.beforeAll, whose worker-scoped testRepoPath runs before the
test-scoped Electron fixtures. Reproduced the failure against a fresh
origin-less repo, then confirmed the fix on the same, including --repeat-each=2.

Note the earlier attribution was wrong: the local failure at this line was
never the gh attribution shim. The button's enabled state reads `git remote -v`
and never consults gh, so a shim can only bite later, at issue creation.
2026-08-11 03:07:44 -07:00
Neil 3713dd7376 perf(git): reuse pinned OIDs for SSH file diffs (#13586)
Forward the renderer's already-pinned {mergeBase, headOid} to the SSH relay so a single-file branch diff reads the two blobs directly instead of rediscovering live HEAD. Six sequential git processes become two concurrent reads, and a branch move mid-review no longer changes which revision is displayed.

Equivalence with the legacy route is proven against real Git across 14 change types; wire compatibility is proven over a real SSH socket against relay bundles built from main and from the pre-merge-base.
2026-08-11 02:18:29 -07:00
Neil 383ae50a35 fix(utf8): stop reading a code point past the end of a sliced string (#13782)
Once V8 optimizes the calling function, `String.prototype.codePointAt` on a
sliced string pairs a trailing high surrogate with the code unit that follows
the SLICE inside its parent, returning a code point the string does not
contain. Every UTF-8 byte scan built on `codePointAt` therefore reported one
byte too many for a prefix slice cut mid-pair, but only after tier-up, which
is what made terminal-stream-byte-length.test.ts fail intermittently on the
same commit.

Read the units explicitly with `charCodeAt`, which stays bounds-correct in
every tier, via a shared `readUtf8CodePointAt`.
2026-08-11 02:04:10 -07:00
NeilandOrca c8ee48701b perf(renderer): keep catalog array identity and path-status cache on no-op refetches (#13770)
* perf(renderer): keep catalog array identity and path-status cache on no-op refetches

mergeByIdentity always allocated a fresh array, so project-group and folder-workspace
refetches replaced state by reference even when nothing changed, refiring a forced
folderWorkspace.getPathStatus sweep per row on every repos:changed.

Unchanged merges now return the previous array, and the paired
folderWorkspacePathStatuses resets are gated on the same check so a no-op refetch
neither clears the cache nor needs to refill it. Real catalog changes clear and
refire exactly as before.

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

* refactor(renderer): make the catalog arrays readonly

The identity guard can now return the caller's array, so a future in-place push or
sort on state.projectGroups / state.folderWorkspaces would alias store state. Typing
the merge helpers and the slice fields readonly removes both `as T[]` casts the guard
introduced and adds none, matching what #13744 did for state.repos.

Partial test fixtures cast the element rather than the array, so no `as unknown as`
laundering is needed.

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-11 01:50:37 -07:00
NeilandOrca e8904cf7d2 perf(renderer): skip runtime status map churn on unchanged re-probes (#13771)
setRuntimeEnvironmentStatus always cloned runtimeStatusByEnvironmentId, so a re-probe
returning an identical status still invalidated every subscriber. It now returns the
state unchanged when the computed entry matches the stored one.

checkedAt is excluded from the comparison: it is Date.now() per probe and no consumer
reads it, so including it would make the guard dead code. Generation advance and the
disconnect toasts still run on every call.

Co-authored-by: Orca <help@stably.ai>
2026-08-11 01:48:17 -07:00
NeilandOrca 0fe04e2c91 perf(sqlite): cache prepared statements in SyncDatabase (#13769)
prepare() recompiled every statement, so orchestration reads re-parsed the same SQL
on the main thread. Adds a bounded LRU keyed by SQL, cleared on close() and before
schema-changing exec().

Wildcard selects are excluded: node:sqlite builds the first post-schema-change row
from stale column names, so a reused SELECT * can silently drop a freshly added
column. PRAGMAs stay uncached.

Co-authored-by: Orca <help@stably.ai>
2026-08-11 01:48:14 -07:00
Neil 0aa7d0b5c1 refactor(renderer): unify the window park-visibility primitive (#13778)
The stream-gating hook (#13608) and the subscription parking installer
(#13621) each reimplemented `isWindowVisible() || isDocumentVisibilityProvenStale()`
plus its visibilitychange + stale-recovery subscription, and each carried
its own 500ms park delay that agreed only by coincidence.

Extract both into `lib/window-park-visibility.ts` and leave the two
consumers as thin wrappers: `useWindowStreamVisible` feeds the snapshot
and subscribe into `useSyncExternalStore`, the parking installer calls
them imperatively. The subscribe now always uses the defensive
`typeof document` guard the parking side already had, and registers
stale recovery before the visibilitychange listener so the stale latch
is cleared before the callback reads it.

Both park delays now derive from one `WINDOW_HIDE_PARK_GRACE_MS`: they
debounce the same signal against the same user behaviour (a quick
app-switch round trip). The parking side's 8x reveal-side backoff stays
where it is - that models its own resume cost, not the grace window.

Renderer-authoritative gating (#13622 native chat, #13634 editor panels)
is deliberately left alone; it keys off Zustand routing state, not OS
visibility.

Also switch the active-runtime ref writes in web-session-tabs-sync to
`useLayoutEffect` so no resume event can land between paint and the
passive flush, and correct the stale comment above them - the install
effect reads neither ref (both readers are gated on `isVisibilityResume`).
2026-08-11 01:43:47 -07:00
NeilandOrca 12f58f6bee fix(editor): keep an invalidated diff visible until its reload lands (#13777)
Mark invalidated diff content stale instead of dropping it, mirroring the file-content treatment from #13634. The diff read-generation fence (diffReadGenerationRef) already rejects a superseded read's write-back, so retaining the bytes and swapping them on arrival is safe and keeps every RPC saving -- it just stops the loading placeholder flashing on each reveal.

The git-status reload effect now treats a stale entry like a missing one so the lazy-load effect stays the single fetcher for it.

Co-authored-by: Orca <help@stably.ai>
2026-08-11 01:43:44 -07:00
NeilandOrca b3965f3205 perf(native-chat): suspend hidden transcript streams (#13622)
* perf(native-chat): suspend hidden transcript streams

* fix(native-chat): keep assembly renders pure

* fix(native-chat): keep a transient error from stranding a revealed chat

- An error snapshot frame no longer latches frameArrived, so the in-flight
  read can still seed the pane instead of leaving it on the error surface.
- Retained history is shown over a full-pane read error for the same source.
- The paged read window survives a hide/reveal; only a source change resets it.

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

* fix(mobile): match the trimmed native-chat retention signature

The shared retention type no longer takes `loading`, so mobile's call was an
excess-property error that broke the mobile typecheck job. Dropping it also
lets mobile inherit the desktop behavior: a stream error or dropped client
keeps the last transcript instead of swapping it for the error empty state.

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-11 01:27:25 -07:00
NeilandOrca 33e8d89d48 [perf-remote] perf(renderer): park hidden remote browser screencasts (#13608)
* perf(renderer): park hidden remote browser screencasts

* test(renderer): pin stale-visibility term and parked-unmount teardown

Restores the WHY behind the '|| isDocumentVisibilityProvenStale()' term the
emulator-module dedupe dropped, and pins it plus the close-tab-while-parked
path with tests. Extracts the lifecycle harness so the spec stays under
max-lines without a suppression.

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

* fix(ci): exclude test-support modules from the localization audit

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-11 01:27:02 -07:00
NeilandOrca 94a4756095 [perf-remote] perf(renderer): park hidden remote session mirrors (#13621)
* perf(remote): park hidden session mirrors

* perf(renderer): bound visibility resume cost and repair fences

- Back off the park delay after a park the user undoes quickly; each resume
  re-enumerates every paired host, so short hide/reveal churn now stops
  parking instead of paying that cost per cycle.
- Expire the active-worktree omission fence one visibility generation after
  the inventory that set it, and name the unfiltered-inventory invariant it
  depends on.
- Never skip the unchanged resume inventory for a worktree whose repair
  replay is already armed.

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

* fix(renderer): move active-runtime ref writes out of render

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-11 01:14:40 -07:00
Neil baeb1b3cc8 perf(editor): stop hidden panels duplicating remote reloads (#13634)
* perf(editor): park hidden external reloads

* fix(editor): sync content refs after commit

* fix(editor): close post-commit ref window

* test(editor): cover commit-time visibility

* fix(editor): keep invalidated content visible and stop redundant reads

- Mark invalidated file content stale instead of dropping it so a reveal swaps
  bytes in place; the read-generation fence already blocks stale write-backs.
- Skip the lazy read when the newest read for the tab is still outstanding, so
  a worktree flip-flop cannot double-fetch.
- Stop claiming Cmd/Ctrl+S outside the workspace view, where no mounted panel
  owns the request and the keystroke was silently swallowed.
2026-08-11 01:14:37 -07:00
NeilandOrca 8fad723f84 fix(worktree-poller): sample the scan gate before its listing (#13772)
A write landing during a full scan was recorded by the post-scan gate stat,
so the next tick saw a clean gate and the create/delete waited up to 15 ticks
(~30s) for the backstop. Sample each gate dir's signature before its listing
so a racing write reads as stale and forces one rescan on the next tick.

Also fixes the flaky 'retires stale marker probes' spec: a folder caught
mid-rm opened a pending-marker window that the swallowed gate change kept
alive for the whole backstop gap (43 probes vs the 30 budget).

Co-authored-by: Orca <help@stably.ai>
2026-08-11 01:14:28 -07:00
Jinjing 9b79cc1b9e Add copy button for quick command (#13768)
* Add copy button to quick commands with visual feedback

Quick command rows now display a copy button that copies the command body to clipboard. The button shows brief visual feedback ("Copied" or "Couldn't copy") and is disabled when the command body is empty. Includes desktop and mobile UI, tests, and full i18n support.

* fix(ci): unblock verify for quick-command copy button

Key feedback to the copied body so prop changes drop stale labels without
setState-in-effect, and mock expo-clipboard in the mobile list test.
2026-08-11 01:06:03 -07:00
Jinwoo HongandJinwoo-H 5e3a2d25f7 Filter remote workspaces by creating device (#13718)
* Filter remote workspaces by creating device

* Fix workspace origin filter reconnect behavior

* Shorten workspace origin filter label

* Refine remote workspace filter UX

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-11 01:05:12 -07:00
NeilandOrca 21d5ca6db3 [perf-remote] perf(renderer): dedupe remote status hydration (#13705)
* perf(renderer): dedupe remote status hydration

* fix(runtime-status): re-list host catalog when the listing goes stale

Coverage matching cannot observe catalog edits made by another client or the
orca CLI, so gate hydration on catalog listing age too.

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

* test(runtime-status): reset the catalog listing clock between tests

The staleness guard keeps its last-listed timestamp in module scope, so the
TTL test left fake time behind and made every following test in the file
depend on its position. Add a reset hook, call it in beforeEach, and pin the
TTL invariant on an externally added host rather than the timer alone.

Also extracts the coverage predicate in fetchSettings and restores the WHY
comment about compat failures clearing on a reachable status.

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-11 00:33:55 -07:00
NeilandOrca a639bd355e [perf-remote] perf(codex): skip migration scans for nonshared reattaches (#13654)
* perf(codex): skip migration scans for nonshared reattaches

* fix(codex): require provenance to skip migration scans and bound ignored launches

Drops the exit-match escape hatch that skipped a scan without route provenance, and ages out ignored reattach launches on the existing 60s/256 lease policy so a lost exit can no longer pin an active launch (and its completion marker) for the process lifetime.

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-11 00:33:17 -07:00
NeilandOrca bb08b66b21 perf(runtime): avoid forced SSH hydration on project refresh (#13636)
* perf(runtime): avoid forced SSH hydration on project refresh

* fix(runtime): backfill SSH states for targets learned without a state read

A failed-connect resync labelled a target with no ssh.getState, and the new
metadata-refresh gates then skipped it forever: no SSH chip and every worktree
mutation to that host failed the connection-generation check.

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-11 00:20:53 -07:00