* test(sidebar): the desktop window and a feed reader draw the same list (dark)
* fix(sidebar): a feed reader lists a paired server's folders and nested workspaces as the window does (dark)
* fix(sidebar): the paired-server shadow compares the lineage the server's check keeps (dark)
* fix(sidebar): paired-server rows carry the unread flag Smart Done reads (dark)
* refactor(native-chat): move the slash picker builder and upload chunking to shared
Mobile builds the same skill and command list as desktop.
* feat(mobile): redesign the chat composer with file attachments and inline skills
* fix(mobile): the composer field owns its input style
The page's font-size census cannot follow a style passed in as a prop.
* refactor(sidebar): the stamp-sorted window list runs no settle, latch or saved order (dark)
With ORCA_SIDEBAR_INPUTS=main the window's sort hook is a pure read of the
rows' stamps: no settled-epoch store listener, no Smart latch, no saved Smart
order writes, no class telemetry over the old attention map. Switch off: the
existing hook, unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): stamp sort hook reads no live sources; stronger switch-on tests
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): clear the order-save mock between tests
Co-Authored-By: Claude <noreply@anthropic.com>
* wip: main keeps sleeping retention only
* fix(sidebar): stamp sort reads a shared empty-sources constant, not a renderer layout literal
The layout-writer ratchet counts layout field keys written in the renderer;
the empty sources object now lives beside SmartSortSources in shared code.
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(sidebar): main keeps sleeping retention only; no order of its own (dark)
Clients sort by the owning host's stamps, so main's temporal owner loses its
settle window, Smart latch, create grace and catalog-listing classifier, and
the shadow check stops comparing order. Retention, its clock and its shadow
comparison stay.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(sidebar): every retention listing shares one sequence; publish after the first
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): the first listing bumps the retention revision
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* chore(layout): the drift check's ignore comment names every main-only kind (dark, layout-owner build only)
* fix(layout): a quit stage retried after a failed one still sends the resume records (dark, layout-owner build only)
* feat(layout): the window's local layout stream reopens after a loss, and its snapshot redraws every workspace (dark, layout-owner build only)
* fix(layout): a command whose target main's copy lacks is a logged refusal, not the window's own change (dark, layout-owner build only)
* feat(layout): frames keep each tab the window drew, closed or moved as drawn until main answers; the global frame hold goes (dark, layout-owner build only)
A refused draw-first terminal create is closed (its terminal stopped) at the refusal. The
activation create no longer draws main's tab itself while the stream is down: the reopened
stream's snapshot does.
* fix(layout): a tab move or drag-out the frame already drew is not placed again by the window (dark, layout-owner build only)
Also holds the group sizes the window drew until main saves them.
* test(layout): with frames flowing, a pin promotes a preview tab once and tab-change bookkeeping runs once (dark, layout-owner build only)
Also states that the held-tab overlay builds frame slices only.
* test(layout): read the rename's title without a cast
* chore(layout): the drift check's header lists the same ignored records as its inline comment
orcad-state-*, orcad-windows-host-* and orcad-machine-* leave src/main/ssh. git mv plus import and
path-literal rewrites, including the Windows host lane's test path in its workflow and PowerShell
runner.
orcad-managed-*, orcad-runtime-*, orcad-stdio-bridge-*, orcad-decommission-* and orcad-rollback-*
leave src/main/ssh. git mv plus import and path-literal rewrites; two e2e route regexes learn the
new folder. orcad-runtime-maintenance stays for now because open #29834 edits it.
orcad-remote-*, remote-install-*, remote-node-runtime-store-*, remote-versioned-install and
remote-deploy-timing leave src/main/ssh. git mv plus import and path-literal rewrites; one e2e route
regex learns the new folder. orcad-remote-deploy and orcad-remote-node-runtime-report stay for now
because open PRs #29834 and #29829 edit them.
* fix(mobile): break the transport and terminal-document import cycles and gate them after mobile's install
client-context re-exported the host-client hooks, which import client-context back: the re-export
barrel is gone and importers (and their test mocks) name host-client-hooks directly. In the terminal
document, the cell-metric getters move to a leaf cell-metrics module and select-mode entry/exit to
selection-mode, so fit, transform, overlay and range no longer import each other. pr.yml lints imports
before mobile's install, when mobile's tsconfig can't load, so Mobile verify now runs the same import
rules over mobile after its install with zero warnings allowed.
* fix(mobile): the browser-pane render harness imports useHostClient from host-client-hooks
Its route source is a template string, so no typecheck saw the removed client-context re-export; the mobile web bundle check did.
* fix(ci): the e2e typecheck ratchet counts the same with or without mobile's install
Specs reach mobile/src/transport, whose expo-crypto and async-storage imports resolve only after mobile's install and then load React Native's globals (numeric timers, a required NODE_ENV), adding errors in unrelated specs. An empty stub pins both, so the static job reads 232 either way.
First slice of the desktop side of managed orcad leaving src/main/ssh. git mv plus import and
path-literal rewrites; host-lane workflow filters and the cross-host prefix list gain the new folder.
Moves legacy-relay-bridge/ plus the relay-only census and endpoint files into ssh/legacy-relay/,
so the 2027-01-15 retirement deletes one folder. Live install, exec and framing modules that only
carried the relay name become remote-*. git mv plus import, path-literal, routing and workflow
path-filter rewrites only; no remote file name or path changes.
* fix(layout): an SSH pane's terminal binds in its own session partition (dark, layout-owner build only)
Main keeps its own layout over a window save, so a binding only a save carries is lost. A window
or runtime spawn of an SSH pane bound in the local partition; it now binds where the pane's layout
lives.
(cherry picked from commit beb2275b45)
* test(layout): the SSH binding test uses the shared spawn commit dependencies
(cherry picked from commit 672b641897)
* fix(layout): main moves a tab between a local and an SSH workspace itself (dark, layout-owner build only)
moveTabToWorkspace across two partitions was refused as other_partition and left to the window's
save. Main now reads both partitions, moves the tabs as within one, and writes both in one durable
mutation.
(cherry picked from commit ee27a0bb07)
* chore(layout): one binding write for the partition choice; shipped builds keep their exact replaced-binding read
* fix(layout): window saves keep main's layout and give it only the window's view and facts (dark, layout-owner build only)
windowViewSaveForMain/windowViewPatchForMain replace windowSaveForMain: main's own layout joined
with the window's view, facts and carried fields, written only into the records that change. A
paired server's partition passes through. A visited workspace main holds keeps its terminal row
(D1). The P10 pending case is on.
(cherry picked from commit 0c2fc15974)
* fix(layout): the save divergence detector becomes a report-only drift check behind its log (dark, layout-owner build only)
It no longer wraps the write: it compares the window's raw save with main's layout model, and
only when ORCA_LAYOUT_SAVE_DIVERGENCE_LOG is set.
(cherry picked from commit 6bc1e5556f)
* fix(layout): tab reconcile and terminal reconnect stop writing tab structure (dark, layout-owner build only)
Main's frames draw the tab bar and rows: reconcile still returns its counts but writes nothing,
and reconnect still advertises restored terminals but leaves the rows to main.
(cherry picked from commit ef8eb625ca)
* fix(layout): a layout command main cannot apply is a logged refusal, not the window's own change (dark, layout-owner build only)
other_partition, a thrown call, and host_not_owned for a workspace that is not a paired server's
are refused and logged: a window save no longer carries the change, so drawing it would last only
until the next frame. A twin main may route to a paired server keeps today's path, as does the
first terminal's host_not_owned fallback.
(cherry picked from commit 2c513b2d10)
* fix(layout): the window's refusal code type keeps main's host refusals
(cherry picked from commit 36d46bdcc2)
* fix(layout): the quit checkpoint records only the quit capture's resume records in main (dark, layout-owner build only)
Main keeps its own resume records over a window save, so the quit capture's changes, which reach
main as async commands, could miss the quit. The capture now sends only what it changed with the
synchronous stage, and main writes them through the record-only sleep command before staging; a
wake main recorded that the window had not seen stays.
(cherry picked from commit 2a0f433c34)
* test(layout): the quit capture test builds a typed resume record
(cherry picked from commit d92d1246ee)
* chore(layout): one layout write per save handler
* chore(layout): shipped builds keep their exact quit capture
* fix(layout): the drift check compares structure only, not records main writes itself (dark, layout-owner build only)
Pane bindings and resume records, default-tab marks, closed-tab records and terminal row owners
are main's own writes; a save that has not caught up with them asks main for nothing, so they are
no longer reported.
(cherry picked from commit 93e8b2627c)
* test(layout): relaunch first-terminal cases, and the side-by-side resume case moves its tab the way the window does
(cherry picked from commit ab381332b1)
* test(layout): the relaunch case also checks a reopen that cleared read-only
(cherry picked from commit bbf3d040c8)
* docs(layout): the drift check names the record kinds it ignores
* revert(layout): drop the SSH binding partition change; SSH terminal starts are refused before any binding is saved
getProvider refuses every non-local host, so neither spawn path reaches persistPtyBinding for an
SSH pane, with the switch on or off.
* test: run A2's two real-SQLite session tests in the Node runtime project
tests/ is typechecked by nothing in CI, so the Windows CLI matrix spec imported a helper that never existed (#30231). Per-file error counts under tests/ (tsc -p config/tsconfig.e2e.json) may now only go down.
Deletes ssh-system-fallback.ts and the re-export lines in ssh-config-parser.ts, ssh-connection.ts,
system-ssh-file-binary-transfer.ts and ssh-remote-powershell.ts; importers name the real modules.
SshConnection tests mock each real module, keeping the parser stub that holds ~/.ssh/config out.
Deletes remote-layout-snapshot/index.ts and workspace-layout/old-client/index.ts. The compat-boundary
check now lists each folder's entry modules, so deleting the folder plus its listed importers'
import lines still removes the path.
The folder is the cloud relay that reaches phones; under its old name it read as the legacy SSH
relay. git mv plus import and path-literal rewrites only.
Worktree ids built as `${repo.id}::${path}` by hand are how one worktree got two
spellings (#30150). New code composes them through src/shared/worktree/id.ts; the
ratchet counts the remaining hand-built ids per file (18) and may only go down. Six
byte-identical `${repo.id}::${repo.path}` sites move to getRepoMainWorktreeId.
Gate evidence prose and evidence runs record what ran then, so the check reads only each gate's
commands and test files (baseline 53 -> 14). Four evidence-pinned historical commands that named
deleted tests are removed from their gates' commands and their runs point at the successor via
currentCommand, the manifest's existing mechanism. One live command drops a deleted updater test.
The managed-SSH catch-all route is renamed remote-sources and its test joins the route contracts.
Render committed branch changes as virtualized SectionList rows for clean and dirty working trees, keeping all files accessible without delaying Back. Share the existing sleek scrollbar rules with mobile web pages and retain explicit hidden-indicator settings.
Fixes#30170. Review reference: #30171; thanks to @zhangxaochen for the diagnosis and initial proposed fix.
Co-authored-by: zhangxaochen <zhangxaochen@163.com>
* ci: ratchet src/ path literals in CI config, workflows and e2e code that name no file
* ci(path-literal): resolve relative imports exactly
* ci(path-literal): resolve against tracked files only; read ./src imports
* ci(path-literal): scan src/packages and src/types references too
* ci(path-literal): skip paths quoted in comments
* ci(path-literal): keep a recursive glob's boundary
* ci(path-literal): strip comments with a string-aware scan; any import occurrence is exact
* ci(path-literal): imports need a module or index; relative globs and folders stay prefixes
src/main/ipc/worktree-path-comparison.ts kept its own areWorktreePathsEqual and
dedupeWorktreesByPath. Its Windows branch lowercased a WSL UNC path's ext4 tail, so two
case-twin WSL checkouts merged into one row in removal safety, folder-upgrade dedupe and
the SSH listing fallback. Every caller now uses git/worktree-path-comparison, which gains
the macOS /private/tmp fold the ipc copy had. worktreePathComparisonKey keeps its exact
bytes (retirement namespace keys persist it) in worktree-path-comparison-key.ts, and the
worktree-logic re-export goes.
Removes modules with no importers on main (and their tests), the unused
RELAY_DEPLOY_TEARDOWN_TIMEOUT_MS constant, and the ssh-connection-utils
re-export of ssh-auth-resolution that only its own test used.
No released build ever passed a certificate to WebSocketTransport or set the pairing TLS
fingerprint; only ws-transport's own tests used tls-certificate.ts. The tests now run over ws://.
The nudge request parsed and stored a rollout block, but no build ever read a flag from it.
Drops the module, its call site, its tests, and the e2e env that fed it.
Reverts #30185 so the separate installer project can proceed. The restored shared command already pins skills@1.7.2 for CLI, desktop updates, and setup commands.
* fix(startup): shell PATH probe falls back and retries when the shell cannot launch
* chore: drop hydrate-shell-path from the process-host direct-import baseline
* security: ship a locked skills installer instead of running npx
* chore: remove retired skills process import exception
* test: include the locked installer in packaged layout fixtures
* test: reuse the real installer builder for package fixtures
* perf(typecheck): pnpm tc uses the pinned Bun checker when available
bun check (Bun 1.4.3, TypeScript 7.0.2 parity, already used by CI) checks the
node project in ~29 CPU-s / ~2 GB peak vs tsc's ~98 CPU-s / ~16.6 GB. tsc's
incremental cache rarely helps agents: noEmit builds store no declaration
signatures, so the first edit to any file after a cold check re-checks every
transitive importer (a comment in src/shared/constants.ts re-checks all 18k files).
pnpm tc / tc:node / tc:web / tc:cli now pick the Bun from config/.bun-version when
it is on PATH and fall back to tsc with a one-line hint otherwise;
--compiler=native forces tsc. The pinned-Bun probe is shared with the
localization extraction check.
* build: pin Bun as a devDependency and make it the only default checker
The PATH probe left anyone without the exact pinned Bun (including a 1.4.2
global) on tsc's expensive path. bun@1.4.3 is now a devDependency, its
postinstall is allowed, and pnpm tc / typecheck:ci run that binary. A missing
binary is an error pointing at pnpm install rather than a silent fallback;
--compiler=native still selects tsc. A test keeps the devDependency equal to
config/.bun-version.
* ci: link the pinned Bun after the script-free install
--ignore-scripts leaves node_modules/.bin/bun as bun's postinstall stub, so the
typecheck runner found no binary and any pnpm-run script calling bun would hit
the stub before setup-bun's copy. pnpm rebuild bun runs only that allowed build.
* feat(layout): main drops the layout of workspaces the catalog no longer validates (dark, layout-owner build only)
* rename the paired-host check so the switch-off bundle keeps its names
* a repo id two hosts register skips the listing purge
* fix(sidebar): main takes its row baseline from the session's catalog and skips unchanged listings
* fix(sidebar): read catalog listings the way the window's merge does
* fix(sidebar): a seeding listing re-samples, and listings compare with the window's equality
* refactor(shared): move the pane title sanitizer to shared, unchanged
So main's layout Loader can apply the same rule the window applies on load.
* fix(layout): pane titles stay layout in a layout-owner build (dark)
The window stops writing a revealed terminal's tab name as its pane title,
and main's Loader drops a lone pane title that only repeats the tab's name,
as the window's load does. Both behind ORCA_LAYOUT_OWNER_IS_MAIN.
* fix(layout): the Loader compares a lone pane title with the tab bar's quick command label too (dark)
As the window's row hydration does when the row has no label.
* fix(layout): the Loader compares a lone pane title with the tab bar label, the live title (dark)
* feat(sidebar): dark phone feed of the sidebar inputs (sidebarInputs.read)
* fix(sidebar): feed review: fresh read on ready, notices kept per revision, one flush owner, closed connections never subscribe
* fix(sidebar): feed skips values JSON cannot hold; a failed read forgets its notice
* fix(sidebar): name the feed batch key by where the batch starts from
* fix(ssh): a tab closed while a managed host is disconnected stays closed after Connect
#30130 made a user's Disconnect mark the host's managed server manually
disconnected, so the desktop now answers its calls itself with
runtime_manually_disconnected. The tab close path read that as the host's
answer and dropped the close instead of holding it for replay, so the tab
came back on reconnect. Treat it as an undelivered close, and route the
Disconnect sources to the managed-orcad Docker lane.
* test(e2e): close the disconnected host's tab the way a user does
The journey called the store's bare closeTab, which sends only terminal.close
and holds nothing; it passed only because the disconnected host used to be
redialed in the background, which #30130 stopped. Click the tab's X instead,
the path that holds and replays an undelivered close.
* test(e2e): the shared-home spec tells a host's own server from a PID that only collides
Each Docker host has its own PID namespace, so the PID host A records in the
login-home slot can belong to an unrelated live process on host B; kill -0
alone then read it as B running from A's slot. Count a recorded PID only when
its process runs from that slot.
* fix(serve): orca serve on orcad serves the browser client and answers plain HTTP
orcad, the default local orca serve host since the compatibility launcher,
was never handed the app's web client directory, so its HTTP server had no
request listener: every GET hung and no Web client URL was printed (#30100).
The CLI now passes the app's unpacked out/web to orcad via --web-client-root,
a server without a web client answers plain HTTP with 404 instead of hanging,
and packaging fails when the unpacked web client or a file it loads is missing.
* test(serve): pin orca serve handing orcad the app's web client root
* refactor(sidebar): move the Smart comparator and attention scoring to src/shared/sidebar-list
Main needs them to own the frozen order. Renderer helpers they depend on move with
them; the window imports the shared copies. No behaviour change.
* refactor(sidebar): one shared ranking for the frozen sort order
The window's sort memo and the palette's Smart sort now call rankWorktreesForSort,
so main can produce the same order from its own records. The pure state-history
step moves to shared for the same reason. No behaviour change.
* feat(sidebar): main keeps the list's time rules on one clock, checked against the window in dev and test
* test(sidebar): fake-clock parity of main's temporal owner against the window
* fix(sidebar): main restarts the settle window only where the window's sortEpoch bumps
* test(sidebar): type the owner harness without an assertion
* fix(sidebar): main's temporal owner stays idle until a dev or test window begins a session
* fix(sidebar): main reads Hide sleeping from the field the window persists
* refactor(sidebar): keep the begin params in their own contract module so the snapshot schema compiles out
* chore: restore files the merge's formatter rewrote to their origin/main content
* fix(mobile): a compact queue: shorter header, a capped scrolling list, and an editor that fits the card
* fix(mobile): keep queue actions visible and reveal new cards
* test(mobile): allow shared SVG host fixture
* fix(mobile): queue header and editor controls grow with large system text instead of clipping
* fix(mobile): at large text sizes the queue header's buttons wrap below its label instead of squeezing it out
* fix(mobile): at large text sizes the queued editor's Cancel and Save wrap below its heading instead of truncating it
* fix(mobile): at large text sizes the queue header's and editor's buttons take a line of their own
* fix(mobile): the large-text button lines wrap when one button is all a small phone's line holds
* test(mobile): the composer tray test's react-native stub reports the text scale
* fix(mobile): while a card is edited at large text, the paused header's button line gives its height to the editor
* feat(mobile): name the desktop under the Tasks title
Tasks can open on any connected desktop, so the header now shows which
one, in the same subtitle line the host screen uses under its title.
* fix(mobile): reserve the Tasks desktop line so the title does not jump
The subtitle line now holds its height before the host profile is read,
a failed read leaves it blank, and the tests cover a desktop switch
while a read is in flight.
* test(mobile): re-measure the page script sweep after the Tasks subtitle
The Tasks route now shares the host screen's styles, so it emits one
script fewer (77 to 76 at its prefix, 80 to 79 for the tree). Re-swept
every route prefix; the worst route step is now 11.
* fix(native-chat): a Codex chat is Working only while Codex holds the send
Working read 'a send still pending delivery', so any echo or idle signal
that never arrived (a hook-blocked prompt, an answer naming no turn, idle
before a timed-out send was written) kept the chat Working, Stop up, the
queue frozen and /compact refused until the agent exited.
Codex now reports which sends it still holds: unanswered, answered into a
turn that has not ended, or answered into none while a status read after
that answer says the thread runs. Replies and statuses are ordered as they
are read, so a stale status cannot release a newer send. The host reads
that set (plus the short window while it hands a send over) for Working,
publishes a send the child stopped holding as in doubt on every page and
frame so older clients agree, and republishes when the set changes with no
row. The Codex idle latch and its release are removed.
* fix(native-chat): name the handover journal type; reverse without mutating
* test(native-chat): a lost Codex answer while idle is no longer the chat's work; run the held-sends test with the database runtime
* fix(native-chat): bound work updates and own command handovers
* refactor(native-chat): reuse session delivery plumbing
* test(native-chat): allow separate ownership restatements
* test(wire): resolve the release tab predicate after relocation
* test(native-chat): reuse existing promise and empty-batch primitives