Commit Graph
11327 Commits
Author SHA1 Message Date
Neil d71e1981e0 fix(daemon): a terminal the old daemon still runs is never handed to the new daemon after an update (#29418)
* fix(daemon): a daemon update never hands a live old-daemon session id to the new daemon

Refs #26296

* fix(daemon): reconnect an older daemon through its inventory before refusing a spawn

* fix(daemon): reattach the current daemon's own session before consulting older daemons

* fix(daemon): reconnect the current daemon too before an older daemon can refuse its session
2026-10-15 23:19:27 -07:00
Neil c5c49a94ce fix(remote-terminal): queue restore subscribes past the PTY wait cap instead of failing them (#29417) 2026-10-15 23:19:11 -07:00
Neil ee316913b0 fix(terminal): a Windows terminal read no longer shows PowerShell's history suggestion as typed input (#29415)
* fix(terminal): read a Windows ConPTY terminal as painted, not with PSReadLine's overwritten history prediction

* fix(terminal): bound the ConPTY rendered read, honor visible-only probes, and fill the line limit

* test(serialize): record the ConPTY capture's pre-existing wrap-pending cursor restore
2026-10-15 23:18:55 -07:00
Neil f8a3ef7fd7 test(agent-hooks): a slow last-status rename never stalls hook traffic (#29414)
* test(agent-hooks): guard that a slow last-status rename never stalls hook traffic

Regression guard for #22804 (STA-8446).

* test(agent-hooks): flush while the older rename is held so the stale-rename race is exercised
2026-10-15 23:18:38 -07:00
Neil 52419be3a5 feat(linux): opt-in per-terminal memory and swap limits for the terminal host (#29413)
ORCA_TERMINAL_MEMORY_MAX and ORCA_TERMINAL_MEMORY_SWAP_MAX set MemoryMax and
MemorySwapMax on each terminal's systemd user scope. Off by default, applied only
after the probe scope shows the kernel enforcing them, and dropped (logged) where
the host cannot, so terminal scopes never turn off because of them.

Refs STA-3430
2026-10-15 23:18:21 -07:00
Neil 3074852b7c fix(orcad): seal integration credentials on the headless server instead of storing them in plaintext (#29412)
* fix(orcad): seal integration credentials on the headless server instead of storing them in plaintext

The Node SecretStore always reported encryption unavailable, so every
credential writer on orcad fell back to plaintext. orcad now seals with
AES-256-GCM using a host-owned master key kept in the OS keyring (macOS
Keychain, Linux Secret Service) when reachable at first start, else in a
0600 profile file bound to the machine ID. A key that cannot load fails
closed instead of downgrading. Existing plaintext credentials are sealed
on startup without loss.

* docs: describe how the headless server seals credentials and its limits

* fix(orcad): unique credential staging files, no keyring items for temp profiles, isolate startup test

* fix(orcad): keep the credential key with the credentials, one record per machine

* fix(orcad): never re-key over an unreadable record, serialize first-start key creation, refuse an unrestricted key file

* fix(orcad): verify a key record's Windows ACL and wait out a concurrent first start's keyring write

* fix(orcad): keep a keyring claim and fail closed when its store result is uncertain

* fix(credentials): restrict the staged file's Windows ACL before it replaces a credential

* fix(orcad): a temporary profile never creates a key in, or re-seals, the real credential home

* ci: baseline the two single-file credential ACL restrictions

* fix(credentials): stage Windows credentials born restricted and verify the key record asynchronously; document rollback

* test(bitbucket): inject the write failure into the Windows staging writer too
2026-10-15 23:18:05 -07:00
Neil 9b8a29c734 fix(notifications): a reconnect replay no longer re-fires task-complete notifications (#29410)
* fix(notifications): a reconnect replay no longer re-fires task-complete notifications

Carry the host's isReplay flag through the agentStatus:set IPC payload so the
renderer seeds completion tracking from replayed rows, as it already does for
the startup snapshot, instead of treating each cached done as a new turn.

* test: drop a type assertion from the replay notification test
2026-10-15 23:17:48 -07:00
Neil 86da9e8696 fix(editor): bind a local worktree's tabs to this machine when its project is also on a paired server (#29403)
A project set up here and on a paired server has repo rows on both hosts.
Local worktree rows are left unstamped, so their owner lookup fell to those
repo rows, found two hosts, and gave up; openFile then bound the tab to
whichever server was focused, which cannot find the workspace.

Listings stamp every paired-server row with its host, so an unstamped row
of such a project was listed here: route it to this machine.

Fixes #25467
2026-10-15 23:17:32 -07:00
NeilandSangrak Choi b7eaef8148 fix(ssh): try the waiting ~/.ssh key before showing a password dialog (#29397)
* fix(ssh): hold the password challenge until the deferred key has been tried

When an agent is configured, the first attempt carries only unencrypted
explicit keys and leaves default-name (~/.ssh/id_*) and encrypted keys to the
no-agent retry. keyboard-interactive still sat at the end of that first
attempt's ladder, so a host that collects its password through
keyboard-interactive showed the Password: dialog before the deferred key that
authenticates had been tried. Since #15588 that prompt goes through the
password dialog and caches the answer, and cancelling it marks the attempt
cancelled, which also skips the key retry, so the connection fails instead of
falling back to the key.

An attempt that carries no key but defers some now skips keyboard-interactive
when the host lists it as a first-factor method next to publickey. When the
host does not list it, the challenge can only be a second factor after the
agent key partially succeeded, so it stays in the ladder. When the host does
not list publickey, the deferred key cannot help, so it stays too. Attempts
that carry a key keep the challenge: SshConnection never retries them with the
deferred keys, and the password retry reuses their config.

The deferral applies only when every deferred key parses as a private key that
needs no passphrase. With an encrypted one the retry would ask for its
passphrase instead, so skipping the challenge would only swap one dialog for
another, and cancelling the passphrase dialog fails the connection rather than
reaching the password flow. A file that is no usable private key (corrupted, a
public key under a private-key name, an unsupported type) makes ssh2's
connect() throw before the retry sends any auth request, which is not an auth
error, so the connection would fail without ever offering the challenge. Those
targets keep the origin/main ladder.

The cost: when the host rejects the deferred key, its password challenge now
arrives on the key retry, one failed handshake (plus a respawned ProxyCommand,
if configured) later than on origin/main.

Constraint: ssh2 1.17 hands an agent key's USERAUTH_FAILURE to the next agent key before recording partialSuccess, so after an agent partial success the auth handler sees the pre-agent method list with partialSuccess=false
Constraint: ssh2 parses config.privateKey before any agent auth can run, so default-name and encrypted keys stay deferred to the no-agent retry
Constraint: SshConnection runs the no-agent key retry only when the agent-first attempt carried no key
Rejected: drop keyboard-interactive from the deferring attempt's whole initial ladder | ssh2 hides an agent partial success, so publickey,keyboard-interactive hosts lose their second factor
Rejected: defer whenever any key is deferred | a mixed-key attempt gets no key retry, so a keyboard-interactive password host would never see the challenge
Rejected: defer for encrypted keys too | the Password dialog becomes a passphrase dialog, and cancelling it fails a connection that origin/main completes
Rejected: check the deferred keys only for encryption | a garbage or public key file aborts the retry with a non-auth error, failing a connection that origin/main completes
Rejected: run the no-agent retry even when no key is waiting on disk | adds the same extra handshake for agent users who have no key to try in between
Directive: keep deferKeyboardInteractive in step with the key-retry guard in SshConnection.attemptConnect
Confidence: medium
Scope-risk: narrow
Reversibility: clean
Tested: pnpm test src/main/ssh; real-socket fixtures for a first-factor challenge on a publickey,keyboard-interactive host, an encrypted default key, and an agent partial success followed by keyboard-interactive; unit tests for a garbage or public key file under a default key name; mutation checks (no deferral, no key check, encryption-only check, publickey guard removed, whole-ladder withholding, any-key-deferred condition) each fail a new test
Not-tested: the reworked revision against a live host
Related: #15588

* fix(ssh): keep an agent key's second-factor challenge when the host first offered both methods

* fix(ssh): defer the password challenge only for a single waiting key

* fix(ssh): read the deferred key only after the agent attempt reaches the challenge

---------

Co-authored-by: Sangrak Choi <kars@kargn.as>
2026-10-15 23:17:15 -07:00
Neil a37c70dd4c fix(daemon): replace a retired macOS terminal service job that blocks new terminals (#29361) (#29395) 2026-10-15 23:16:59 -07:00
Neil a4e3dc2b66 fix(automations): Run Now matches the managed server's setup after an SSH host moves (#29394)
A legacy project setup's id is its repo id, so a moved SSH host's
retained setup and the managed server's setup share one id. The Run Now
check took whichever came first and reported a host mismatch. Pick the
setup whose host matches the run context.
2026-10-15 23:16:43 -07:00
Neil d4098980d1 perf(runtime): read a remote server's SSH target states three at a time (#29393) 2026-10-15 23:16:26 -07:00
NeilandClaude Opus 5.5 b908d929f7 fix(session-history): keep scan notices from burying the session list (#29392)
Every session the scanner read only partly (for example a Codex transcript on an
SSH host with a record over the 10 MiB cap) adds its own notice row above the
list, outside the list's scroll area. With dozens of them the panel filled with
notices and the sessions could not be reached. Notices now sit in one bounded
area that scrolls on its own.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 23:16:09 -07:00
Neil 1faa210d4c fix(serve): reap orphaned processes when orca serve is PID 1 (#29390)
* fix(serve): reap orphaned processes when orca serve is PID 1

* fix(serve): keep AppImage, eval launches and exit status intact under the PID 1 reaper
2026-10-15 23:15:53 -07:00
OrcaWinandOrcaWin 6a2b789b35 fix(browser): defer capture cleanup until streamed tabs finish closing (#29365)
* fix(browser): defer publication cleanup until native destruction completes

* test(browser): reproduce the serve tab-close SIGSEGV with a hidden page window

Headless serve backs each page with a hidden top-level BrowserWindow, whose
Aura view is what crashes; the <webview> guest the fixture used never reached
that path, so the test passed against the original code. With the serve shape
the test segfaults (fault 0x1268, RIP +0x55a8d7b) without the fix.

* test(runtime): wait for the deferred publication restore after paired create and close

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-15 23:15:40 -07:00
Neil b16b01d092 fix(remote): leave originless legacy records unresumed on a paired-client open (#29389)
Fixes #29303
2026-10-15 23:15:35 -07:00
Neilandthukabjj 7c9bb00622 fix(orcad): ship @linear/sdk with the managed server so Linear works on SSH hosts (#29388)
linear-sdk.ts loads the SDK through createRequire, which the orcad bundle
cannot follow, and nothing staged the package beside it. Bundle the SDK
into node_modules/@linear/sdk/index.js at build time and list it as an
orcad artifact so templates and installs carry it.

Co-authored-by: thukabjj <31043997+thukabjj@users.noreply.github.com>
2026-10-15 23:15:18 -07:00
NeilandClaude Opus 5.5 4e8f75c0fb fix(agent-hooks): clear a Claude permission wait when its approved tool fails (#29387)
Orca keeps a Claude permission wait visible until it sees the exact tool it was
waiting on resume. A tool that fails reports PostToolUseFailure, whose tool
name and input Orca clears, so the resume check never matched and the pane kept
showing "needs permission" while Claude was already working. The exact
tool_use_id and owner now settle the wait for a failure as well.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 23:15:01 -07:00
Neil 904b6f2b32 fix(ssh): flag a macOS host whose managed server outlived its launching SSH session (#29371)
* fix(ssh): flag a macOS host whose managed server outlived its launching SSH session

macOS Local Network privacy pins a nohup-launched orcad to the sshd-session
that started it; once that session ends, its terminals lose LAN access (#25934).
A macOS launch now records that session, a connect reports it when it proved
exited, and the host status offers a restart when no terminals are running.

* fix(ssh): retire the idle terminal daemon on a launch-session restart

* fix(ssh): keep the fence on an unsettled restart stop, and report a restart that can't hold its session

* fix(ssh): carry the launch session forward while the terminal daemon survives; never redial for the check

* chore(i18n): add the launch-session warning and restart strings to the English catalog
2026-10-15 23:14:45 -07:00
Neil b1f54f281d perf(remote): list a worktree's tabs over the ready shared socket, not a new one (#29360)
Every worktree switch on a paired desktop lists that worktree's session tabs.
session.tabs.list was pinned to the one-shot transport so snapshot recovery
keeps working while shared-control streams reconnect after a host restart, so
each switch opened, upgraded and E2EE-authenticated a brand-new WebSocket just
for that read: about three extra round trips per switch. Use the shared-control
socket when it is ready, and keep the one-shot path whenever it is not.

Refs #26785
2026-10-15 23:14:28 -07:00
Neil fc534c6a19 fix(remote-terminal): a headless host keeps a parked terminal at its viewer's size (#29349)
* fix(remote-terminal): a headless host keeps a parked terminal at its viewer's grid

Parking a paired pane closes its stream, the PTY's last viewer. The floor then reclaimed the PTY to the host's geometry, which on a windowless orca serve/orcad host is the 120x40 spawn grid nobody sees. Every reveal then fetched a snapshot at 120x40 and reflowed it to the pane, and a TUI redrew twice. With no host pane to give the grid back to, the floor now keeps the last owner's grid; the parked owner's reserved seat resumes it without a resize.

* test(remote-terminal): a phone or another viewer still takes a parked terminal's grid on a headless host

* fix(remote-terminal): a phone leaving a parked headless terminal restores the parked grid

* fix(remote-terminal): keep the parked desktop grid as the restore baseline even while a phone fits

* test(remote-terminal): tie each PTY size probe to its own reply
2026-10-15 23:14:11 -07:00
Neil 00ab9bd694 perf(serve): a headless host ignores a remote pane's unchanged layout write (#29347)
Every time a paired desktop remounts a remote terminal pane (revealing a parked
worktree or tab), it re-sends the layout the host gave it. A headless orca serve
or orcad persisted the workspace session and republished the whole workspace's
tabs to every paired client for each of those identical writes. Compare the
edit with what the host already holds and skip both when nothing changed.

Moves terminal-layout-equality to src/shared so the host can reuse it.

Refs #26785
2026-10-15 23:13:55 -07:00
Neil 9b2adf4e97 fix(runtime): route a CLI close of a paired host's terminal to that host (#29455)
* fix(runtime): route a CLI close of a paired host's terminal to that host

orca terminal list on a desktop lists terminals a paired runtime runs, but
orca terminal close on one of those handles stopped the remote PTY id on this
machine, which cannot own it, and answered terminal_stop_unverifiable while
the process kept running on the host. closeTerminal and closeTerminalTab now
send the close to the owning runtime first and return its receipt; when that
host is unreachable the close fails and the terminal stays visible.

* fix(runtime): revalidate the pane after a paired-host close, and read the owner's stale-handle answer

Codex review: a pane rebound while the host call was pending had its new
occupant retired, and an owner that no longer knows the handle answers
terminal_handle_stale, which rejected instead of retiring the mirror.

* fix(runtime): refuse a pinned paired-host tab before asking its host to close it

Codex review: the renderer's pin guard runs at the local commit, after the host
had already stopped the process.
2026-10-15 23:13:43 -07:00
NeilandJinwoo Hong d9e4017470 fix(terminal): a reveal after hidden output was dropped always restores it (#29342)
* fix(terminal): a reveal after hidden output was dropped always restores it

A restore that failed while its pane was hidden zeroed recovery and wrote
its loss banner where nobody could see it, so the reveal skipped the
restore and the gap stayed silent and permanent. Stay armed instead and
spend one fresh snapshot on reveal. Main's drop latch now retires on the
renderer's paint ack rather than on unhide, so a hidden pane that retires
cannot spend the only marker on a handler that is already gone.

Fixes #15504

Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>

* fix(terminal): dedupe a disclosed hidden-output loss by drop episode

The unhide marker carries main's emit-time output sequence, which visible
output advances without any new drop, so a seq comparison re-bannered the
same gap. Main now tags gate markers with a drop episode, and a drop after
an unhide starts a new episode that the in-flight restore's ack cannot
retire.

Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>

* fix(terminal): a restore ack retires only the drop episode it covered

An older restore still in flight when a newer hide/drop/reveal cycle ran
could ack and erase the newer episode's drop memory. The renderer now
captures the latest reported episode before fetching and names it in the
ack; main retires the latch only on a matching episode.

Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>

* test(terminal): pin hidden-restore recovery against a drop-reporting host (#29253)

Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>

---------

Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
2026-10-15 23:13:38 -07:00
Neil 16c2940ec5 fix(remote): drop this desktop's rows for a paired host's project when it is removed (#29436)
The startup residue sweep deliberately never reaps runtime:* rows (#17776), and
the renderer never told main when a paired host confirmed a project removal, so
those rows outlived every remote delete. Removal now calls repos:removeForHost
for the owning runtime host once the host confirms, and main prunes that
removal on the paired host's partition only.
2026-10-15 23:13:30 -07:00
Neil 825963380d perf(worktrees): a Smart-sort save on a paired host no longer refetches every repository or triggers the next save (#29333)
* perf(worktrees): a Smart-sort save on a paired host no longer refetches every repository or schedules the next save

The host now publishes the saved sortOrder values on the order-only
worktreesChanged event, so a new desktop patches its rows in place instead of
re-reading each repository. A refetch whose only difference is sortOrder no
longer bumps sortEpoch, so an older host's save cannot trigger another Smart
re-sort and save.

Refs #27268

* fix(worktrees): keep lineage retry and receipt order for published sort-order saves

* fix(worktrees): refetch rather than apply sort-order values published on an earlier connection

* fix(worktrees): fence published sort orders on the event-stream subscription key

* fix(worktrees): refetch a published sort-order save when its rows were never loaded
2026-10-15 23:13:21 -07:00
Neil adff94d282 fix(ssh): report an unreachable host's worktree graph as unavailable, not empty (#29416)
listRepoWorktreeGraph answered [] for a runtime- or ssh-owned repo, so hosted
review told the user a live remote worktree did not belong to its repository.
It now throws the same WorktreeCatalogUnavailableError as listRepoWorktrees.

Fixes #18272
2026-10-15 23:13:09 -07:00
Neilandmmarabel 73393f7009 fix(terminal): a cold-parked terminal reveal paints once, at the pane's own size (#29331)
* fix(terminal): a cold-parked terminal reveal paints once, at the pane's own size

A structural replay (park reveal, reattach, hidden-output restore) clears the
pane, resizes xterm to the snapshot's grid, replays, and only then fits back to
the pane. Every step was presented, so a reveal showed a blank pane, then the
host's grid in the top-left, then the real fit. The screen is now withheld
behind a copy of its last frame until the transaction and its fit finish,
capped at 1 s so a deferred fit can never leave a live pane unpainted.

Pane-level half of #22728, plus a hold cap and a paired-runtime reveal e2e.

Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>

* fix(terminal): keep a WebGL screen shown when its frame cannot be copied, and keep decorations positioned

Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>

---------

Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
2026-10-15 23:13:04 -07:00
Neil c5a92b1df1 fix(terminal): prevent automatic replacement of host-mirrored agents (#29289)
* fix(terminal): leave mirrored agent recovery to execution host

* fix(terminal): limit mirrored recovery guard to replacement launches
2026-10-15 23:12:47 -07:00
Neil 2c44830c68 fix(remote-terminal): a pane-restore snapshot moves the gap baseline so reveal skips a resync repaint (#29285)
Refs #22087
2026-10-15 23:12:29 -07:00
Neilandggbdpq 187c0b023a fix(runtime): suspend the socket idle timeout while a dispatch is in flight (#29262)
The runtime socket transport reaped connections after 30s of raw byte
silence, and Node's idle timer counts bytes — so a slow dispatch like
worktree.create on a large repo, which is not a long-poll and sends no
keepalives, read as a dead peer: the server destroyed the socket mid-
request, the handler kept running (the worktree got created), and the
CLI reported runtime_unavailable and told the user to restart. Callers
then retried, leaving orphaned worktrees or two agents on one task
(#25830, the root behind #15663/#23911/#20108 symptoms).

The reap timer now suspends while dispatches are pending and rearms
when the last one cleans up. Quiet connections with nothing in flight
still reap after exactly the same 30s, and a dead peer on a local
socket is delivered by the kernel as close/error, which the existing
cleanup already handles — so the reaping duty is unchanged where it
mattered.

Fixes #25830

Generated-by: GLM-5.3-Flash (ZCode)

Co-authored-by: ggbdpq <ggbdpq@gmail.com>
2026-10-15 23:11:59 -07:00
ebd8eff4ce fix(github): resolve SSH host aliases for account binding (#29018)
* fix(github): resolve SSH host aliases for account binding

An origin like git@github-personal:owner/repo kept the alias as the
required gh host, so the Settings account list matched nothing.

* fix(github): keep github.com filter for resolved aliases and resolve in the gh runtime

Real canonical keys carry github.com, so an alias resolving to github.com
must filter to github.com like a direct remote. Resolve via the runtime-scoped
resolveSshConfigHostname (WSL distro aware) and keep Enterprise hosts exact.

* fix(github): expand SSH aliases only for SSH-transport remotes

* fix(github): resolve the SSH alias with its original case

---------

Co-authored-by: orchardprior <342342873+orchardprior@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
2026-10-15 23:11:42 -07:00
OrcaWinandOrcaWin 0acb4933c0 test: observe keepalive suppression while short RPC is pending (#29475)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-15 23:08:45 -07:00
OrcaWinandOrcaWin 82f9eef651 test: remove duplicate POSIX missing-artifact install probe (#29470)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-15 23:06:05 -07:00
OrcaWinandOrcaWin c3811b80d0 test: exercise a real mobile floor claim during preview paste (#29464)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-15 23:05:33 -07:00
OrcaWinandOrcaWin e82bdbe9da test(linear): reuse runtime import across project access cases (#29452)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-15 23:05:27 -07:00
Jinwoo Hong 13084ba347 feat(mobile): long-press a workspace to see its linked PR, issues and notes (#29225)
* fix(mobile): phone server rows follow the desktop's other-client and sleeping filters

A phone created through a desktop now records whom it acted for, so the
desktop's Hide other-client workspaces filter keeps the user's own phone
workspaces (locally and on servers) and hides only other people's.
The desktop relay marks each server row its sidebar would hide, and the
rows its own browser or chat tabs keep awake; the phone applies both.

* test(e2e): dedupe sidebar row ids in the visibility spec

* fix(mobile): address review: owner naming, old-server relayed phones, scope trim

* refactor: move the sidebar's project section grouping into shared

The project sections the desktop sidebar draws under Group by Project are
built from the project/setup projection, the per-host catalog fold, the
endpoint host stamp, and the section labelling and ordering rules. Move the
pure parts of each into src/shared (no behaviour change) so the paired phone
can call the same code instead of guessing the grouping.

* fix(mobile): group by Project with the desktop sidebar's own sections

The phone grouped Project sections by repo display name, so it merged
checkouts the desktop keeps apart (several local stablyai/orca checkouts,
or a server's copy) and split what the desktop merges. The phone now reads
each host's repo.list, project.list and projectHostSetup.list (the desktop's
own and each server's through the relay), folds them with the renderer's
shared catalog fold and projection merge, and builds sections with the
desktop's shared grouping, labelling and ordering code. Keys, labels and
Recent order equal the sidebar's; a host that predates the project lists
gets the repo-derived projection, as the desktop does.

Header colour and icon come from the section's anchor repo and rows from
their own repo by id, as on the desktop. The repo filter lists every
catalog repo by id per host instead of merging same-named repos. Group-by
labels read Project and None. project.list and projectHostSetup.list are
routed for paired phones (read-only, workspace permission).

* refactor(mobile): record a phone's owner as creator and fold desktop tab activity into the host flag

A phone now records its owner as the creator (this host for a directly
paired phone, the relaying desktop for a delegated one), so the shared rule
is unchanged and onBehalfOf is gone. The relay ORs the desktop's saved
browser/chat tabs into hasHostSidebarActivity, so the phone's status dot
follows too, and local worktree.ps counts the same signals. The old-server
fallback is removed.

* test(e2e): poll the phone and the sidebar together in the visibility spec

* fix(mobile): one Project-section builder for desktop and phone, with the desktop's repo order

Review fixes. The sidebar's buildOrderedGroups now calls the shared
buildProjectSections for Group by Project (notice and pending repos are
optional inputs), so desktop and phone run one function. The phone ranks
manual header order by its folded catalog order, which is each host's
repo.list order with the desktop's own repos first, as the renderer loads
them; only the desktop's cross-host manualRepoOrder (pairing-local) is
unknown to it. The projection selector is now a memo over the shared
resolver. A transient project-list failure keeps that host's last catalog;
only forbidden or method_not_found falls back to the repo-derived
projection. Catalog rows are checked on every member grouping reads, and
the parity test answers the renderer's real catalog fetch, includes a
server's own project rows, and fixes Manual and Recent orders that differ.

* fix(mobile): use the repo-derived catalog until a host's project lists first answer

* fix(mobile): the desktop serves its sidebar's Project catalog instead of the phone fetching it

The phone fetched repo.list, project.list and projectHostSetup.list from
the desktop and every relay-ready server each minute and folded them
itself, so rows from offline or older servers fell back to repo sections
and keys moved on cold start.

The desktop renderer now publishes the inputs its own Project headers use
(state.repos in sidebar order, the projection selector's projects and
setups, projectOrderBy) on the runtime window graph it already sends to
main, only when they change. Main keeps the last one and answers it on a
new paired-desktop read, mobileRelay.projectSections. The phone reads it
with its repo metadata and groups with the shared builder; a desktop that
predates the read (forbidden or method_not_found) leaves the phone grouping
rows by repo from the desktop's repo.list, and a transient failure keeps
the last catalog. The payload carries only grouping and label fields, no
hooks, credentials or uploaded icons. The phone's own catalog fetching and
its project.list/projectHostSetup.list routes are removed.

* fix(mobile): keep uploaded repo icons on the phone, and isolate the snapshot tests

* test(e2e): the desktop window serves its sidebar's Project catalog to the phone

* chore(i18n): regenerate the runtime English catalog (drift already on main)

* fix(mobile): read the Project catalog as its own request, outside the recorded repo-metadata flow

The extra read inside the repo-metadata refresh stalled its recorded RPC
sequence. It now runs beside it with its own throttle. The repo-metadata
goldens are re-recorded for the intended state change: the name-keyed
colour, icon and id maps became desktopRepos.

* refactor(sidebar): one shared Workspace options filter and sort evaluator

The sidebar, the jump palette and the options badge each applied the
Workspace options filters with their own copy of the rules. They now
call one pure evaluator in src/shared/workspace-view, which reads each
row through a small fact reader so the phone can call it next with the
facts its host serves. The sort comparator moves there too, with each
surface supplying its own Agent activity rank.

No behaviour change: a parity test runs the old sidebar chain and the
new evaluator over every toggle combination, and the old comparator
against the new one for each sort mode.

* wip: workspace view descriptors

* refactor(sidebar): address review of the shared Workspace options evaluator

One predicate builder instead of a per-row scope argument; one
exemption-narrowing function taking the hide flag; consumers import the
shared predicates directly; Agent activity ties fall to the label in the
shared comparator. The activation filter lift reuses the shared
predicates. The parity test now covers other-client rows, live agent
attention classes and the sort-label map.

* fix(mobile): fetch the Project catalog at once when the screen moves to another host

The refresh window is now kept per client, so a host screen reused for another desktop doesn't wait out the previous host's 60s window before showing its Project headers.

* wip: desktop option items from shared choices

* refactor(sidebar): the Workspace options menu renders from one shared option list

src/shared/workspace-view/workspace-view-options.ts lists every Workspace
options control (Hosts, Projects, Group by, Sort by, Project order, Card
layout, Show properties or Card display, and each filter) with its
section, its English copy, its choices and when it shows. The desktop
menu walks that list and renders each option through a table keyed on
the option key, so a new option fails to compile until the desktop has
a control for it; the phone sheet will do the same.

The choice lists (sort modes, project order, card layout, agent activity
layout, card properties) are derived from the shared ids, and a test
checks the desktop's English copy matches the shared copy. The unused
classic branch of the card property list is removed.

No visible change.

* wip: row facts

* refactor(sidebar): address review of the shared Workspace options list

The show rules get their own tests with fixed expectations (the cases
the old filter-section test covered, plus the rest), and the menu test
checks each option's own control rather than any matching text. Card
display rows toggle one property each, with the folder-path copy in
shared. The unused Status and Tasks copy left by the removed classic
card list is deleted from the catalogs. Filter rows write through the
store directly; files are named for what they hold.

* feat(runtime): worktree.ps carries the Workspace options row facts

Each worktree.ps row now says whether it is the project's default-branch
workspace, created by an automation, created by the CLI, or on a
detached HEAD, answered by the same shared predicates the desktop
sidebar filters with. The phone has none of these facts today, so its
copies of the filters either guess (default branch) or are missing.

Additive and inert: optional fields that nothing reads yet. An older
host omits them; readers must treat absence as 'not that kind', so the
row stays visible. Server rows relayed through the desktop pass them
through unchanged.

* feat(mobile): the desktop's own phone reads and writes its other-client filter

ui.get and ui.set strip the pairing-local UI fields for every paired
client. That rule exists for clients with their own host catalog (a
desktop paired to a server, a web client). A phone has none: it shows
its desktop's view, so 'Hide other-client workspaces' must be one shared
value. For the host's own phone (clientKind mobile, no relaying parent)
ui.get now always answers the field and ui.set persists it; delegated
phones, paired desktops and web clients keep the stripping rule.

The phone's view-settings schema reads the rest of the Workspace options
values it does not read yet (the automation, CLI and detached-HEAD
filters, project order, card properties, agent activity layout), each
salvaged to absent so an older desktop keeps the phone's local value.

* feat(runtime): address review of the worktree.ps row facts

Name the fact defaultBranch, and say what absence means for it: a reader
keeps its own guess, because 'hide default branch' must not start hiding
or showing rows an older host never described. Folder workspace rows get
the facts too, so every row from a newer host carries them. The
desktopOtherClient comment now says it is a verdict, not a fact. The test
covers the rows where the host disagrees with the phone's current guess.

* wip: phone uses the shared evaluator

* feat(mobile): address review of the phone's other-client filter mirror

One isHostOwnPhone predicate decides both who shares the filter value
(ui.get/ui.set) and who gets the other-client row marks, so the two
cannot drift. The pairing-local field list notes the exception. Tests
cover a paired desktop still being stripped, and a phone write that
leaves the field out not touching the host value.

* feat(mobile): the phone filters and sorts with the desktop's shared Workspace options rules

The phone's own filter chain and comparator are replaced by the shared
evaluator, reading the facts the host stamps on each row. Phone filter
state takes the desktop's setting names and carries every filter; the
badge and Clear filters match the desktop's. The desktop's other-client
marks become facts the phone filters with the shared value.

A parity test runs the same workspaces through the desktop's reading
and the phone's (facts stamped as the host stamps them) over every
filter combination, folder projects included.

* wip: phone view state carries project order and card props

* fix(mobile): host scope in shared filters, patch-key writes, live view push

Folds the phone's host filter into the shared filters so Clear and the badge agree,
writes back only the touched filter keys, and pushes workspaceViewChanged so an open
phone list re-reads desktop Workspace options.

* fix(mobile): Clear sends only changed filters; skip a view re-read older than a local edit

An older host's strict ui.set refuses a key it lacks, so Clear names only what differs. A ui.get
that started before a local edit no longer overwrites it; the edit's own change event re-reads.
The change publisher's field list now lives with it and derives the filter fields.

* feat(mobile): the phone's Workspace options sheet, rendered from the desktop's shared option list

The Filter drawer becomes the desktop's Workspace options: hosts, projects with search, group,
sort, project order, card layout and properties, card display, and every filter. Each option
renders through a table keyed on the shared option keys, so a new desktop option fails to compile
until the phone has a control. Sort and group pickers use the desktop's words; Repo reads Project.

* fix(mobile): Workspace options sheet review fixes

Hosts read as on the desktop (none checked while all show; the last shown host is disabled).
Project order falls back to the served value until the shared one is read. Card layout and card
style changes now push to an open phone. Pick-one rows read as radio buttons. Simpler wiring:
flat card state, the preset flag rides the patch, shared choices used directly.

* fix(mobile): reset project order on host switch; simpler preset-flag handling

* fix(workspace-options): all hosts and clear reach the desktop; one filter key list drives every surface

Host visibility: the legacy host scope fallback moves to shared code; the phone reads it, and the
host resets the scope whenever a client writes the host list, so All hosts and Clear stick.
An older desktop that keeps the other-client filter to itself has its marks treated as hide.
Badge counts, is-anything-filtered, Clear and reveal derive from the shared filter keys on the
desktop sidebar and the kanban filter menu. The phone uses the desktop's group and sort values,
holds its view in one state value, and reads its UI fields from one shared list.

* feat(workspace-options): Status and PR grouping read the shared tables

The phone groups by status with the desktop's status helpers and default catalog (its own copy
had the defaults in reverse order) and by PR with a shared lane table, so its labels now read
In review / In progress as the desktop's do. The desktop's PR lane decision ends in the same
shared state-to-lane step.

* feat(mobile): phone rows follow Card layout, Show properties, Card display and agent activity

One shared helper (getWorktreeCardDetails) decides which card details show for the chosen
properties and layout; the desktop card's flags and the phone row both read it. The phone row
hides the PR badge, note/issue/Linear glyphs, host badge and agent rows the desktop turns off,
and the Full list agent layout shows every agent row.

* test(sidebar): folder rows survive restrictive filters; create grace ignores non-finite times

* fix(mobile): write the host scope with the host list, and wait for the first read before forcing the other-client filter

An older desktop narrows back to its legacy single-host scope when the phone
writes only a null host list, so the phone now also writes the scope for a null
or one-host list. The other-client fallback waits for the first ui.get so a new
desktop's marks don't hide rows before its filter value arrives. Inline the
clear-filters key list and note the PR state asymmetry.

* test(mobile): re-record view-settings goldens for the unknown other-client state

* fix(mobile): let a tall bottom drawer scroll on the page

The drag wrapper around the drawer's ScrollView had no flex limit, so it grew to
its content and the sheet clipped it instead of scrolling. The full Workspace
options sheet is the first drawer tall enough to hit this.

* fix(sidebar): reuse the palette's default host instead of reading the focus setting again

* fix(sidebar): read the host options once in Workspace options and pass them to the Hosts item

* fix: parse the ui.get reply into a named type, fix the row memo test's filters, prune the focus-read baseline

* test(mobile): give the project catalog fixture its project order

* feat(mobile): name the options chip Workspace options, as the desktop does

The chip keeps the active-filter count and reads to screen readers with the
desktop trigger's label. The pluralized filter count moves to shared code so
both surfaces word it the same way.

* fix(mobile): show the Workspace options count as a corner badge so the chip row fits

"Workspace options (2)" squeezed the Sort and Group chips to ellipses at phone
width. The count moves to a corner badge, as on the desktop trigger, and the
chip drops its icon, which the desktop trigger does not show either.

* refactor(sidebar): one switch-to-kind table, and fold the leftover helper files back

- The four kind switches come from WORKSPACE_KIND_SWITCH in workspace-kinds;
  the shared evaluator and main's phone-push check both apply it, and the filter
  type derives its switch fields from it.
- git-identity.ts folds back into git-identity-display.ts, as on main.
- SidebarFilterState moves into sidebar-filter-actions.ts; visible-worktree-kinds.ts is gone.
- buildWorkspaceRowPredicate is module-private.

* feat(mobile): Workspace options sheet in the desktop menu's shape

The sheet showed every choice of every option, so the filters sat screens down.
It now follows the desktop menu: Hosts and Projects rows with their value, Group by
as a segmented control, Sort by, Project order, Card layout and Show properties
as rows that open a page of choices, and the filters as switches. Each option's
control (an inline row, or a value plus a page) still comes from one table keyed
by the shared option list. The segmented control is the voice settings one,
extracted so both screens share it.

* fix(mobile): Workspace options review fixes

- A page whose option stops showing is forgotten, so it cannot reopen by itself.
- Only the switch takes the tap on a filter row, so the web page writes once.
- Sections split their rows in one pass; unused heading styles go.

* fix(mobile): neutral filter switches on the page

react-native-web paints an on-switch thumb teal unless given activeThumbColor,
which React Native's types omit. MobileSwitch applies the app's switch colors,
and its web sibling also sets that prop.

* refactor(mobile): one MobileSwitch file, adding the web-only thumb color on web

* fix(mobile): Show properties reads Hover under Compact, as the desktop does

The row's value now comes from getWorkspaceCardPropertiesValue in the shared
options, which the desktop trigger uses too: Hover under the compact layout,
otherwise how many properties show. Card display shows no value, as on the desktop.

* test(mobile): find the renamed Workspace options chip in the page drawer render check

* feat(mobile): drop the Sort and Group chips; the Workspace options chip covers both

The desktop has only its options button, and the sheet now holds Sort by and
Group by. The chips' pickers, their screen state and the sort label go with them.

* feat(mobile): option pages slide in from the right and keep the sheet's height

The main sheet stays laid out under an open page, so the drawer keeps its height
going in and out. The page slides in over 200 ms and back out on back (the row,
or Android's back key), instantly under reduced motion. The reduced-motion hook
moves to platform/ now that two screens use it.

* feat(mobile): Compact rows fold into one title line like the desktop compact card

The phone's Compact layout now moves repo identity into a title-row icon chip
and drops the identity meta row (repo, host, branch or folder path, child
badge), matching the desktop's compact card. The rule lives next to
getWorktreeCardDetails as getWorktreeCardLayout, and the desktop presentation
reads it too so both surfaces share one decision.

* fix(mobile): keep a remote host glyph on compact rows; pass the card layout from the list

Review follow-ups: compact rows show an icon-only host glyph for remote
projects as the desktop compact card does, the list computes the layout once
next to the card details, and a new-card-style row test.

* fix(mobile): option pages fill the sheet and open at its top

- The page layer grows to the pager's height, so no part of the main sheet shows below a short page.
- The drawer takes a scrollResetKey; the sheet passes the open page, so a page opened from a row low on a scrolled sheet starts at the top.
- The main sheet's height is measured under a page too, and the pager state lives in the sheet so it resets when the drawer closes.
- A misplaced doc comment goes back to its function.

* fix(mobile): phone rows show the Jira, automation and CLI glyphs; the options pager forgets a gone page in an effect

* fix(mobile): Jira glyph sits before Linear as on the desktop; Jira reader tests beside it

* feat(mobile): long-press a workspace to see its details

The phone's long-press sheet now lists the workspace's linked PR and
issues (tap to open), who created it, its notes and live ports (Copy),
the phone's take on the desktop sidebar card's hover card. Folder
workspaces get the sheet too, with details only.

The desktop lets phones run workspacePorts.scanHost (read only).

* fix(mobile): details read links the desktop's way and stay one-off

Links come through the shared getWorkspaceAttachments, so references the
desktop keeps apart stay apart, in the host's order with reviews first.
The branch review read no longer takes the host's active-branch slot,
each read re-runs only for its own inputs, a reopened sheet reads afresh,
and Copy's confirmation never outlives the sheet.

* fix(mobile): details clear on close without an effect; lint findings

* fix(mobile): the phone leaves Ports out of Show properties and Card display

* refactor(mobile): clearer names and tests for the phone's hidden card properties

* feat(mobile): details sheet heading carries the origin; no ports

As Jinwoo approved: the sheet heads with the workspace (primary text),
its short branch, and a quiet origin line (automation name, or Orca CLI).
The group below holds only linked reviews and tasks, then notes.
Pin uses the pin icon. Ports are left out of the phone, so the desktop
change that let phones scan them is reverted.

* test(mobile): details section render helper matches its props

* fix(mobile): details review read asks by branch; review merge by number; comments

* fix(mobile): details rows share the action rows' text column

* refactor(mobile): details sheet reads only the row

The sheet no longer fetches. The host stamps two optional row fields:
automationName beside automationCreated, and the branch PR's title, url
and checks on linkedPR (from the GitHub cache it already reads). Older
hosts omit them and the sheet shows what they send. Links match by
provider, type and number; the reference label is one shared helper the
desktop card uses too. Folder rows keep no long-press. One sheet file
plus the pure model.

* fix: review nits on the row-only details sheet

* refactor(mobile): one view state and one settings read for the phone's Workspace options; tighter card glyph wiring

- The phone's Card layout and card style now live in its view state; one parallel settings.get
  reads them with pinned placement beside ui.get.
- The Card layout preset flag is a write option, no longer a view field or a change-event field.
- getWorktreeCardDetails reads the raw desktop settings and resolves the layout once; the desktop
  card computes it once in its foundation.
- The row computes its glyph presence in one place; Ports is hidden through one set.
- Drops the vanished-page effect, the native-only a11y props and the web-only switch branch.

* fix(mobile): the list settings read applies each half on its own, with tests; pinned placement changes announce themselves

* fix(mobile): compact host glyph tests for older rows and the new card style; truer comments

* fix(mobile): Card layout writes the setting before its preset; Card display names folder paths when the desktop has folder workspaces

* fix(mobile): match the branch PR by repo and link, not number alone
2026-10-15 23:01:45 -07:00
OrcaWinandOrcaWin a8e3734e11 test: remove setup sequencing shell inspection seam (#29399)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-15 22:58:40 -07:00
OrcaWinandOrcaWin 144aac00cc chore: update Vite, Tailwind, and build tooling (#29345)
* chore: update build and test toolchain

* fix(build): isolate shared code from CLI entry overwrites

* fix(build): preserve main startup asset paths

* test(build): override resolved Vite 8 fixture inputs

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-15 22:57:00 -07:00
OrcaWinandOrcaWin 7f3677ec13 test(mobile): remove repeated presence and restore cases (#29457)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-15 22:49:21 -07:00
OrcaWinandOrcaWin 955c7a95a6 perf(css): exclude renderer tests from Tailwind scanning (#29428)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-15 22:46:40 -07:00
Jinwoo Hong 991e2dda26 feat(layout): move, reorder and split tab groups through main behind the layout-owner build switch (#29313)
* feat(layout): add the dark layout.apply channel and route pane splits through main behind ORCA_LAYOUT_OWNER

A build made with ORCA_LAYOUT_OWNER=main registers layout.apply: main applies the
window's layout command to the stored partition through the shared applyLayoutCommand,
minting new ids. A user split then waits for main's answer and mounts the pane id main
minted. Every other build substitutes 'window', so neither branch is compiled in.

* fix(layout): write only the records a layout.apply command touched

A load-then-save rewrite also normalizes records the command never touched. The command's
effect is now the diff between the save before and after it, written onto the stored
partition record by record. A split whose main reply says a paired server holds the tab
splits as today.

* fix(layout): write a load-supplied record whole and check that patched partitions reload to the command's result

* feat(layout): close, rename and equalize panes through main behind ORCA_LAYOUT_OWNER

In a layout-owner build the window's pane close, title change and equalize go through
layout.apply and the window shows main's answer. A window pane close keeps the legacy
close's rules: the tab's last pane is refused, a pane restarted since main read the close is
left alone, every partition holding the tab closes it, and the tab's remote session id follows
the remaining pane. The rules sit behind a command field only layout.apply sets.

* fix(layout): close the tab when a racing pane close leaves the window one pane

* fix(layout): leave an unmounted tab alone when main refuses its last pane

* feat(layout): move panes and save divider ratios through main behind ORCA_LAYOUT_OWNER

Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125

* test(layout): a taken pane drop whose pane closed does nothing

Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125

* test(layout): pin the window's pane move to main's saved tree, and the drops main cannot take

Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125

* fix(layout): close a user's pane even if its terminal restarted, as today

Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125

* test(layout): pin the restarted-pane close directly, not through a made-up profile state

Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125

* fix(layout): leave a split to today's path only when the workspace's row names a paired server

The split switch point asked which host to route to with a helper that falls back to the focused
server, so a focused server could claim a workspace whose row names no owner. Use the same owner
test the tab mirror uses.

* test(layout): type the split owner mock without a cast

* test(layout): type the layout-owner command mocks with the real signature

* test(layout): type the gesture command mock with the real signature

* fix(layout): close the dark path's gaps: unmounted split panes, refused or failed splits and moves, ratios during a move, release guard

- A pane main saved but the window could not mount (source pane or manager gone during the await)
  is closed again in main, so it does not come back on restart.
- A split main refuses because its copy lacks the workspace, tab or pane, or a failed call, splits
  as today; a refused or failed move moves as today.
- Divider ratios committed while a move is in flight wait for the window's move, then go to main.
- The orcad bundle gets the ORCA_LAYOUT_OWNER define when the switch is on.
- Packaging and the orcad template refuse ORCA_LAYOUT_OWNER=main and any output a dark build marked.
- workspace-layout-record-patch.ts: no non-null assertions; plain comment for the map depth.

* test(layout): count a split pane as mounted from the split mock's own results

* fix(layout): act as today whenever main lacks the target or the call throws, not just for splits

- sendPaneCommandToLayoutOwner runs asToday for workspace/tab/pane_not_found and a thrown call, so
  close, rename and equalize fall back like split and move; a handler that throws no longer runs the
  fallback too.
- A close main refuses as its last pane, while this window still has others, closes as today.
- A move whose local step throws still releases held divider ratios.
- Drop the orcad output marker: nothing read it; the template build refuses the switch itself.

* test(layout): plain names for the last-pane fallback test and the marker comment

* docs(layout): say plainly what an indeterminate write can leave behind

* fix(layout): finish the main merge with this branch's versions of the layout-owner files

* test(build): copy the layout-owner build guard into the compiler-cache checkout fixture

* fix(layout): write a folded legacy row in its loaded form when a layout.apply command touches its claim

A legacy terminal row the load folds into a tab-bar tab stays stored as is. Closing the tab it
repeats, or giving it a tab-bar entry by moving it, let the next load resolve the claim the other
way: the row came back or took the other tab's terminal. Such a command now writes the row as
the load had it; every other record still stays as stored.

* feat(layout): accept tab moves, group splits and group sizes on layout.apply

Group sizes have no tab, so they go to the workspace's routed partition.

* refactor(layout): move the window's layout.apply sender out of the terminal pane folder

Tab groups send through it too; it now also acts as today when main lacks the group.

* feat(layout): reorder, move and split tab groups, and save group sizes, through main behind ORCA_LAYOUT_OWNER

A tab drop and Move Tab to Split wait for main's answer and then show main's placement, with main's
new group id; group divider sizes are drawn while dragging and saved by main when the drag ends.
Closing an empty group needs no command: main never holds an empty group.

* fix(layout): review fixes for tab moves through main

- Read a folded row's terminals with the loader's own claim rule, so rows sharing only an SSH
  session are covered too, and drop the row's remote session entry with it.
- The record-patch test allows a folded row only in its loaded form.
- A tab drag ends at once; main's answer shows the move after.
- A lone group main moved whole is placed so only when this window's group is lone too.

* refactor(layout): adopt only a folded row's pane layout beside its row

A row's remote session entry is carried through load and save unchanged, so adopting it from the
load changed nothing (the earlier commit's message overstated it).
2026-10-15 22:43:29 -07:00
OrcaWinandOrcaWin 29c825d72a test(runtime): observe fresh mail pointer receipt without fixed delay (#29447)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-15 22:43:07 -07:00
Neil e65a1dd553 Keep idle Claude sessions selected during nested Codex work (#29386)
* Preserve idle foreground Claude session ownership through nested hooks

* Discard obsolete hook probes and cancel queued restarts
2026-10-15 22:36:36 -07:00
OrcaWinandOrcaWin a8f2c65df5 test: remove duplicate terminal query authority checks (#29422)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-15 22:25:30 -07:00
Neil 4462001ba0 fix(orcad): never launch the orca-ide bash shim as the headless browser (#29363) 2026-10-15 22:24:30 -07:00
OrcaWinandOrcaWin ac0d8c4fd0 test: consolidate CLI regressions and simplify IPC lifecycle proof (#29298)
* test: batch lint fixtures and remove repeated runtime scan

* test(cli): consolidate owner coverage and remove false decoding proof

* test(renderer): retain IPC lifecycle behavior without copied inventories

* test(git): keep checkout progress proof within file limit

* fix(lint): classify the shared mobile chat harness as test support

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-15 22:21:36 -07:00
Neil da099c3201 fix(worktrees): remote hosts honor the client's Nest Workspaces setting (#29404)
* fix(worktrees): remote hosts honor the client's Nest Workspaces setting

The client now sends nestWorkspaces on worktree.create and the host uses it for the path layout, falling back to its own setting for older clients.

* fix(worktrees): carry the chosen layout through name retirement and metadata
2026-10-15 22:03:55 -07:00
Neil 90849ed0c6 Enable Claude plugin images and bounded terminal file transport (#28200)
* fix: render Kitty Unicode placeholder images with bounded ownership

* Enable managed Claude plugin images and bounded host file transport

* Preserve isolated Claude evidence and paired restart diagnostics

* Resolve Windows workspace aliases before WSL image reads

* Handle split remote terminal identities and improve WSL file-read diagnostics

* Reuse native drive paths for WSL image files and preserve guest temporary roots

* Apply native Windows path restrictions to mapped drive image sources

* Preserve WSL participation evidence across image checks

* Keep WSL drive directory containment case-sensitive after canonicalization

* Negotiate managed Claude image support with the launching renderer

* Make image repaint checks independent of display profile and shell output

* Capture repaint diagnostics only after the pixel assertion fails

* Allow floating terminal image reads from host temporary folders

* Keep image stack independent of unrelated CI runner and mobile lint settings

* Reuse worktree argument builder for terminal viewer context

Remove duplicated mapping and keep the RPC method table within its existing line limit.
2026-10-15 21:43:48 -07:00
Neil 9c863a6f83 fix(ssh): read the macOS machine id from IOPlatformUUID and make the shared-home claim exclusive without hard links (#29259) (#29284) 2026-10-15 21:22:05 -07:00