Commit Graph
559 Commits
Author SHA1 Message Date
Brennan Benson c8a67dcf3b fix(native-chat): retain cold TUI proof authority 2026-08-11 22:17:56 -07:00
Brennan Benson eac2520f42 fix(native-chat): recover claimed TUI owners 2026-08-11 22:10:24 -07:00
Brennan Benson ff35abf7a8 fix(native-chat): recover TUI transcript gaps 2026-08-11 21:56:01 -07:00
Brennan Benson 7316ac3926 fix(native-chat): complete mobile reverse after TUI exit 2026-08-11 20:35:12 -07:00
Brennan Benson c0f6e427d3 fix(native-chat): make reverse handoff transactional 2026-08-11 15:01:52 -07:00
Brennan Benson 34635aed0f fix: align structured TUI return readiness 2026-08-11 06:45:21 -07:00
Brennan Benson 9d8857c20a fix: complete structured chat round trips 2026-08-11 06:12:59 -07:00
Brennan Benson 6bc01085d0 fix: bind structured TUI before rollout proof 2026-08-11 05:20:06 -07:00
Brennan Benson 1ab28b5fc0 fix: harden structured session handoffs 2026-08-11 04:24:54 -07:00
Brennan Benson 827e4a1c6c feat: toggle structured sessions between chat and TUI 2026-08-11 03:01:11 -07:00
Brennan Benson 07af7c9285 Merge remote-tracking branch 'origin/main' into brennanb2025/codex-structured-mobile-write 2026-08-10 23:37:18 -07:00
Jinjing c1e75477f3 Fix static analysis page stuck in loading state (#13674)
* Fix static analysis page stuck in loading state

- Bound check-details requests with 30s timeout, matching remote RPC budget
- Track request IDs to discard stale responses when context changes
- Propagate githubRepository through store and components for proper routing
- Add retry button for failed check-details loads
- Improve accessibility with ARIA labels for loading and error states

* Fix static analysis page stuck in loading state

When an open check-details tab's repository is removed, the loading
state would continue indefinitely because the fetch was still being
triggered. Prevent the fetch call in this scenario to unblock the UI.
Also migrates translation keys to obfuscated identifiers.

* Fix static analysis page stuck in loading state

Add deadline-based timeouts and request ID tracking to prevent stale responses
from freezing the checks panel. Include abort signal propagation throughout the
request chain and provide retry UI for failed check details loads.

* fix(checks): prevent loading state from getting stuck on retry

- Consolidate mount checks into a helper function
- Details now clear when a new request begins
- Add i18n strings for retry status
2026-08-10 23:11:45 -07:00
Neil ee7e743813 perf(orchestration): skip redundant federation acknowledgments (#13671) 2026-08-10 22:51:33 -07:00
Brennan Benson 75b3a209e6 Merge remote-tracking branch 'origin/main' into brennanb2025/codex-structured-mobile-write 2026-08-10 22:03:55 -07:00
Jinwoo Hong 550eabe921 feat(workspaces): review preserved branches after bulk delete (#13693) 2026-08-10 18:37:45 -07:00
Neil fc7387b28b perf(runtime): index PTY worktree inventory lookups (#13690) 2026-08-10 18:32:04 -07:00
Brennan Benson f2984e2230 fix(terminal): skip a too-wide alt frame on snapshot replay (#13014)
* fix(terminal): skip a too-wide alt frame on snapshot replay

Reopening a parked worktree could paint a stale full-width TUI frame
through a narrower viewport, leaving clipped gutter fragments and
mid-word omissions until the live application repainted.

Replay pins xterm to the snapshot grid so soft-wrapped normal-buffer
history stays exact (#7279), then the post-replay fit returns the pane
to its container grid. Alternate buffers have no scrollback and do not
reflow; an absolutely positioned frame remains at its capture layout,
so narrowing exposes only clipped portions of those fixed-grid rows.

Skip only the visual frame when its capture is wider than the grid the
fit will land on. The alt buffer is still entered and cleared, so the
resize signal lands on a clean screen that the live application can
repaint. Equal-width and wider restores retain the frame, while normal
history always replays at its capture grid before fitting.

The target width comes from proposeDimensions, not terminal.cols: an
unfitted pane can still read xterm's default grid even when its actual
container matches the capture.

* fix(terminal): preserve offline SSH prepaint frame

* fix(terminal): drop a too-wide daemon alt frame on reattach

The renderer-only gate did not run on the user-visible remount path.
Instrumentation showed that daemon-connectResult-snapshot won before
the model-snapshot branches, so the composed snapshot painted in full
and the later narrower fit exposed its stale fixed-grid frame clipped
at the new viewport.

The daemon branch could not omit only the visual frame while main sent
one merged string. Publish the normal-buffer/mode prefix and visual alt
frame as additive optional metadata while retaining the merged snapshot
for mixed-version fallback. New renderers can keep history and restore
state without painting a frame captured for a wider grid.

Replay ordering remains capture-grid, write, then fit so normal-buffer
soft wrapping stays exact. The application owns the foreign-width alt
frame and repaints it after the resize signal rather than Orca trying to
transform an absolutely positioned screen.

* fix(terminal): preserve split daemon snapshot payload

* fix(terminal): preserve live state when dropping alt frame

* fix(terminal): restore DECOM cursor state exactly

* fix(terminal): preserve ordinary snapshot bytes

* test(terminal): cover fixed-grid alt replay resize

* fix(terminal): repaint after dropping mismatched frames

A hidden snapshot can omit an alternate-screen frame before the pane has a measurable target grid. If reveal later lands on the capture grid, a same-size PTY resize emits no SIGWINCH, so pulse the local PTY size whenever that frame was skipped.\n\nKeep performSafeFit's measurable-pane contract intact, publish daemon snapshot prefix and frame as explicit optional strings, and fall back to the merged payload when either field is absent. Cold owner-gone restores now omit a mismatched frame while retaining history and fresh-shell reset treatment; offline SSH preconnect remains unchanged.\n\nPin the vendored SerializeAddon out-of-range-row behavior used to capture live SGR state.
2026-08-10 18:24:04 -07:00
Brennan Benson 398d3807e5 Merge remote-tracking branch 'origin/main' into brennanb2025/codex-structured-mobile-write
# Conflicts:
#	mobile/src/session/MobileNativeChatMessage.tsx
2026-08-10 16:17:25 -07:00
Brennan Benson 8da362919e perf(runtime): batch legacy worker recovery persistence (#13649)
* perf(runtime): batch legacy recovery persistence

* fix(runtime): preserve concurrent recovery state

* fix(runtime): require durable recovery retry
2026-08-10 15:58:17 -07:00
Brennan Benson 84bd306949 perf: Stop unchanged worktree refresh churn (#13662)
* fix: stop unchanged worktree refresh churn

* fix: preserve smart sort telemetry recomputations

* fix: preserve duplicate worktree host identities

* perf: skip reconciled catalog traversal

* test: strengthen worktree refresh regressions
2026-08-10 15:44:05 -07:00
Jinwoo HongandNeil ec7e3ea477 fix(terminal): prevent paired activity renderer starvation (#13508)
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-08-10 14:32:08 -07:00
Brennan Benson c62f782554 Merge remote-tracking branch 'origin/main' into brennanb2025/codex-structured-mobile-write 2026-08-10 12:01:26 -07:00
Brennan Benson 9202192f10 fix: restore structured chat parity 2026-08-10 01:37:59 -07:00
ce3ec4d5ce fix(repo-icon): keep a renamed fork's own owner avatar (#12271)
* fix(repo-icon): keep a renamed fork's own owner avatar

Fork repos always took the upstream owner's avatar, so a renamed fork
showed its parent project's logo. Same-name forks (personal copies)
still prefer the upstream owner; renamed forks now keep their origin
owner across auto-detect, the startup backfill, and the settings
avatar refresh.

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

* fix(repo-icon): re-read repo state before backfill avatar write

The startup backfill computed icon updates from a pre-loop snapshot, so
an icon chosen in settings while the upstream/origin probes were pending
could be clobbered. Re-read the repo after the probes and only migrate
an icon that is still the auto-detected GitHub avatar.

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

* refactor(repo-icon): own the fork avatar rule in one shared selector

The renamed-fork rule was written out twice — once in the main-process
auto-detect and once in the renderer refresh — so the two copies could
drift. Move it next to `githubAvatarIcon` as `githubAvatarSlug`, which
collapses the renderer resolver to a single unbranched path.

Also stop swallowing a rejected origin probe: it cannot tell a renamed
fork from a same-name one, so degrading to the upstream owner would flip
a renamed fork's stored avatar back to the parent's. Letting it propagate
keeps the stored icon, matching how the non-fork path already behaved.

Adds coverage for the startup backfill, the third decision point the fix
claims, which had none.

* test(repo-icon): cover pending backfill icon change

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-08-10 01:06:32 -07:00
Jinjing f5d934a3f5 3c5f0060 (#13496)
* refactor(runtime): extract pure path-candidate, review-branch, and folder-workspace helpers from orca-runtime.ts

Mechanical move of three closed, pure module-scope clusters out of
orca-runtime.ts (37,608 -> 37,207 lines) into domain-named siblings:

- terminal-output-path-candidates.ts: PTY output path harvesting and the
  recent-candidate history bound (3 entry points + 15 private callees).
- selected-review-branch.ts: forge-agnostic selected-review predicates and
  lookup hints (GitHub/GitLab/Bitbucket/Azure DevOps/Gitea).
- runtime-folder-workspace.ts: folder-workspace id math and the repo+meta
  -> Worktree projection.

Bodies are token-identical to their previous form; the only production
changes are the moves, the new import statements, and `export` keywords.
The no-control-regex suppression travels with the path-candidate scanning
that needs it. No max-lines suppression was added and the ratchet is
unchanged.

Adds characterization tests for the two clusters that had no direct
coverage; the path-candidate cluster keeps its existing tests, repointed
at the new module.

* fix flaky timer on CI
2026-08-09 23:37:33 -07:00
Neil 73efab98f7 perf(orchestration): keep drift Git off main thread (#13440) 2026-08-09 23:26:28 -07:00
Jinwoo HongandJinwoo-H 158212b8b3 feat(github): add PR comment reactions (#13470)
* feat(github): add PR comment reactions

* fix(github): harden comment reaction updates

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-09 22:11:54 -07:00
OrcaWin 75650936f4 fix(source-control): hydrate Windows shell profile PATH (#13418) 2026-08-09 18:42:15 -07:00
JinjingandOrca b075a95b06 Strip liveness gate from AI Vault session delete (#13279)
* Strip liveness gate from AI Vault session delete

Delete now requires only path validation + user confirmation — no process
roster, no liveness check, no quiescence, no ownership ledger.

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

* Remove obsolete AI Vault liveness delete reliability gate

Session delete no longer checks process liveness, so drop the
manifest entry that still referenced the deleted test files.

* minor fix

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-09 18:40:01 -07:00
Brennan Benson b4bcd20caa Add mobile structured session read flow 2026-08-09 18:22:23 -07:00
Brennan Benson b4ed53f7c6 Install the structured agent-session host on first use
The record store and the journals live under the profile's user-data path,
which is not final until Electron is ready, and a runtime that never serves a
structured session should not pay for a store it will never read. So the host
is built by the first `agentSession.create` or `.ensure` rather than at
startup, and the memoized slot clears on failure so the next call retries.

The capability gate runs first: building the store and spawning a child is an
observable side effect, and a client that cannot read structured sessions must
not be able to cause it.

Owner probing is scoped to this host. Checking another host's pid against this
machine's process table is exactly how a live owner gets declared dead, so a
foreign owner reports `indeterminate` and routes to manual recovery instead.
So does a reservation that named no process — freeing it needs positive proof
that nothing spawned under its token, and this host has no spawn-token process
scan yet.
2026-08-09 16:54:49 -07:00
Brennan Benson 5df2ddbc9c perf(ai-vault): isolate tab title resolution (#13377)
* perf(ai-vault): isolate tab title resolution

* fix(ai-vault): preserve background scan caches

* fix(ai-vault): resolve nested worker from chunks
2026-08-09 16:45:52 -07:00
Jinwoo Hong 6397668271 Add manual artifact sharing from HTML and Markdown views (#13369) 2026-08-09 16:11:16 -07:00
JinjingandOrca 3ec48a74d5 Gate artifact publishing behind off-by-default capability (#13368)
* fix(artifacts): gate agent artifact publishing behind an off-by-default capability

Public artifact sharing was reachable by any agent through `orca artifacts
share`: the Artifacts settings toggle only controlled sidebar visibility, and
nothing in the main process checked a capability before minting a public URL.

Add `artifactSharingEnabled` (default off) and enforce it in
ArtifactCloudService.share/update — before auth, network, or the share-record
write — so the CLI, relay-forwarded remote CLI, and IPC paths are all denied.
The denial carries a stable `artifact_sharing_disabled` code plus next steps
through the RPC error allowlist, so the CLI prints actionable guidance.

list, unshare, and delete stay ungated: turning publishing off must not strand
already-published links. The capability is absent from the `settings.update`
RPC schema, so an agent cannot grant it to itself — only the desktop UI can.

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

* fix(artifacts): gate agent artifact publishing behind an off-by-default

Publishing is blocked until enabled in Settings → Artifacts. CLI preflights the capability before reading files to avoid unnecessary uploads. RPC surface rejects capability grants so callers cannot self-grant. UI shows opt-in workflow and recovery path when publishing is off. Web clients mirror the host's setting read-only.

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-09 13:19:08 -07:00
Brennan Benson 0f97a9414e Fence PTY writes across async admission gaps 2026-08-09 13:14:41 -07:00
Brennan Benson c69b12b3dd Enforce the agent-session lease at every PTY write choke point
Route every byte that can reach a PTY through the single-writer lease
adjudicator, so a session owned by one runtime cannot be typed into by
another. Nothing binds a PTY to a durable session record yet, so the gate
short-circuits and today's shells, legacy agent terminals, and unbound
sessions are untouched.

Gated paths: terminal.send RPC, the renderer IPC write paths
(pty:write / pty:writeAccepted), plugin sendTerminalText, the agent-prompt
paste, and ptyController.write as a backstop for runtime writes that never
passed a typed gate. Every yield inside a write - inter-chunk, the
text/suffix pause, the agent-prompt submit delay, deferred size
measurement - re-checks the fence, so a handoff mid-paste stops the
remaining chunks instead of interleaving two owners.

Refusals are typed and additive on the wire: an optional
agentSessionRefusal on RuntimeTerminalSend and on the existing
pty:writeUnavailable payload, refusal codes drawn only from the existing
agent-session error codes, and stream-input refusals mapped onto the
WriteUnavailable frame old clients already decode.
2026-08-09 13:00:56 -07:00
NeilandOrca 970696a008 fix(sidebar): show Cursor rows and stop a stray "claude" title hijacking OpenCode (#12466)
* fix(sidebar): show Cursor rows and stop a stray "claude" title hijacking OpenCode

Two defects in the same title-resolution path.

**#10258** — Cursor's only native OSC title is the literal `cursor agent`, which both title trackers dropped unconditionally. A hookless Cursor pane therefore had neither a status entry nor any title carrying Cursor identity, so the worktree card showed nothing at all.

**#8940** — two owner-blind paths let an incidental `claude` token anywhere in an OpenCode session or task title outrank the pane's known owner, so the tab icon and sidebar row flipped to Claude Code.

#10258: let the literal through exactly once as identity, so a restored or mobile tab keeps its Cursor row instead of vanishing. #8940: require an *identity frame* — after stripping status decoration the title must PRESENT Claude, not merely mention it — before a Claude title may reclaim a pane from its prior identity, and make the sidebar row builder owner-aware.

> These two are in one PR because they share the `ownerAgentType` plumbing through `buildTitleDerivedAgentRow` — split apart, neither half compiles on its own.

Fixes #10258
Fixes #8940

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

* test(e2e): add recordable proof for sidebar-agent-row-identity

Fails on origin/main, passes on this branch.

Test: sidebar keeps a Cursor pane visible and an OpenCode pane out of Claude Code hands

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

* fix(terminal): preserve restored Cursor identity

* test(terminal): cover restored Cursor redraw suppression

* refactor(terminal): tighten Cursor identity handling and Claude frame matching

Review follow-ups on the title-resolution path:

- pty-transport dropped a native Cursor literal that main emits whenever a
  non-Cursor title preceded it, re-introducing the #10258 blank row in the
  renderer path. The pre-filter now projects the predecessor the drain will
  actually see, and defers to the drain gate while facts are still queued.
- applyTrackedPtyTitle threaded the cursor flag through 12 sites, including
  ptyRecordChanged bookkeeping the sole caller ignores. Force the status null
  once, and the activity-gated effects fall out unchanged.
- isClaudeIdentityFrameTitle missed a multiplexer-wrapped Claude title
  ("zsh | Claude Code"), costing a genuine Claude pane its identity. Reuse
  the ' | ' segment split that agent-title-owner already had inline.
- Keep title normalization on launchAgent: it only rewrites within an
  identity group (OMP wraps Pi), so a split does not make it wrong, and
  the hook-row path normalizes the same way.
- Drop the tab.ptyId tracker fallback, which read a pty that the pane
  identity check had just rejected.

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-09 01:32:34 -07:00
Brennan Benson 06144ca26c fix(worktrees): guard lineage pruning from failed scans (#13248)
* fix(worktrees): guard lineage pruning from failed scans

* fix(worktrees): back off failed local resolution scans
2026-08-08 12:42:38 -07:00
Jinwoo Hong c991bb27d3 Add account-backed artifact sharing (#13012) 2026-08-07 23:02:29 -07:00
Neilanddgk-dev 6ea99f6607 fix(runtime): isolate same-path folder workspace PTY identity (#12474)
Folder-project workspace ids (`repoId::/path::workspace:<uuid>`) were compared via the suffix-stripping `splitWorktreeIdForFilesystem`, so every workspace sharing one directory compared equal at ~35 runtime call sites — PTYs leaked between siblings and paired/mobile clients hung on "Loading terminal". Both identity helpers now use the suffix-preserving `splitWorktreeId`, `stopTerminalsForWorktree` routes through the shared helper, and `findResolvedWorktreeIdForPath` gains a `targetWorktreeId` tie-break.

Co-authored-by: dgk-dev <dgk-dev@users.noreply.github.com>
2026-08-07 21:11:01 -07:00
Jinwoo HongandJinwoo-H 2f30eb9af5 fix(ai-vault): block deletion of live sessions (#13108)
* fix(ai-vault): block deletion of live sessions

* fix(ai-vault): retain external session authority

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-07 20:36:23 -07:00
Avichal DwivediandJinwoo-H 96a6c93757 fix(orchestration): allow fresh agents to take over legacy runs (#12896)
* fix(orchestration): allow fresh agents to take over legacy runs

* test(orchestration): cover forged takeover evidence

* fix(orchestration): retain renderer launch authority

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-07 18:10:27 -07:00
Jinwoo HongandJinwoo-H ddf58d6d6a fix(terminal): restore preserved remote PTYs after host relaunch (#12990)
* fix(terminal): foreground preserved daemon PTYs

* fix(terminal): keep snapshot sequence domains distinct

* test(terminal): use active reconnect control

* test(terminal): await reconnect control activation

* test(terminal): validate reconnect with fresh control

* test(terminal): tighten host restart evidence

* fix(terminal): retry preserved PTY attach after inventory

* fix(terminal): retry attach after overlapping inventory

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-07 01:25:18 -07:00
Jinwoo Hong 2b42de1f52 fix(orchestration): wake coordinators with mail pointers (#12988)
Wake idle Run coordinators with durable orchestration mail pointers while keeping message payloads in the store until check consumes them. Preserve waiter, Cursor, restart, real Codex title, and PTY replacement behavior.\n\nPart of #12953.
2026-08-06 22:50:09 -07:00
BingZandJinwoo-H a025a71447 fix(orchestration): deliver pending mail to already-idle agents (#12584)
Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-06 17:08:28 -07:00
Jinjing 8ddf575fe6 Revert "Remove source control group order preference (#12785)" (#12955)
This reverts commit ae1ed5e886.
2026-08-06 16:33:42 -07:00
BingZ 74e1678d4b test(runtime): pin the fail-closed contract for bare Cursor titles (#12816)
Reverts the classification change and keeps behavior at base.

cursor-agent's native OSC title is the bare literal "Cursor Agent" and never
carries a status word, so it names the agent without proving one is present.
The title tracker drops it live, so main records it only when the stale-working
timer strips the spinner off the synthesized "⠋ Cursor Agent" — and that fires
both when Cursor parks idle and when cursor-agent exited and the shell reclaimed
the pane. The two states are observationally identical: same title, same null
foreground read.

Classifying it as an agent therefore removes a refusal rather than adding
evidence. Guarded sends auto-submit Enter, so the false positive types into the
user's shell. A null foreground is also not "unreadable" on the default local
provider, which returns null when the pty is gone.

hasPty, probePtyLiveness, hasChildProcesses and inspectProcess were each checked
as corroborating signals; none separates alive-with-agent from alive-with-shell
when the foreground read is unavailable.

Tests pin every no-evidence branch fail-closed and document the mechanism, so
both attempted fixes fail loudly if reintroduced.

Real gap tracked in #12946.
2026-08-06 13:15:48 -07:00
Jinwoo Hong b0ba51831c Add per-worker model and effort overrides (#12851) 2026-08-05 21:17:45 -07:00
Jinwoo HongandJinwoo-H aa64ac9606 fix(runtime): degrade focus-requested terminal create on headless serve (#12791)
`orca serve` publishes a ready graph under HEADLESS_RUNTIME_WINDOW_ID with no
BrowserWindow behind it. `shouldCreateInBackground` only degraded when the
create was renderer-backed, so any focus-requested create fell through to
getAuthoritativeWindow() and threw "No renderer window available" — leaving
`terminal create --focus` with no workaround on a remote server (#10333).

With a worktree selector and no renderer window, a background spawn is the only
usable path, so collapse the renderer-backed window check into a plain
"no window" check. That is the existing rendererBacked clause plus exactly the
missing focus case, and it drops the confusing `rendererWindow === null`
indirection (rendererWindow is already gated on rendererBacked).

Focus is not lost by the degrade: the spawned pane is still published to the
session-tab model and revealed with `activate: true`, which is how a paired
client learns about it. Mirrors the in-tree precedent in
runCreateMobileSessionTerminal.

Headed hosts are unaffected — the clause only fires when no window exists.

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-05 17:49:04 -07:00
Brennan Benson 6df8997c3c fix(file-explorer): sort numbered file names naturally across every listing surface (#11576)
* fix(file-explorer): sort numbered file names naturally

The File Explorer compared names with bare localeCompare, so numbered
files listed 100, 200 before 99. Hoist the numeric collator Source
Control file rows already use (#10850) into src/shared and apply it to
the local and runtime directory listings, the name-filtered view, and
Source Control directory nodes, which were inconsistent with the file
rows one line below (#11426).

* fix(file-explorer): natural sort on SSH funnels, relay, and pickers

Adversarial-review round 1 rework:
- Both readDir funnels short-circuited to the SSH filesystem provider
  before the patched sort, so SSH workspaces kept lexicographic order;
  re-sort locally after the provider returns (the remote relay may be an
  older build), and fix the relay's own comparator for relay-native
  consumers.
- sortDirEntries (shared, unit-tested) owns the directories-first +
  natural-order listing contract used by every funnel.
- compareFileNames breaks numeric-collation ties ('2' vs '02') by code
  units so sibling order stays total instead of readdir order, and pins
  the collator locale to 'en' so every host produces one order.
- The SSH folder browser and runtime server dir picker now match the
  Explorer they browse into.
- Ordering pinned by tests at the relay, source-control tree, and shared
  helper.

* fix(mobile): natural sort in the mobile file explorer

Mobile re-sorted host readDir results with bare localeCompare, undoing
the host funnel's natural order (round-2 review). Reuse the shared
comparator and pin the order in the mobile suite.

* fix(file-explorer): natural sort at the renderer choke point and remaining ties

Round-3 review: the remote-runtime RPC and paired-web routes return the
host's order verbatim, so re-sort in readFileExplorerDirectory where
every desktop route converges; pin the SSH funnel with a handler-level
test; and route Source Control path compares through compareFileNames so
numeric-collation ties share one total order with the Explorer.

* docs(file-name-sort): state the real perf baseline in the hoist comment

* refactor(source-control): drop the dead collator export; pin the test oracle locale

* fix(file-listings): cover remaining natural-sort surfaces
2026-08-05 17:11:28 -07:00