Commit Graph
7702 Commits
Author SHA1 Message Date
Neil 074f135ee1 style(renderer): format extracted palette modules 2026-09-01 00:06:23 -07:00
Neil 0c638b0fe5 fix(renderer): preserve palette create telemetry
(cherry picked from commit b13c2b1960)
2026-09-01 00:06:23 -07:00
Neil aad0c632a7 fix(renderer): preserve Codex sign-in behavior
(cherry picked from commit 81f4803e6f)
2026-09-01 00:06:23 -07:00
Neil 936595a8e5 fix(automations): preserve destination form save semantics
(cherry picked from commit a793b07c12)
2026-09-01 00:06:23 -07:00
Neil e897acc471 fix(renderer): preserve floating editor visibility
(cherry picked from commit bcf715b76e)
2026-09-01 00:06:23 -07:00
Neil 01d6725be3 fix(renderer): preserve status localization parity
(cherry picked from commit 0a802c70e1)
2026-09-01 00:06:23 -07:00
Neil bae9ea8c79 fix(renderer): document intentional render-time refs
(cherry picked from commit 6fc78dd869)
2026-09-01 00:06:23 -07:00
Neil 6848a6958c refactor(renderer): finish worktree palette extraction
(cherry picked from commit ea8e6e595a)
2026-09-01 00:06:23 -07:00
Neil a8d9acb3f6 style(renderer): format palette project candidates
(cherry picked from commit 1e93d66854)
2026-09-01 00:06:23 -07:00
Neil 29c760726b fix(renderer): restore worktree palette behavior after split
(cherry picked from commit 0b78c1cbbc)
2026-09-01 00:06:23 -07:00
Neil 3e262c1fc6 fix(automations): preserve Escape drill-out behavior
(cherry picked from commit 790680dfc2)
2026-09-01 00:06:23 -07:00
Neil 3fe63b5104 fix(status-bar): preserve workspace-space review and agent freshness
(cherry picked from commit 9455e318fa)
2026-09-01 00:06:23 -07:00
Neil b94a65a4fc fix(lint): preserve TaskPage effect suppressions after split
(cherry picked from commit 4a3bc23670)
2026-09-01 00:06:23 -07:00
Neil cd986c7759 fix(status-bar): preserve Git refresh ordering after split
(cherry picked from commit e65d298afb)
2026-09-01 00:06:23 -07:00
Neil 3349a460e2 fix(renderer): preserve extracted lifecycle and retention behavior
(cherry picked from commit 1731f2a2f3)
2026-09-01 00:06:23 -07:00
Neil 7123146ac2 fix(renderer): keep split imports lint-clean
(cherry picked from commit c778ac7a7a)
2026-09-01 00:06:23 -07:00
Neil 286c21005b refactor(renderer): split oversized UI surfaces
(cherry picked from commit da89be4345)
2026-09-01 00:06:23 -07:00
Jinjing e7f15367a8 fix(agent-send-target): acknowledge delivery even when picker closes bef (#17835)
Delivery callbacks and telemetry belong to the completed send operation, not to the
picker instance that launched it. Remove early returns that skipped delivery
acknowledgment and success toast when the popover was already closed.
2026-08-31 23:51:32 -07:00
Brennan BensonandMerge Sim d4db524ba7 fix(native-chat): stop unjournaled provider frames from killing the session (#17813)
A frame the classifier declines (status-chrome, suppressed-benign, stream-into-item)
is deliberately not journaled. #17720 turned that null translation into
`{accepted: false, reason: 'untranslated'}`, which is not `backpressure`, so the
notification retry queue treated it as unreplayable and escalated through fail() ->
forceCloseUnexpected -> connection.close(). The app-server latched `closing` and the
create path's next model/list rejected with "codex app-server connection is closed
(model/list)".

The provider emits `remoteControl/status/changed` right after initialize, so every
structured Codex session died on its first chrome frame. Admit the null translation
instead, before any bookkeeping or publish.

Also restore the error-frame exemption from the generic row cap, dropped by the same
PR: the cap now runs after the classification check, so a noisy turn can no longer
reduce provider errors to a suppression count. The test that pinned the capped
behavior is inverted to assert the exemption.

Co-authored-by: Merge Sim <sim@local>
2026-08-31 23:31:27 -07:00
Brennan BensonandMerge Sim 51bc2ec343 fix(native-chat): keep the attachments on a Claude turn that pasted images (#17801)
* fix(native-chat): keep the attachments on a Claude turn that pasted images

A Claude turn carrying pasted images reached native chat with no images at all —
no thumbnails on mobile, and not even an attachment chip on desktop. Nothing
showed that the message had any.

Both carriers were being dropped:

- Claude records the paths in a companion turn marked `isMeta`, holding one
  `[Image: source: <path>]` text block per image. The decoder treats an `isMeta`
  user row as injected, filters it down to tool-result blocks, and returns null
  when none remain — so the whole row went away.
- The prompt row's own `image` blocks are `{source: {type: 'base64'}}`, which
  carry no url or path, so `imageRefBlock` drops them too.

With the companion gone, `isImageSourceUserTurn` could never fire and the fold in
`normalizeImageTranscriptMessages` was unreachable on the Claude path.

Surveying every transcript under `~/.claude/projects`: 238 of 241 image-source
rows are `isMeta`, across every versioned release (2.1.220 through 2.1.237); the
3 that are not carry no version field at all. 38 of those rows hold more than one
content block, which also defeated the single-block rule in
`isImageSourceUserTurn`.

Let image-source text survive the injected-turn filter, and recognize a turn
whose blocks are *all* markers rather than only a lone one. An ordinary injected
turn (a skill preamble, a compact summary) is still dropped, and a turn that
mixes prose with a marker is still not an image-source turn.

Carrying the paths keeps the payload small; decoding the base64 instead would put
hundreds of KB per image on the wire to mobile.

* fix(native-chat): preserve image companion ordering

* fix(native-chat): keep image companions turn-local

---------

Co-authored-by: Merge Sim <sim@local>
2026-08-31 23:27:56 -07:00
Brennan BensonandMerge Sim a9e6fb7eff fix(native-chat): stop rendering tool output as the agent's streaming reply (#17782)
* fix(native-chat): stop rendering tool output as the agent's streaming reply

A tool result could appear in native chat as a raw, un-collapsed "assistant"
bubble that never went away for the rest of the turn — on mobile it showed up
as a wall of a source file's contents, prefixed by "Exit code 1".

Providers publish a tool's stdout/error as `lastAssistantMessage` so status
cards and dashboard rows can preview what the agent just did. Native chat reuses
that same field as its live streaming bubble, so the preview rendered as prose.
For Claude the preview is *only ever* tool output mid-turn: claude-tool-fields
writes real prose exclusively at Stop, so the bubble could never contain an
actual streaming reply.

It also could not be retired. The bubble hides once a transcript assistant block
leads with the streamed text, and tool output never lands in one — so the only
remaining exit was the turn ending, which is why a long tool-heavy turn pinned it
on screen.

Carry provenance instead of changing what the status surfaces show: mark the
writes that come from a tool result/error, keep the flag in lockstep with the
value it describes through the listener merge, and have both native-chat
streaming paths ignore a flagged preview. Status cards, dashboard rows and
automation capture are untouched.

The wire field is optional, so an older host that never sends it keeps today's
behavior rather than silently suppressing previews.

* fix(native-chat): preserve tool output provenance through renderer sync

* fix(native-chat): retain preview provenance in Claude roster state

* test(native-chat): cover restored tool preview provenance

---------

Co-authored-by: Merge Sim <sim@local>
2026-08-31 23:05:31 -07:00
Neil 9bc564c2aa fix(cleanup): follow WSL-written gitdir pointers on a Windows host (#17806)
`readLocalWorktreeGitDir` resolved a linked worktree's `.git` gitfile
pointer by hand, translating a POSIX-rooted pointer only when the
worktree path was itself `\\wsl.localhost\...`. On a Windows host
`path.isAbsolute('/mnt/c/repo/.git/worktrees/wt')` is true, so a
drive-path worktree (`C:\Users\me\wt`) whose gitfile was written by git
running inside WSL kept the guest spelling and was joined to
`\mnt\c\repo\.git\worktrees\wt\HEAD`. All four probes (HEAD,
COMMIT_EDITMSG, ORIG_HEAD, tail of logs/HEAD) missed, so the row's
lastActivityAt fell back to the worktree directory mtime and a worktree
with recent commits could read as stale in the cleanup browser.

Delegate to `resolveGitMetadataPath` (src/shared/git-metadata-path.ts,
landed in 7f63db7d7a, already used by repo-git-marker-scan.ts). Five
lines out, one in; the shared resolver is not modified.

Delta, enumerated over 10 base x 18 pointer x 3 platform combinations
(540 pairs) against a reimplementation of the removed branch: 28 differ,
every one win32 + non-UNC base + `/mnt/<lowercase-letter>` pointer.

- WSL-on-Windows: a drive-path worktree with a WSL-written pointer now
  probes its real git metadata.
- WSL UNC worktrees, native Windows, macOS, Linux: no change (0 deltas
  on darwin/linux, 0 for any `\\wsl.localhost\...` base).
- SSH / relay / folder workspaces: no change; remote repos return the
  persisted timestamp before any probe, and a folder workspace has no
  gitfile pointer.

Not strictly monotone: the old probe target `\mnt\c\...` is a real
drive-relative location, so if it existed with a newer mtime than the
genuine gitdir this lowers lastActivityAt for that row. The persisted
timestamp stays a floor via Math.max, and in every realistic case the
change only raises the value.

No signature changes, no options threading, no new call sites.
2026-08-31 22:37:34 -07:00
Neil d7d3114716 perf(wsl): single-flight the async WSL distro list (#17805)
On Windows, seven production call sites reach listWslDistrosAsync and on a cold
cache each spawned its own `wsl.exe --list --quiet` (5s timeout each): the
wsl:listDistros IPC behind the renderer capability read, the host.wsl.listDistros
RPC, the skill-install IPC, CLI registration reconciliation, the hook relay deps,
the kimi runtime home, plus relay preflight in the relay process. Concurrent
callers in one process now share one spawn.

Joining happens ahead of the negative cache, which also fixes a stranding bug: a
synchronous listWslDistros() landing an empty result mid-probe arms the 15s retry
window, and later async callers read that [] even though the pending probe is
about to see a distro that just finished provisioning. The non-empty-cache
short-circuit sits ahead of the join so a list already found synchronously is
still returned without waiting; that is main's existing behaviour preserved, not
a new fast path.

The shared promise cannot reject -- `catch` sits ahead of the stored promise, so
joiners get the same fail-safe [] the old per-caller catch returned -- and the
slot is cleared on settle, by the owning probe only.

wsl-directory-probe-command.ts is a verbatim move of the guest directory-probe
marker protocol and its parser out of wsl.ts, for oxlint max-lines headroom:
inlining it back makes wsl.ts 306 effective lines against a cap of 300. It takes
WslUncPathInfo from ../shared/wsl-paths -- the actual type of every value passed
at both call sites -- so it does not import from wsl.ts. _resetWslCachesForTests
and _setWslCachesForTests now share one resetWslDistroListState() instead of
repeating the same six assignments.

Per-platform delta:
- WSL on Windows: fewer wsl.exe spawns under startup fan-out, and a distro
  provisioned while a probe is pending is no longer hidden for the retry window.
- Native Windows without WSL: no behavioural change. The empty/failure retry
  windows, their backoff and the cache sequence guard are unchanged; N concurrent
  callers now cost one failed spawn instead of N.
- macOS, Linux, folder workspaces: no change. Both new early returns are
  unreachable off win32.
- SSH remote: no change for macOS/Linux hosts; a remote Windows host gets the
  Windows behaviour in its own process. No wire change -- host.wsl.listDistros
  keeps its string[] shape and its [] failure value.
- Relay: same single-flight inside the relay process. It stays per-process; the
  relay and main process still probe independently, as before.

Costs: a never-settling execFileUtf8 now pins the shared slot for the process
lifetime rather than only its own callers -- transient-to-permanent, not identical
exposure. And a joiner inherits the first probe's failure instead of making an
independent attempt.
2026-08-31 22:37:23 -07:00
Neil a9babde9a3 refactor(git): accept a caller-named WSL distro on the metadata path resolver (#17804)
Two small changes to the Git metadata read path. Neither has a user-visible
effect on any platform except for a malformed `.git` gitfile, described below.

1. resolveGitMetadataPath's third parameter becomes an options object
   `{ platform?, wslDistro? }`. A caller that knows which distro wrote a pointer
   can now say so, where previously only a WSL UNC base path could. The distro
   encoded in the base path still outranks the caller's, and translation only
   happens when the reading host is win32, so a caller-named distro cannot make
   a POSIX host fabricate a Windows path. The UNC-base branch is exempt from
   that gate because that spelling only exists on Windows. Main's other
   contracts are verbatim: never null for a non-empty pointer, and a drvfs
   pointer keeps its drive spelling even when a distro is named. Both production
   call sites (repo-git-marker-scan.ts) pass no options, so they are unchanged.

2. The `.git` gitfile marker parse moves into one shared function,
   parseGitdirMarkerPayload: `gitdir:` at the start of the file, payload
   trimmed, empty payload rejected — git's own read_gitfile_gently rule.
   resolve-git-dir.ts and repo-git-marker-scan.ts both call it; the latter had a
   near-identical private copy and is behaviorally identical after the swap
   (verified across twelve marker spellings; the only divergence, a
   whitespace-only payload, already resolved to null one call further down).
   Main's `/^gitdir:\s*(.+)\s*$/m` in resolve-git-dir captured trailing padding
   into the path and honored a `gitdir:` line anywhere in the file.

Per-platform delta: none on macOS, Linux, native Windows, WSL, SSH, relay, or
folder workspaces. The wslDistro option is inert; this change adds no caller.
For a malformed `.git` gitfile, padding is now stripped (strict improvement), a
whitespace-only payload falls back to `<worktree>/.git`, and a `gitdir:` line
that is not the first line is no longer honored — a narrowing, since main could
return a working gitdir there. All four resolveGitDir consumers already degrade
through a catch, so that case reports no sparse state / conflict operation /
diff stamp rather than failing.

Six other hand-rolled `gitdir:` parsers remain, including the relay's SSH copy;
converging them is its own change.
2026-08-31 22:37:09 -07:00
Maxon PhongandJinjing 05b31d6e92 fix(i18n): correct zh-CN translation for editor view toggle buttons (#13723)
* fix(i18n): correct zh-CN translation for editor view toggle buttons

- "Rich Editor" (aff15f94f5): 丰富的编辑器 → 富文本编辑器
- "Source" (4d6ccb7ba6): 来源 → 源码
- Settings description (f80603d293): 丰富的编辑器 → 富文本编辑器

"Source" in the Markdown editor context means source-code view, not
data source. "丰富的编辑器" is an awkward literal translation; the
standard term is "富文本编辑器", already used inconsistently in
nearby keys (5f02e6fb21, 8090:694613d47f).

* fix translation

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-08-31 21:49:58 -07:00
Neil 28373fcea7 fix(windows): repair the install-dir package ACL that blanks the window (#17740)
* fix(windows): repair the install-dir package ACL that blanks the window

An install tree carrying an orphan AppContainer ACE (S-1-15-2-<x>) with no
ALL RESTRICTED APPLICATION PACKAGES grant denies Chromium's LPAC children read
on the shipped modules; they die at init with 0x80000003 and the window stays
blank forever (electron/electron#51761).

- Tighten the probe verdict to require the S-1-15-2-2 grant specifically: an
  ALL APPLICATION PACKAGES (S-1-15-2-1) ACE, the Program Files default, does
  not appear in an LPAC token and cannot satisfy the orphan.
- Drop BUILTIN from the English-locale heuristic (fr-FR/es-ES print it
  verbatim) so a localized icacls is correctly reported as un-name-checkable.
- Add an additive, marker-guarded icacls self-repair: an inheritable root
  grant plus a flagless (RX) /T pass, never /grant:r.
- Route the crash-loop dialog through a testable prompt module that names the
  permission cause, offers Copy Commands without dismissing itself, and keeps
  the graphics-driver hint.

The repair only runs on win32, off serve mode, and only on the exact probe
verdict that reproduced the crash.

* docs(windows): correct the install-tree ACL walk cost model

* fix(windows): keep the install-ACL poison gate at the reproduced shape

An orphan package ACE alongside the Program Files ALL APPLICATION
PACKAGES default launches clean on win32 10.0.26200 / Electron 43.4.1,
so requiring S-1-15-2-2 specifically declared poison on healthy installs
- and this branch acts on that verdict with a tree-wide icacls write and
the crash-recovery dialog's primary cause. hasRestrictedPackageGrant
stays reported for triage; only the verdict reverts.
2026-08-31 21:15:19 -07:00
Neil e6257b6e32 perf(worktree): resolve the WSL workspace root off the main thread when preparing (#17792)
`computeWorkspaceRoot` resolves a WSL repo's mirror root through `getWslHome`,
which is a synchronous `execFileSync('wsl.exe', ...)` with a 5s timeout. Two
worktree preparation paths ran it on the Electron main thread:
`prepareLocalWorktreeRootForRepo` (repo registration, clone completion, repo
update, project host setup, folder->git upgrade) and `prepareWorktreeCreateForRepo`
(the speculative checkout started while the create composer is open). On a stopped
or cold distro that froze every window for up to 5s. Being fire-and-forget did not
help: only 3 of the 16 `prepareLocalWorktreeRootForRepo` call sites are `void`-ed,
the other 13 are awaited inside IPC handlers, and the sync probe blocks the main
thread either way. `prepareLocalWorktreeRootsForRepos` runs the same probe for
every repo from the settings-save handler.

Adopt the existing `computeWorkspaceRootAsync` (now exported) at those two call
sites, and give the two resolvers a shared mirror-distro decision and shared
root-from-home layout so they cannot drift apart.

Also thread the mirror distro into the prepare-side path settings.
`createLocalWorktree` passes `getWorktreeMirrorDistro(store, repo)` and
`prepareWorktreeCreateForRepo` did not, so a `C:\` repo on a WSL project runtime
prepared under `C:\workspaces` while the create click looked under the mirrored
WSL root: the keys never matched and every prepared checkout was discarded, after
paying for a full checkout that sat until the 5min TTL. Pre-existing on main;
included because it is the same line and the same resolver.

Scope of the win, stated precisely: only those two preparation paths stop
blocking. On a reachable distro `getWslHome` caches on success, so before this
change the first repo paid one blocking probe and the rest were cache hits -- the
change makes that one probe non-blocking, it does not remove N probes. Failed
probes are never cached, so on a stopped distro N repos did pay N sequential 5s
blocking probes and now share one in-flight async probe.

Costs: five sync `computeWorkspaceRoot` callers remain (allowed-roots resolution,
the create click in worktree-remote, CLI create, watch targets, worktree trash),
and `getWslHome` reads only `wslHomeCache` -- it cannot join an in-flight async
probe. The guaranteed synchronous cache warm-up therefore becomes a window in
which one of those callers can still block and can spawn a second concurrent
`wsl.exe`. Concretely: opening the create composer and clicking Create within a
few hundred ms on a cold distro now pays the freeze on the click instead of on the
background prep. Separately, the mirror-distro fix makes prepare spawn an async
`wsl.exe` home probe for `C:\` repos on a WSL runtime, where it previously spawned
none.

No race added: `prepareWorktreeCreateForRepo` computes the preparation key and
inserts the registry entry in one synchronous run after the await, so two
concurrent creates still dedupe to a single prepared checkout.

`worktree-create-preparation-wsl-root.test.ts` runs the real resolver through
prepare and then claims the entry with the production consume-side call shape
(including the mirror distro), so a divergence between the two resolvers fails a
test instead of silently discarding every prepared checkout.
2026-08-31 21:11:09 -07:00
Neil 5c831c1846 fix(crash-reporting): record Linux MemAvailable at process-gone (#17733)
Linux process-gone crash reports emitted only systemMemoryFreeMB, which is
/proc/meminfo MemFree — it excludes page cache and other reclaimable memory, so
an OOM-killed renderer could report gigabytes "free" and hide the pressure that
caused the kill. Electron 43 exposes MemAvailable as `available` on Linux, and
getSystemMemoryAtGoneDetails already had the getSystemMemoryInfo() result in
hand, so emit it as systemMemoryAvailableMB through the existing field table.

The field is omitted on macOS/Windows (where Electron does not report it) and
on a non-finite reading, matching every other bucket.
2026-08-31 21:02:19 -07:00
Neil ed496317f0 perf(renderer): fetch host lineage once per connect, narrow sidebar ack subscriptions (#17761)
* perf(renderer): bound runtime refresh and sidebar subscriptions

* perf(renderer): widen interactive connect to 15 concurrent probes

The 5-wide bound came from the coalesced background event lane, where repo
events repeat and can storm. Connect is one-shot and the user is waiting on
it, so it gets its own wider lane while still capping fan-out on the remote,
which runs a worktree-detection RPC per repo.
2026-08-31 20:41:17 -07:00
Neil a4cd00ed18 fix(wsl): drop the linked-worktree Git route cache after worktree mutations (#17791)
On Windows with a WSL distro configured, `prepareWslLinkedWorktreeGitRouting`
caches for 30s which Git owns a drive-letter checkout, by reading that checkout's
`.git` marker. `git worktree add/move/remove` rewrites exactly that marker, so
the verdict could stay authoritative for up to 30s after it stopped being true.

- Add `invalidateWslLinkedWorktreeGitRouting(cwd)`: drops the cached route and
  the probe retry backoff for that path and for anything under it (a submodule
  inside the worktree derived its route from the same marker walk). Eight calls
  at six sites: `worktree add`, `worktree move` (both paths), `worktree remove`,
  the prepared checkout's add, the finalize move (both paths), and the
  prepared-worktree discard. Five sites invalidate from a `finally`, because a
  Git failure can still have rewritten the marker; the prepared checkout's add
  invalidates on the success path only, since its failure path runs the discard,
  which has its own `finally`.
- Split the parent-directory marker walk into
  `wsl-linked-worktree-git-route-probe.ts` (the routing module was at the
  `max-lines` ceiling) and have it report whether the walk settled. A `.git`
  file with no `gitdir:` line is a half-written marker mid `worktree add`: it is
  now retried under the existing backoff instead of cached for 30s. The route it
  yields is unchanged (distro); only the number of parent walks changes.

An invalidation only drops cached state; a probe already in flight is left to
finish and cache normally, so a mutation landing mid-probe is no worse off than
main's 30s TTL. The cost is that a failed mutation also drops a still-correct
route: `gitExecFileAsync` and `gitStreamStdout` re-resolve the route after their
own `prepareWslLinkedWorktreeGitRouting`, and `gitSpawn` resolves again after the
git-admission wait, so a command already in flight for that path can take the
empty-cache default and run a host-owned checkout under `wsl.exe git`. It fails
once and self-heals on the next command.

Reachable only on win32 + configured WSL distro + drive-letter cwd; every other
platform and configuration never populates this cache, so the new calls scan two
empty maps.
2026-08-31 20:35:49 -07:00
Neil abc099e4c7 fix(worktree): run the create-base warm-up on the routed git host (#17794)
The speculative warm-up that runs while the create composer is open resolved
refs and fetched with host Git even when the project's runtime is a WSL distro,
while both the checkout preparation it feeds (`prepareWorktreeCreateForRepo`,
which already resolves `{ wslDistro }` itself) and the real create path run
inside the distro.

The concrete cost was a discarded fetch: `getCanonicalFetchKey` namespaces the
runtime's remote-fetch cache `wsl:<distro>` vs `local`, so the warm-up's fetch
landed in a namespace create never looks at, and create fetched again. On a
Windows host with no usable host-side Git the probes also failed outright, so
that cohort got no warm-up at all.

Thread the project's worktree Git options through the prefetch (resolved by a
non-throwing helper, because an optimistic warm-up must not surface a
repair-required runtime as a failure) so every probe and fetch runs where create
runs. `gitOptions` is a required argument, so a caller cannot drop the routing
silently. Host-routed calls keep their original arity, so macOS, Linux,
native-Windows-host projects, SSH repos and folder workspaces are unchanged.

Narrower than it looks: for a repo under \\wsl.localhost\<distro>\... the probes
were already routed by cwd, and for a repo on a Windows drive letter host Git
and WSL Git read the same on-disk repository, so the answers were already
correct there. What those cohorts gain is a fetch create can reuse; what they
pay is that the probes now run inside the distro (over /mnt/c for drive-letter
repos, which also newly arms the linked-worktree routing probe) and the
speculative fetch now shares create's per-remote fetch queue, as it always has
on native platforms.

Also collapse the three byte-equivalent copies of `hasLocalWorktreeBaseRef`
(create, prefetch, remote-repo create) into one in
git/worktree-base-ref-probe.ts, drop the host-only `hasLocalCommitObject` that
caused the routing bug, and add the first routing assertions on the create-path
consumers of the now-shared probe.
2026-08-31 20:32:59 -07:00
NeilandNeil 7f63db7d7a fix(git): resolve WSL drvfs Git metadata pointers on a Windows host (#17790)
When Orca's runtime is a WSL distro but the repo sits on a Windows drive, git
inside the distro writes `/mnt/c/...` into a worktree's `.git` gitfile and its
`commondir`, while Orca reads those files back through Win32.
`repo-git-marker-scan` returned the pointer verbatim, Windows read it as
drive-relative `C:\mnt\c\...`, and the worktree was reported `invalid`.

Move that resolver out of `repo-git-marker-scan` into
`src/shared/git-metadata-path.ts` and give it exactly one new case: on win32, a
drvfs pointer resolved against a base path that is not a WSL UNC path now gets
its drive spelling. Every other base/pointer/platform combination is
byte-identical to the deleted helper, verified differentially across a
base x pointer x platform matrix — macOS, Linux and native Windows are unchanged.

`toWindowsWslDrivePath` is factored out of `toWindowsWslPath` so the drvfs
matcher has one home; `toWindowsWslPath` itself is unchanged for all inputs,
including the line terminators JS `.` excludes (fuzzed 2M inputs, 0 divergences).

This changes the marker scan's verdict only. `resolve-git-dir.ts` and the relay's
own copy still `path.resolve` the same `/mnt/c/...` pointer in the Win32
namespace, so a worktree that is now accepted still degrades quietly in conflict
detection, sparse-checkout detection, the diff stamp and worktree listing. Those
parsers are deliberately untouched here; see the PR description.

Co-authored-by: Neil <neil@example.com>
2026-08-31 20:32:46 -07:00
Neil 8b2d72114b fix(gitlab): stop the native glab known-hosts probe waking an idle WSL distro (#17789)
On Windows with no host `glab.exe`, the cwd-less `glab auth status` known-hosts
probe fell through to `wsl.exe -d <default distro>`. Probe failures are never
cached, so when that WSL leg also fails (glab absent or logged out inside the
distro) every forge detection re-booted the distro; when it succeeds it cached
that distro's auth hosts under the 'native' execution key, which the comment two
lines above the call already forbids. gitlab-auth-and-rate-limit.ts already
passes allowDefaultWslFallback: false for this exact command; the known-hosts
probe now agrees with it.

Connection-keyed probes keep the fallback: glab has no SSH/relay dispatch, so the
`glab api` calls this gates run the same local CLI with no cwd and would
otherwise disagree with the probe. wsl:<distro>-keyed probes were already
unreachable by the fallback, so passing the flag there is inert.

Counted child_process.execFile calls over 3 sequential probes, process.platform
forced to 'win32', host glab mocked ENOENT (wsl.exe / glab.exe spawns):
  native key, glab absent in the distro too: 3/3 -> 0/3
  native key, glab logged in in the distro:  1/1 -> 0/3, and that distro's
    self-hosted hosts stop reaching the native known-hosts list
  connection key:                            1/1 -> 1/1, unchanged

Residual risk on that second config: a repo on a \\wsl$\ UNC path can be keyed
'native' (no project runtime match) while its own glab calls still route into
WSL by cwd, so it loses the seeded host and must re-derive it through
`glab auth status --hostname`. A co-resident Windows-path repo on the same host
can now write a shared `native\0<host>` unauthenticated negative that stalls that
recovery for one NEGATIVE_ENTRY_TTL_MS window. Fixing that properly means keying
the cache by the host that actually served the call, which needs an exec-layer
API change and is deliberately out of scope here.

Also splits the getGlabKnownHosts suite out of gl-utils.test.ts (796 counted
lines against the 800-line cap for tests) into gitlab-known-host-probe.test.ts.
2026-08-31 20:32:35 -07:00
NeilandNeil ad760c8b92 fix(worktree): reject Windows drive-qualified shared paths in the symlink guard (#17793)
getSafeRelativePath strips leading `/` and `\` before testing absoluteness, so
the only rooted spelling that can still reach the guard is a Windows drive
designator. It tested that with the host `path.isAbsolute`, which left two gaps:
the drive-absolute form `C:/payload` was refused on Windows but admitted as an
ordinary relative filename on macOS/Linux, and the drive-RELATIVE form
`C:payload` was admitted on every host including Windows, where
`win32.resolve(root, 'C:payload')` discards the worktree root and lands under
C:'s current directory. That holds for a drive root (`D:\wt`) and for the
`\\wsl.localhost\<Distro>\...` UNC root a WSL project uses, both verified.

The value reaches the guard from two configs: the per-user Worktree Shared Paths
setting alone on the create path (createWorktreeLinkedPaths, called with
`repo.symlinkPaths` from orca-runtime.ts:27820 and worktree-remote.ts:2636), and
that setting merged with the repo's checked-in `orca.yaml`
`worktree.sharedDirectories` on the removal and detection paths
(getWorktreeSharedLinkPaths). No repo config is required to reach it.

Replace both `isAbsolute` calls with a `/^[a-zA-Z]:/` test, verified by fuzz to
be a strict superset of `posix.isAbsolute || win32.isAbsolute` for every
post-strip input. No filesystem escape is closed on macOS/Linux, where such an
entry resolves to a literal in-worktree filename.

Cost: on POSIX, `:` is a legal filename character, so a shared/linked path whose
first segment is `<letter>:...` is now refused where it previously worked — it
stops being created, and if a worktree already holds an Orca-created symlink
there it stops being excluded from the untracked-file filters in all four
findExistingWorktreeSymlinkPaths callers, which means a refused non-force
worktree removal (remove-registered-local-worktree.ts:91, orca-runtime.ts:30205),
a phantom untracked row in Source Control (status-read.ts:90), and a blocked
hosted-review creation (hosted-review-creation-git-state.ts:290) — and
removeWorktreeLinkedPaths no longer unlinks it, so nothing cleans it up.
Accepted because a per-host verdict would defeat the point of judging the same
config identically on every host it is evaluated on.

Co-authored-by: Neil <neil@example.com>
2026-08-31 20:32:23 -07:00
Neil e4f77f7d13 perf(renderer): collapse duplicate reveal atlas rebuilds (#17762) 2026-08-31 20:16:42 -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 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
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
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