Commit Graph
9741 Commits
Author SHA1 Message Date
Merge Sim 0cf48d64f9 Read the scoped Keychain channel the managed bridge actually uses
The scoped Keychain mock keys managed config dirs by path, so tests seeding the
shared field left the bridge re-reading whatever the previous launch wrote — the
foreign credential never reached the code under test and the identity assertions
passed without exercising the guard.

Splits the macOS bridge cases into their own file; the combined file crossed the
800-line ceiling.

Claude-Session: https://claude.ai/code/session_012E63B2jhajtUbArQHhZjVQ
2026-09-01 21:19:10 -07:00
Merge Sim 4b8f97c192 Prove scoped credential identity against its own config dir
Real credential blobs carry no identity fields, so the match rests entirely on
the .claude.json record. The bridge read that through the path resolver, which
resolves against the main process env and yields the shared file, not the
managed dir the scoped credentials belong to. A genuine in-pane CLI refresh was
then rejected as a mismatch, leaving the rotated token stale in managed storage
for the next launch to replay; and a stale shared record could vouch for a
different login made inside a managed pane.

Claude-Session: https://claude.ai/code/session_012E63B2jhajtUbArQHhZjVQ
2026-09-01 21:17:57 -07:00
Merge Sim bab06c4b6b Use trusted managed path for scoped keychain cleanup 2026-09-01 16:37:52 -07:00
Merge Sim 94171acae9 Surface managed Claude keychain fallback state 2026-09-01 16:34:41 -07:00
Merge Sim 7cbda71833 Guard scoped Claude keychain read-back by identity and freshness 2026-09-01 16:31:02 -07:00
Merge Sim 430257a647 Delete scoped Claude keychain credentials on account removal 2026-09-01 16:29:31 -07:00
Merge Sim 459f95286f Hash the Claude config dir as NFC, like the CLI does
The CLI derives its scoped Keychain service from sha256 over the NFC-normalized
CLAUDE_CONFIG_DIR literal. Orca hashed the string unnormalized, so on macOS —
which surfaces decomposed (NFD) paths — a non-ASCII config dir produced a
different service name than the CLI reads. A missing Keychain item falls back to
the file silently, so that account is unauthenticated with no error anywhere.

Ablation: dropping the normalize fails 2 of the 3 new cases. The test also pins
that the literal is hashed as given, so a later resolve()/tilde-expansion breaks
a test rather than breaking auth.
2026-09-01 15:28:14 -07:00
Merge Sim 674f86d03c test: pin host Claude account isolation 2026-09-01 13:57:40 -07:00
Merge Sim 914bb8e55e test: pin Claude scoped keychain path hashing 2026-09-01 13:54:11 -07:00
Merge Sim 2af65eede5 fix: trust Claude migration destination before shared read 2026-08-31 22:11:46 -07:00
Merge Sim 6d13dc2bbf test: close Claude shared auth migration coverage 2026-08-31 22:11:36 -07:00
Merge Sim c424d4065a Give the status bar copy for the expired-sign-in state
Step 1 makes an expired stored token report token_expired instead of Orca
refreshing it, but neither status-bar switch handled the new kind, so the
type-aware lint failed exhaustiveness. The adjacent 'Refreshing sign-in' copy
would also have been wrong: Orca no longer refreshes, the CLI does.

Also drops two redundant `Promise<unknown> | unknown` unions, where unknown
already absorbs the promise arm.
2026-08-31 21:20:28 -07:00
Merge Sim 770549bdef fix: preserve re-authenticated Claude credentials 2026-08-31 20:01:04 -07:00
Merge Sim f83ae4295b refuse macOS routing when keychain bridge fails 2026-08-31 19:44:38 -07:00
Merge Sim 2e042d2258 test macOS managed credential bridge 2026-08-31 19:44:37 -07:00
Merge Sim ec49ae6df5 fix Claude managed auth routing isolation 2026-08-31 19:44:37 -07:00
Merge Sim 52ceb92a96 Allow CLAUDE_CONFIG_DIR as a wrapper contract global
The wrapper guard permits each agent's per-account home export by name
(CODEX_HOME, MIMOCODE_HOME, OPENCODE_CONFIG_DIR); CLAUDE_CONFIG_DIR is the
same contract and joins them. Snapshots regenerated after verifying the only
content change is the Claude restore line.
2026-08-31 19:44:37 -07:00
Merge Sim 38e09105cf harden Claude auth migration and keychain errors 2026-08-31 19:44:37 -07:00
Merge Sim afcc06cb3d fix Claude managed runtime routing and pane environment 2026-08-31 19:44:37 -07:00
Merge Sim 49b484a8f0 fix(claude-auth): preserve legacy shared runtime compatibility 2026-08-31 19:44:37 -07:00
Merge Sim aefe7e67fa Gate Claude shared auth migration by active identity 2026-08-31 19:44:37 -07:00
Merge Sim 4d95e60a48 Classify Claude managed credential availability 2026-08-31 19:44:36 -07:00
Merge Sim 987c74237d Isolate Claude auth ownership and migration 2026-08-31 19:44:36 -07:00
Merge Sim 93e58124d7 Opt relay panes out of the Claude config-dir restore
Mirrors codexHome: relay panes are remote-owned, so they keep the host's own
CLAUDE_CONFIG_DIR rather than Orca's managed one.
2026-08-31 19:44:36 -07:00
Merge Sim 4d70e19e96 Route Claude panes through a per-account CLAUDE_CONFIG_DIR
Mirrors the CODEX_HOME spawn-time mechanism: ORCA_CLAUDE_CONFIG_DIR joins the
positive overlay allowlist and each shell wrapper re-exports CLAUDE_CONFIG_DIR
after user rc files, so a hand-typed claude inherits the selected account.

WIP recovered from an interrupted worker; per-account home creation and the
shared-store migration are not implemented yet.
2026-08-31 19:44:36 -07:00
Merge Sim d768cc6fc5 Test indeterminate Claude credential reads 2026-08-31 19:44:36 -07:00
Merge Sim 0707387473 Align Claude usage tests with read-only credentials 2026-08-31 19:44:36 -07:00
Merge Sim b0df241434 Keep legacy Claude Keychain compatibility best effort 2026-08-31 19:44:36 -07:00
Merge Sim a8b52c5460 Refuse Claude auth writes after indeterminate reads 2026-08-31 19:44:35 -07:00
Merge Sim 84a32ab33b Make Claude usage credential reads read-only 2026-08-31 19:44:35 -07:00
Neil a5796ec8eb refactor(runtime): split OrcaRuntimeService and compatibility tests (#17605)
* refactor(runtime): split OrcaRuntimeService into focused modules

* test(runtime): cover admission tiers and strict worktree reconciliation

* fix(runtime): preserve owner and structured session visibility

* fix(runtime): port post-extraction compatibility fixes

* fix(runtime): preserve skill-share cancellation barrier

* test(runtime): update identity inventory after extraction

* fix(runtime): preserve hook transport environment cleanup

* fix(runtime): consolidate idle probe imports

* test(runtime): retire split file process allowlist entry

* fix(runtime): route child process types through shared boundary

* test(runtime): preserve worktree host metadata precedence

* fix(runtime): update extracted test seams

* fix(runtime): gate the split's ts-nocheck set and restore the stop-confirmed contract

Audit follow-ups for the OrcaRuntimeService split:

- Freeze the 171 @ts-nocheck files behind a ratchet so no new file can disable
  type checking. The split's linear mixin chain cannot express forward
  references yet, so the existing suppressions are grandfathered; the baseline
  may only shrink.
- Drop the stray @ts-nocheck at the end of orca-runtime-get-status.ts. It sat
  after the first statement, where TypeScript ignores it, so the module was
  already checked.
- Restore `retireRejectedPty(ptyId, stopConfirmed: boolean)` as a required
  argument. The split widened it to optional and patched the resulting error
  with `stopConfirmed === true`; an omitted argument would have silently taken
  the unverified-stop path instead of failing to compile.
- Guard that every orca-runtime-tests fragment is imported by the compatibility
  entrypoint. The fragments are .spec.ts, which no Vitest include glob matches,
  so one left out of the list would silently stop running.

* fix(runtime): restore four behaviors the OrcaRuntimeService split dropped

Audit findings against the refactor's true base (ad5ba2572e):

- retirePtyAgentLaunchAuthority collected pane keys after deleting the
  restored-authority receipt instead of before it. collectPaneKeysForPty reads
  that receipt, so a receipt-only pane lost its key and never had its agent-hook
  compatibility authority retired. on-pty-exit.ts already carried a comment
  naming this exact invariant.
- The PTY-exit path kept orchestrationMailboxNotifications.retirePty but lost
  the loop that schedules a debounced mail-pointer repoint for the dead pty's
  terminal handle and any run bound to its panes. Restores the schedule call
  count to 7, matching base.
- subscribeToPtyExit lost isPtyKnownExited's leaf fallback and its
  post-registration lifecycle-generation recheck. leavesByPtyId is rebuilt from
  the renderer graph independently of ptysById, so a leaf can outlive its pty
  record; without the fallback a caller waiting on an already-dead pty never
  gets released.
- The chain root declared `[key: string]: unknown`, which base had nowhere. It
  leaked through the exported runtime type into every consumer, so any misspelled
  member access typechecked as unknown instead of erroring, and it accounted for
  957 of the suppressed errors. Removing it costs zero type errors.

* fix(runtime): restore escalation prose and unscoped automation publication

Two more behaviors the split dropped, each with a regression test that fails
against the pre-fix code:

- The worker-exit escalation stopped deriving its title through
  buildOrchestrationTaskDisplayMetadata and inlined `task.spec` instead. That
  ignored an explicit task_title, dropped the single-line normalization and the
  80-character bound, and turned the no-spec case into a quoted, duplicated id.
  A multi-paragraph spec landed verbatim in the coordinator's banner. The
  existing 11 tests all use short single-line specs, where the derived title and
  the raw spec are identical, so none of them could see it.
  Also reverts an added `if (!handle) return` guard: the dispatch lookup is
  deliberately keyed on the pane as well, because a reminted handle no longer
  matches the row while the pane identity outlives the remint.
- updateAutomation stopped going through automationChangePublications and
  published `source` unconditionally while gating the fallback on a non-null
  destination. A destination the store can no longer name then published only
  the stale source, so subscribers scoped elsewhere kept rendering a row that
  had left them — the exact case the helper documents. The helper had been left
  with zero callers; all three sites use it again.

* fix(skills): stop swallowing lookup errors and hard-erroring on non-ssh hosts

Follow-ups from auditing the skill install path against the refactor's base:

- resolveWorktree wrapped showManagedWorktree in `.catch(() => null)`, so a
  transient git or IO failure surfaced to the user as
  skill-install-workspace-not-found with the real cause discarded. Errors
  propagate again; a genuine id mismatch still returns null.
- resolveSkillSshTarget threw skill-install-workspace-host-unavailable when the
  execution host was neither local nor ssh, on both the repo and folder
  branches. Base gated these on connectionId, so a runtime-owned repo simply
  was not an SSH install and fell through to the local path. Both return null
  again, and the error code the split invented is now unreferenced.
- listManagedSkillInstalls awaited the receipt walk and the worktree resolve in
  sequence. They are independent and either can hit disk, WSL, or an SSH scan,
  so Promise.all is restored.

Deliberately unchanged: resolving the worktree through listResolvedWorktrees
rather than showManagedWorktree, which disambiguates a worktree id colliding
across hosts and is covered by its own test, and the SSH-folder
skill-install-ssh-dispatch-required throw, which matches the repo branch.

* fix(runtime): merge duplicate worktree-logic imports

The #17448 port added a third import from ../ipc/worktree-logic, which the
code-quality oxlint config rejects under --deny-warnings. Plain oxlint does not
flag it, so it only surfaced in CI's static analysis job.

* ci: run the ts-nocheck ratchet in PR checks

pr-workflow-lint-parity requires every leaf command in `pnpm lint` to have a
matching step in pr.yml. The ratchet was wired into lint but not the workflow,
so PR CI would not have enforced it.

* Merge remote-tracking branch 'origin/main' and retry the paired-host launch evaluate

main advanced 9 commits; none touch the orca-runtime.ts this branch splits, so
nothing needed porting.

CI failed twice on `Execution context was destroyed` thrown from
headless-paired-runtime-host's first `evaluate` after launch — a different spec
each run, which is the signature of the flake #17780 describes rather than a
regression. That commit added retryTransientMainEvaluate and adopted it in five
helpers but not this call site, even though its docblock names exactly this
case: the first evaluate after electron.launch() resolves, before the app is
ready. Wrapped it the same way.
2026-08-31 19:34:55 -07:00
Jinwoo Hong 1efd4e1a97 test(e2e): seed source control diff before opening panel (#17784) 2026-08-31 22:22:17 -04:00
Jinwoo Hong 12be5aed9e fix(browser): refuse devtools for offscreen guests (#17485) 2026-08-31 22:15:47 -04:00
Jinjing c5d43b8a24 Avoid Linear read re-fetches when workspace scope is unchanged (#17529)
* Avoid Linear read re-fetches when workspace scope is unchanged

Derive a stable scope signature that captures only the connected state
and workspace identity, ignoring volatile metadata like displayName.
Use this in dependency tracking so Linear searches don't re-run on
status updates that don't affect which issues can be queried.

* Expand workspace scope to detect credential and org changes

Cache invalidation key now includes credentialRevision and organizationUrlKey for
both workspace and viewer, ensuring Linear reads re-fetch when credentials rotate or
organizations are renamed — fields that affect what read operations return.

* Include activeWorkspaceId in workspace scope signature

URL lookup falls back to the active workspace even when all workspaces
are selected, so activeWorkspaceId must be part of the scope signature
to ensure reads are keyed correctly.
2026-08-31 18:56:06 -07:00
Neil f116d2ca2a test(ci): retry Windows teardown EPERM and restart evaluate misses (#17780)
Restart-survival polls treated a recycled renderer as a hard failure.
Wrap those evaluates so "Execution context was destroyed" is a pending
miss. Windows package-lane teardowns after a force-kill used rmSync
with force:true only, which does not absorb EPERM; put them on the
shared maxRetries:8 policy.
2026-08-31 18:53:01 -07:00
Neil eff317939a fix(terminal): mount one surface per workspace id in the workbench (STA-4846) (#17432)
* fix(terminal): mount one surface per workspace id in the workbench (STA-4846)

* test(terminal): pin the workbench projection against under-selecting

Losing a surface unmounts live terminals, which is worse than the
duplicate mount STA-4846 fixes, so cover every catalog shape that reaches
the workbench: local-only rows that name no host, an unqualified row
colliding with a host-qualified one, two SSH hosts on one id, folder rows
across three hosts, folder ids alongside git worktree ids, and a
whole-catalog assertion that the emitted id set equals the distinct input
id set. Also pin the `useAllWorktrees` -> `useWorktreeMap` swap: both read
the same WeakMap-cached snapshot, so the zustand compare is unchanged.

Harden the folder tie-break to require the row to name its own host.
`getCatalogOwnerHostId` defaults an unstamped row to `local`, which would
let a row that never named a host win the `local` tie and mount another
host's path; it now keeps first-wins instead of guessing.

* fix(terminal): surface the unresolvable folder-surface collision

When two hosts publish the same folder-workspace id and the active workspace's
host cannot be resolved, the projection drops one row's folderPath first-wins.
That path is the PTY cwd for any tab without a startupCwd, so the drop was
silent. Warn on it, and pin the two tie-break branches the unit tests missed:
a colliding row that is not the active workspace, and the same collision with
the rows in swapped order (a host reconnect re-appends its rows, flipping which
row is first mid-session).

* test(e2e): ride out Playwright's spurious main-process evaluate rejection

`e2e / changed e2e specs` failed on `pr11346-selected-runtime-add.spec.ts`
with "Execution context was destroyed, most likely because of a navigation"
from the paired client's first `app.evaluate` — the isolated-HOME assert that
runs one millisecond after `electron.launch()` resolves, which is before the
app is `ready`. Nothing navigates there: Playwright raises that message for
any main-process CDP failure that is neither a JS error nor a closed session,
and `ElectronApplication.evaluate` is unreliable on Electron 27+
(microsoft/playwright#33737). Reproduced locally, and a plain re-run of the
same commit went green.

Extract the retry `installTerminalPtyWriteSpy` already carried for this exact
message into `retryTransientMainEvaluate`, and use it for the launch-time home
read in all three launchers. The read is idempotent and a real boundary escape
still throws on the first successful read.

Also forward the paired client's process logs before the assert instead of
after: this failure reached CI with none of the client's own output, because
forwarding had not started yet.

* test(e2e): wait on the owning group before asserting a Cmd-J browser tab is active

`changed e2e specs` then failed at the remote browser-page step: the store poll
had already seen `activeBrowserTabId` land on the mirrored workspace, but
`[data-tab-id=...][data-active="true"]` never appeared. `data-active` on a
`BrowserTab` is the strip's active tab, which comes from the owning group's
`activeTabId` — not from `activeBrowserTabId` — so the DOM assert was racing an
activation the poll never waited for. The simulator rows in the same spec
already poll the group; the two browser-page rows did not.

Poll the same triple for them, so a genuinely stuck group fails with the ids it
ended on instead of a bare "element(s) not found".
2026-08-31 18:45:52 -07:00
Neil 406bd0e378 perf(relay): cache process-table descendant indexes (#17646)
* perf(relay): cache process-table descendant indexes

* fix(relay): keep the process-table index first-wins and narrow

Two defects in the memoized index this PR introduced.

- Restore the first-wins duplicate-pid tie-break the relay had as
  `rows.find()`. A process whose argv contains a newline makes `ps` print a
  continuation line that the lenient parser can accept as a spurious row
  duplicating a real pid; that row always FOLLOWS the real one, so last-wins let
  it capture the pane's foreground. The rule now lives in
  `buildProcessTableIndex`, so the batched evidence resolver's `byPid.get(rootPid)`
  root lookup gets the same semantics the subsystem had before indexing.
- Build only the two indexes a resolver reads. `byPgid`/`byTpgid` have no readers
  repo-wide, and delegating to a four-map build made a one-pane relay pay more
  per 500ms capture than the single `childrenByParent` map it replaced --
  a regression in the majority topology, in a PR whose point is relay CPU.

Matches the same deletion in #17763 line for line so whichever merges second
resolves trivially.
2026-08-31 18:38:03 -07:00
Jinjing d2aab68ae7 Automations ux improvement (#17626)
* Add keyboard navigation to automations UI

Improves workflow efficiency by enabling keyboard-driven navigation
across automations list, run history, and detail pane tabs.

* Add Escape key support to automations detail pane

Pressing Escape now clears external and automation run page views,
then returns to the automations list. Also improves cross-browser
compatibility of keyboard event handling by using Element checks and
getAttribute instead of dataset access.

* Fix keyboard navigation to let Enter key reach focused controls

- Enter key now passes through to focused buttons, links, and other interactive controls
- Arrow key navigation through automation run history still works
- Prevents intercepting native keyboard behavior of interactive elements

* improve test

* Move keyboard focus to follow row selection

When navigating automation runs with arrow keys, focus must follow the selection so Enter key acts on the newly selected row rather than the previously focused one.
2026-08-31 18:37:19 -07:00
Jinjing 50938b2dbd Serialize filesystem watcher batch flush operations (#17602)
* Serialize filesystem watcher batch flush operations

- Prevent dropped events during rapid concurrent file changes
- Queue and drain follow-up batches to preserve event ordering
- Cancel pending batch work when watchers are torn down

* Prevent queued batch drain while debounce timer is armed

An armed timer means the debounce window is still open. Drain only after
the window closes to avoid splitting related filesystem events across
separate payloads.

* Remove redundant batch timer cleanup

Rely on cancelLocalBatchFlush to handle the batch timer
teardown, eliminating duplicate logic in the watcher
cleanup path.
2026-08-31 18:28:26 -07:00
Neil 2222e54754 refactor(test): organize SSH and terminal recovery fixtures (#17751) 2026-08-31 18:18:15 -07:00
Jinwoo Hong 40d245fe45 ci(release): gate signing behind release preflight
Prevents SignPath requests until all blocking release gates pass.
2026-08-31 21:17:31 -04:00
Neil ae35e044f2 fix(terminal): keep restored OSC-8 ranges across a no-op resize (#17759)
Cold restore seeded a checkpoint's OSC-8 link ranges and then replayed records
that resize, so any resize record after the checkpoint dropped them and
restored hyperlinks in scrollback lost clickability. Same-size resize records
reach the durable log routinely, because every attach re-asserts the pane's
dimensions and session-output-plane records each one without a same-size
dedupe — so an ordinary reattach was enough to lose the links.

Restored ranges are row-indexed, so clearing them on a reflow is right; a
resize to the size already applied is not a reflow. Gate on the dimensions
actually changing.

Introduced in d46349ce82 ("fix: improve mobile link modifier handling",
#5597), which added setRestoredOscLinks along with unconditional clearing in
both resize() and clearScrollback(). clearScrollback's clearing is correct and
is unchanged, with a test pinning it.

Found during adversarial review of #17752 and filed as #17756. Not a
regression from that PR: #17667 had incidentally masked it by gating no-op
resizes to protect a snapshot cache, and removing the cache removed the gate.
The same gate returns here on its own terms — as a correctness fix with tests,
rather than as a side effect of a cache.
2026-08-31 18:06:08 -07:00
Neil ad4f068040 fix(diff): close large-diff deferral review findings from #17521 (#17758)
* fix(diff): close large-diff deferral review findings from #17521

Deferral keyed "no line counts" off the untracked area, which both prompted
ordinary untracked binaries and silently auto-loaded every tracked row when a
status pass skipped counting (entry cap hit, numstat failed) — the freeze case
the deferral exists for. Decide from the path instead: rows that render as a
preview or a binary stub stay automatic, everything Monaco would open as text
defers.

Also give all three combined-diff virtualizers one shared row estimate, so the
PR-review viewers stop estimating a deferred/in-flight large row at 88px while
DiffSectionItem renders it at 188px, and drop the dead isLoadOnDemand
parameter that estimate covered.

* fix(diff): stop deferring cheap uncounted rows the extension list misses

The path-only rule relocated friction rather than removing it: every uncounted
row deferred unless its extension was in BINARY_FILE_EXTENSIONS, so two classes
of tracked row flipped to a "Large diffs are not rendered by default" prompt
they had never shown. Tracked binaries outside the list (this repo's own
resources/build/icon.icns, plus .tiff/.avif/.psd/.parquet and every
extensionless binary) get '-\t-' from `git diff --numstat`, and a submodule
whose only change is untracked content inside it gets no numstat row at all
while porcelain v2 still reports `1 .M S..U ... sub`. Both are cheap, and both
are unreachable from a hardcoded extension list — verified against real git.

OR the extension check with two signals already on the entry. A submodule row
diffs to a "Subproject commit" line or two whatever it contains, so it is
always cheap. And an uncounted row whose siblings in the same pass DID get
counts is uncounted for a reason of its own: for a tracked row that reason can
only be numstat's binary marker. Untracked rows keep deferring either way,
since the scan also skips them past MAX_UNTRACKED_LINE_COUNT_BYTES and their
size is exactly what is unknown. No new field crosses git status, the wire, or
the section cache; `submodule` and the sibling counts are already there.

Fan-out, accepted deliberately: when a pass counts nothing at all — didHitLimit
at DEFAULT_GIT_STATUS_LIMIT, or runNumstat returning null — no row has a
counted sibling, so the whole combined diff renders as Load prompts. Keeping
it. Over 1000 changed entries is precisely the freeze this deferral exists for,
and auto-loading that many unbounded Monaco models is the bug, not the
mitigation; a numstat failure leaves every size genuinely unknown. Each row
still has its own Load diff button, so nothing is unreachable — the only thing
missing is a bulk "load all", which would reinstate the freeze on demand.

* fix(diff): scope the counted-siblings signal to one counting pass

hasCountedSiblings was one boolean over the whole entries array, but that array
is not one counting pass. combined-all — the default whenever a branch compare
exists — concatenates uncommitted rows with branch-compare rows, and even within
the uncommitted set staged and unstaged are separate numstat calls that fail
separately. So a single counted branch row vouched for an uncommitted pass that
counted nothing (numstat null, or didHitLimit at DEFAULT_GIT_STATUS_LIMIT), and
every uncounted row in it auto-loaded into exactly the Monaco freeze the
deferral exists to prevent: the guard was off in the default view.

Collect the passes that actually counted something, keyed by staging area for
status rows and 'compare' for branch/commit rows, and ask that set per row.
Untracked rows are unaffected — they never consult the signal.

Class 1 of the charter (tracked binaries outside BINARY_FILE_EXTENSIONS) stays
open, deliberately. Porcelain v2 reports a modified binary as `1 .M N... 100644`
— indistinguishable from text — so only `git diff --numstat`'s `-\t-` knows, and
that stdout is parsed on the host (shared/git-uncommitted-line-stats.ts) for
both the local and relay status paths. The renderer sees entries, not numstat,
so surfacing it per row means a new field on GitStatusEntry and
GitBranchChangeEntry that also has to be re-applied in two attachLineStats
copies and in the line-stats reuse cache, which persists only {added, removed}
and would silently drop it. The one existing field that could carry it —
added/removed set to 0 — changes what the host publishes to old clients and
mobile, contradicts the documented "undefined for binary files" contract, and
collapses the undefined-vs-zero distinction the virtualizer's height estimate
reads. So a lone tracked .icns still shows the load prompt; not worth a wire
field, and not worth another hardcoded extension.

* fix(diff): stop calling an uncounted diff large in the load prompt

The deferral prompt had one sentence for two different reasons. A row over
MAX_AUTOMATIC_DIFF_CHANGED_LINES really is large. A row with no counts at all —
numstat's binary marker, a pass that skipped counting — is deferred because its
size is unknown, and "Large diffs are not rendered by default." is simply false
for it: a lone tracked resources/build/icon.icns with no counted sibling in its
own pass is 4 KB and still says large.

Split the copy on the counts the section already carries. No new field on the
entry, nothing across the wire, no change to attachLineStats or the line-stats
cache — the predicate is renderer-local and mirrors the uncounted branch of
shouldLoadCombinedDiffOnDemand, so the two stay in step.
2026-08-31 17:51:56 -07:00
Neil 704167197a perf(relay): serve one ps capture per window and pin the batched inventory path (#17763)
Follow-up defect fixes for the batched PTY-inventory evidence path (#17525),
now on main.

- One memoized `ps` capture serves both the lenient and strict views. The two
  readers ran byte-identical argv behind separate caches, so a relay serving
  both forked `ps` twice per 500ms window — the doubling issue #6288 removed.
- Drop the `byPgid`/`byTpgid` indexes no resolver reads, plus the zero-caller
  `parseProcessTableRowsStrict` and `getFreshStrictProcessTableSnapshot`; the
  batch resolver now reuses the shared index lookup and candidate score instead
  of private copies.
- Restore `getForegroundProcessName`'s ladder contract: the extracted table scan
  answers null again, so an unconfirmed wrapper fallback publishes the
  recognized (normalized) name rather than node-pty's raw one.
- Pin the SHIPPED `pty.listProcesses` path: one capture and one linear row pass
  for N panes, and node-pty's own name (never "shell") when the capture cannot
  disambiguate a `node`/`python` wrapper.
- Pin the hidden-pane cadence gate in the production option shape, and move the
  strict-parser coverage next to the parser it tests.
2026-08-31 17:45:55 -07:00
Jinwoo Hong 26031ca317 fix(browser): scroll oversized viewport presets (#17569)
* fix(browser): scroll oversized viewport presets

* fix(browser): preserve guest wheel scrolling at viewport edges

* fix(browser): keep viewport scroll state synchronized

* test: assert partial viewport wheel forwarding
2026-08-31 20:39:58 -04:00
Brennan BensonandMerge Sim 1a47b9ee85 fix(remote): distinguish SSH transport from runtime availability (#17710)
* fix(remote): distinguish transport from runtime availability

* fix(remote): preserve transport diagnostics for unavailable runtime

* fix(remote): propagate transport diagnostics to host setups

* fix(remote): keep unavailable runtimes out of ready setups

* fix(remote): preserve unavailable runtime state in settings

* fix(remote): preserve reconnecting runtime state

* fix(remote): guard stale settings connectivity

* fix(remote): preserve diagnostics after main merge

* fix(i18n): preserve translations during runtime status merge

* fix(remote): refresh settings row health from store

* fix(remote): refresh settings row health from store

* fix(remote): clear diagnostics generations in tests

* fix(settings): refresh runtime availability summary

* refactor(runtime): split status slice types

* refactor(runtime): reuse status app state type

---------

Co-authored-by: Merge Sim <sim@local>
2026-08-31 17:37:53 -07:00
Jinjing 45c4823109 Format documentation with consistent line wrapping and table alignment (#17765)
Standardize MDX files across docs with:
- Remove trailing semicolons from import statements
- Wrap long lines and multi-line component props for readability
- Align Markdown table column separators
- Normalize text and JSX formatting for consistency
2026-08-31 17:24:35 -07:00
Neil fa0180dc61 perf(renderer): avoid combined-diff tree rebuilds during progressive loads (#17643)
* perf(renderer): avoid combined-diff tree rebuilds during progressive loads

* fix(renderer): preserve collapsed combined-diff tree boundaries

* perf(renderer): skip unfiltered combined-diff flatten when hiding viewed files

* fix(renderer): keep reordered viewed keys in the combined-diff delta

The incremental viewedSectionKeys delta walked indices issuing a delete
then an add, so a key added at index i and deleted as the previous key at
a later index was silently dropped. Fall back to a full recompute when any
index's key differs; the progressive-load fast path (stable keys, flipping
loading state) is unchanged.
2026-08-31 17:14:34 -07:00
Jinjing f546f53a4e docs: update Android APK link to 0.0.47 (#17764)
Update the README download links to the latest mobile Android release.
2026-08-31 17:03:39 -07:00
Neil c558d7e083 Activate terminal splits before inherited CWD resolution (#17601)
* perf(terminal): activate splits before cwd resolution

* test(terminal): prove split focus before cwd publish

* fix(terminal): release stale split cwd fence

* test(terminal): add visible split activation latency benchmark

* docs(reliability): clarify split benchmark provenance

* fix: preserve deferred split handoffs across remounts

* fix: fence late deferred split closes

* docs(reliability): record exact split benchmark runs

* test(reliability): fail benchmark on artifact write errors

* test(reliability): attribute split activation phases

* docs(reliability): record schema-v2 split benchmark

* refactor(terminal): collapse duplicated split-handoff and write-queue paths

- Drop the discardDeferredSplitPaneHandoff alias for its identical clear twin.
- Fold the deferred-cwd resolve/reject settle handlers into one applier.
- Extract settlePaneCwdDeferredSpawn for the repeated read-clear-write pattern.
- Share one head-index FIFO primitive between the ordinary and reply queues.

* fix(terminal): stop retaining a promise reaction per acknowledged write

Racing every accepted write against one queue-lifetime cancel promise kept a
reaction record alive until that promise settled: 200k acknowledged writes
retained 88.6MB, now 0.1MB. Give each in-flight write its own cancel, and
split the shared FIFO primitive into its own module.

Also sanitize the split-latency benchmark report at its single serialization
point so shared artifacts no longer carry the machine-local repo path or
unbounded cleanup error text.

* fix(terminal): settle deferred split input when the spawn is abandoned

An abandoned deferred spawn returns before transport.connect(), so nothing
drained the pre-connect buffer: sendInputAccepted's promise never settled and
a paste into that pane hung forever. Clear the buffer on the abandon fence.

Also re-derive the pre-connect retention cap from the clipboard-paste ceiling
rather than the 16MB single-write ceiling; it is held twice per pane across up
to 64 deferred splits, so 5.59M code units guarded the wrong thing.

* fix(terminal): release the deferred cwd fence on a rejected reattach

A daemon createOrAttach can turn an apparent fresh spawn into a reattach; when
that reattach is refused the spawn ends with deferredSplitSpawn/pendingCwd
still set, permanently arming the pre-bind detach refusal. The release no-ops
when a PTY did bind, so it only fires where the fence would otherwise leak.

The stale-generation return above is deliberately left alone: a newer connect
already owns the pane there, and the fence is not generation-scoped.
2026-08-31 16:45:36 -07:00