Skip to content

[upstream #30555] fix(mobile): let the long-press finger drag the new terminal selection - #345

Open
innocarpe wants to merge 8492 commits into
mainfrom
ios-selection-long-press-drag-20261019
Open

innocarpe wants to merge 8492 commits into
mainfrom
ios-selection-long-press-drag-20261019

Conversation

@innocarpe

Copy link
Copy Markdown
Owner

Portfolio mirror of my contribution to upstream stablyai/orca.
Exhibition only — the real review/merge target is upstream.

Upstream

Summary

ELI5 On the phone, long-press a word in the terminal and keep your finger down: you can now drag to extend the selection right away, like selecting text in other iOS apps, instead of the terminal scrolling under you. ## What Changed armLongPress in tap-dispatch.ts: afte

Note

  • Do not merge this into innocarpe/orca main until the upstream PR is merged.
  • After upstream merges: sync fork from upstream, then close this mirror PR.
  • This open PR exists so visitors see in-flight work on this fork's Pull requests tab.

@innocarpe innocarpe added the bug Something isn't working label Oct 19, 2026
AmoabaKelvin and others added 29 commits October 20, 2026 02:09
…hen another relay lists the same id (stablyai#30870)

* fix(ssh): a legacy relay pane stays with the relay that runs it when another lists the same id

Co-authored-by: Claude <noreply@anthropic.com>

* fix(ssh): keep an exit reported before a pane's owner is known, and re-list a contested pane

Co-authored-by: Claude <noreply@anthropic.com>

* fix(ssh): a legacy relay id's owner survives reconnects, is adopted only once every relay answered, and a contested id is never served

Co-authored-by: Claude <noreply@anthropic.com>

* fix(ssh): pin a legacy relay id's owner only on a listing that accounts for every relay

Co-authored-by: Claude <noreply@anthropic.com>

* test(ssh): the cross-version bridge test passes the target's owner ledger

Co-authored-by: Claude <noreply@anthropic.com>

* fix(ssh): an epoch-scoped relay id keeps its sole lister as owner on an incomplete listing

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
…clicking them (stablyai#30966)

Fresh sandboxed iframes run in their own renderer and can finish loading
before the browser routes pointer input to them, so Playwright's click
landed on the parent and no popup opened. Hover-poll until the new frame
sees trusted input before clicking, in one helper shared by the adapter,
native and background popup probes.

Share the cold first-frame budget from stablyai#30854 with popup-origin-chrome,
which still waited only 10s for the same one-time GPU startup.
…timer-drift budget (stablyai#30968)

The main-pressure scenario holds background PTYs behind the ACK gate on
purpose, but it passed the unloaded 250ms drift budget while every other
under-load scenario uses 3.5s. Two unrelated PRs failed it today at
252ms. The 75ms median key-latency check still guards responsiveness.
…stead of faking timers (stablyai#30967)

Under CI's bun runner the fake-timer loop ran out of turns before the retries finished, and the
test hung to its 30 s timeout.
…eserve (stablyai#30972)

evaluateWatchSample's two early returns (waiting for reserve to apply, and
a cell flipped back to db by hand) carried no pages, so watchSegment threw
'result.pages is not iterable' and the run ended as failed instead of
stopped (c29, 10-20 02:25Z, run 39354895994).
…inal agent finishes or needs input (stablyai#30602)

* fix(notifications): a host with no window pushes phones when a terminal agent finishes or needs input

* test: name the cast rationale in the host-delivered notification test

* fix(notifications): wait out a finish's quiet window and match the window's request by its news id

* fix(notifications): announce a lead agent's finish while background work runs, not its all-clear

* fix(notifications): announce a lead agent's finish at once and keep it through the all-clear

* fix(notifications): the host pushes only while no window runs, with the window's rules; a session boundary is no finish

* chore: drop formatter churn in files this change does not touch

* test(e2e): record the window's own notification requests in main

* fix(notifications): a desktop whose window has not shown stays silent; seed restored panes as the window does

* fix(notifications): seed a restored pane fully, and read a listed worktree's own repo
…nterval drift (stablyai#30971)

In the hidden E2E renderer the 16ms interval probe measured timer
scheduling: a CI trace shows a 268ms interval gap while every keystroke
echoed within 45ms. Long-task timing reports how long the main thread was
actually blocked, which is what the 250ms and 3.5s budgets are meant to
bound. The report field keeps its maxTimerDrift name so the perf report
pipeline still reads and budgets it.
…'t reach it (stablyai#30945)

* fix(ssh): let the host download the pinned Node when this computer can't

* fix(ssh): keep both host download tools inside one time budget

* fix(ssh): use timeout for wget only where it runs as expected
…ed port forwards (stablyai#30932)

* fix(ssh): a port forward never starts on a connection its teardown already disposed

* fix(ssh): a Disconnect-cancelled edit still resyncs the renderer, and tests model a disposed connection faithfully
…#30853)

* feat(native-chat): sync per-agent permission defaults with explicit host support

* test: include native permission capability in host settings mock
…eping the adopted tabs (stablyai#30942)

* fix(runtime): a failed orphan adoption write rolls back instead of keeping the adopted tabs

The adoption rollback ran outside the store's durable transaction, so the store
rebased it like a stale renderer write below the topology fence and kept the
adopted tabs, layouts and incarnations; the next save then persisted them.
Run the adoption through runDurableMutation, whose rollback phase skips that
rebase, and refuse if a queued write changed the session it was checked against.

Co-authored-by: Claude <noreply@anthropic.com>

* fix(runtime): an adoption into an unwritten host partition is not refused as a conflict

Co-authored-by: Claude <noreply@anthropic.com>

* test: run the orphan adoption rollback test in the SQLite runtime project

Co-authored-by: Claude <noreply@anthropic.com>

* refactor(runtime): check and write an orphan adoption inside the store transaction

A snapshot comparison could not see a queued binding write that edits the
session in place. Plan the adoption against the session the transaction
reads instead, so queued writes are validated against rather than guessed at.

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
…y that named them (stablyai#30976)

* fix(worktrees): a sort order and a minted push remote land on the copy that named them, not skipped for a twin

The renderer already knows each id's host when it splits a sort order; it now sends it
(an optional parallel hostIds; older peers ignore it and keep the skip-twins rule). An IPC
git handler knows the host it required, so a minted push remote records on that copy.

* fix(worktrees): a twinned repo re-lists instead of an id-keyed sort patch; client hosts must name a registered repo

* fix(worktrees): a sort order naming an unregistered host skips that id instead of falling back

* fix(worktrees): a moving project's sort order is fenced before any host-qualified read

* fix(worktrees): a client's local sort-order host matches this server's own runtime stamp

* fix(worktrees): an exact host match wins before the own-runtime alias

* fix(worktrees): terminal-spawn materialization serves only a local row and records on the local copy

* test: drop casts from the spawn twin tests

* fix(worktrees): adopt evidence and the spawn hook stay on their host; an unmatched local keeps the id-only rule

* fix(worktrees): adopt evidence reads the host's own metadata rows

* fix(worktrees): reusing a same-URL remote reads ownership from this host only

* fix(worktrees): a server's own runtime-stamped row still materializes its push remote
…tablyai#30970)

* fix(native-chat): link Antigravity sign-in notices to settings

* test(native-chat): use typed failure sentences in settings link coverage

* fix(native-chat): put Antigravity settings link in sign-in sentence
…tablyai#30944)

* fix(updater): avoid macOS background installer throttling

* fix(updater): harden installer scheduling build and diagnostics
… host (stablyai#30862)

* feat(accounts): save MiniMax and GLM Coding Plan keys on the Settings host

The usage poller on an SSH, `orca serve` or paired server reads these keys from
that machine's ~/.orca, but Settings could only write them on this computer, so
those hosts never showed the meters. New accounts.* RPC methods (advertised as
accounts.usage-credentials.v1) save, clear and report the keys on the host and
refresh its usage. Settings routes the MiniMax and GLM sections, and their
endpoint/site settings, to the host picked in the page; an older host is asked
to update instead of the key being saved locally.

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

* fix(accounts): a host whose desktop owns its accounts refuses usage keys instead of storing them unread

A WSL host's server has no usage poller, so a key saved there was reported
saved yet never read. Every usage-credential method now throws the same
desktop-owns-these-accounts message as the other account methods, and the
host stops advertising accounts.usage-credentials.v1.

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

* fix(accounts): a paired client never overwrites a usage key the other Orca app on that machine sealed

The desktop app (safeStorage) and orcad (its own host key) share ~/.orca but
cannot open each other's sealed files. A remote save or forget of a MiniMax or
GLM key that this host cannot read is now refused, and the status reports it so
Settings locks that key and says to change it in the other app. Blank keys now
fail parameter validation.

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

* fix(accounts): only blame the other Orca app for a key it really sealed, and always allow forgetting

Any read failure was treated as "saved by another Orca app", so a corrupt file,
a permission error or a host that cannot decrypt at all locked the key with a
false message and refused Forget, the only recovery. The host now reads the file
itself: only a well-formed encrypted envelope that a working store cannot open
counts as sealed elsewhere and blocks Replace; anything else is reported as
unreadable and stays editable. Forget is always allowed, and the check logs
nothing per status poll.

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

* test(e2e): fail clearly when the orcad slot is missing instead of passing null as the program

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

* fix(settings): name the host in remote key descriptions, give the MiniMax API key its own failure toast, and treat malformed sealed files as unreadable

---------

Co-authored-by: Jinwoo-H <e2e@test.local>
Co-authored-by: Claude <noreply@anthropic.com>
…uter and the host (stablyai#30981)

* feat(ssh): let a nodejs.org mirror serve the pinned Node on both machines

* fix(ssh): share one deadline across every host download URL

* fix(ssh): give each host download URL its share of the deadline and cap its size

* refactor(ssh): one tool per host, labelled sources and a portable wget watchdog

* fix(ssh): quote this computer's short download reason in the combined error

* fix(ssh): fetch only validated mirror URLs on the host

* fix(ssh): drop an empty query or fragment from the mirror URL

* fix(ssh): keep a cancel or a local error as it is after the mirror fails
…user's Codex config strips environment variables (stablyai#30857)

* fix(codex): keep Orca's terminal identity in Codex commands under a filtering shell_environment_policy (stablyai#25775)

With inherit = "core" in a user's Codex config, every command Codex runs lost
ORCA_TERMINAL_HANDLE and the other ORCA_* values, so orca orchestration commands
failed with no_active_sender_terminal. Orca's codex shell function (bash, zsh,
fish, PowerShell) now passes those identity keys as per-launch
shell_environment_policy.set overrides, which Codex applies after its filter.
Skipped when the user opted into Codex's shared server, since Codex treats any
-c as opting out of it. The CLI error now names Codex's policy when it runs
inside Codex without an Orca identity. Daemon protocol 49 -> 50.

* test(windows): keep the node-as-codex Git Bash fixture free of forwarded identity

* fix(ci): keep the PowerShell wrapper inside its command-line budget; move the build contract to protocol 50; correct include_only advice

* refactor(codex): name the shared-server args for what they are; shorten the Codex identity error

* fix(codex): skip identity values holding an apostrophe instead of passing a truncated path

A TOML literal can't carry ', and Codex's fallback for the failed parse
truncates values such as /a' or /a' #b. Also drop the reversed key list
(order of distinct keys is irrelevant; tests now compare unordered) and
rename the host-side skip set to NO_DAEMON_SKIP_ARGS.

---------

Co-authored-by: E2E Test <e2e@test.local>
…tablyai#30943)

Tab-scoped combined-diff view keys include the tab id, which is fresh for
every opened tab, so closing "All Changes" and reopening it (View all or
reopen-closed-tab) always started at the top. A fresh tab now inherits the
latest view a closed tab left for the same diff in the same workspace.
Views of mounted viewers or still-open inactive tabs are never taken, the
entry is moved rather than copied, and older closed views of that diff are
dropped.
…stablyai#30989)

* fix(space): speed up workspace rows and enable reviewed cleanup

* fix(space): resolve change inspection by workspace owner

* fix(space): register changed-file and cleanup labels
stablyai#30950)

A hidden worktree is display:none, so ResizeObserver reported every mounted
diff row as 0px and the virtualizer cached that. Rows above the viewport
collapsed, and the unchanged scrollTop showed files two rows further down
after each switch back. Keep the last real row size while the scroller has
no layout box, and skip Monaco height reports from a hidden (0-width) editor.
…gured on <host>" under a remote workspace (stablyai#30858)

* wip(status-bar): remote empty usage state names the host and opens its accounts

* fix(status-bar): keep this computer's providers visible as "Not set up on <host>" under a remote workspace

* test(e2e): paired client keeps a this-computer-only provider as a host-named placeholder

* fix(status-bar): place usage placeholders only on the host's own 'unavailable' answer and open Settings on the owning host

* fix(status-bar): label the not-set-up placeholder chip "Not set up" instead of "--"

* fix(status-bar): placeholder only on a not-set-up reason; compact chip keeps "--"

* fix(status-bar): word the placeholder "Not configured on remote host"

* fix(status-bar): name the host in placeholder chips and keep Claude as 'No usage from <host>'

Only the host name truncates, so the phrase stays readable in every locale.
Claude set up here shows a placeholder when the host reports no OAuth login,
which reads the same as API-key billing, so it never says 'not configured'.

* test(status-bar): cover the Claude 'No usage from <host>' chip end to end

Pins USER on both apps so the runner's real Claude Keychain login cannot leak into
the host or client, seeds a refresh-token-only login on the client, and checks the
chip, its tooltip, the popover row, and that the chip goes away without Claude here.

* refactor(status-bar): share the owner placeholder snapshot and drop a dead Claude check

* fix(status-bar): build the owner placeholder tooltip in the current language

---------

Co-authored-by: E2E Test <e2e@test.local>
…and log the original error (stablyai#30992)

* fix(native-chat): name the request a chat start ran out of time on, and log the original start error

A start that failed because a provider request hit Orca's deadline (e.g. Codex
thread/start after 30s) now says so instead of the generic couldn't-start
banner. The deadline marks its error with the method and timeout; the start
failure fact carries them as an optional requestTimeout field, and the host
logs the original error, method and elapsed open time.

* fix(native-chat): neutral start-timeout wording; log only the current child's failure, without timing in the repeat key
…blyai#30990)

* perf(release): overlap server qualification and reuse mobile bundle

* fix(ci): preserve draft suppression for independent compatibility checks

* test(release): update template download cache policy contract
…ls; a phone never clears a twin's unread flag (stablyai#30991)

* fix(runtime): a moved shell history waits only on this machine's shells, and a phone clears a server-owned copy's own unread flag

The migration's live-shell check counted a same-id SSH twin's pane, so a local history could wait
indefinitely. readAndHoldPhoneSession read a runtime: host as this machine and cleared the local
twin's record instead.

* fix(runtime): a phone clears neither copy of a twinned workspace; the history wait reuses the PTY host fence
)

Report the execution host's accepted, launching, started, skipped, failed, or completed status and print the available run/workspace/terminal identities. Add safe same-target follow-up guidance and complete UUID examples. Preserve the existing JSON envelope and run fields while adding result.runId.

Keep scheduling, persistence, request count, and SSH owner fencing unchanged. Isolate CLI output tests from inherited connection selectors.

Fixes stablyai#27542
…dark, layout-owner build only) (stablyai#30986)

* fix(layout): restored terminals named only on their tab reattach after a restart (dark, layout-owner build only)

In a layout-owner build, a terminal recorded only on its tab (an older session, or an SSH tab
whose relay session was never visited) started a new shell after a restart and orphaned the old
one, and an unvisited SSH tab lost its relay session id on the next save.

- Main's Loader binds a one-pane tab's own terminal to its unbound pane in a workspace that was
  live at shutdown, as the window's reconnect does, so main's frames and saves hold it.
- The window's reconnect writes the restored row terminal again: ptyId is a field the frame merge
  leaves to the window, so main's frames never set it on a restored row.

* fix(layout): main binds a restored tab's own terminal once at load, not on every Loader run (dark, layout-owner build only)

The Loader re-derived the binding on each load from fields the window rewrites live, so an exit
main recorded could be undone. A switch-gated Store load repair now binds a one-pane tab's own
terminal to its unbound pane, as the window's reconnect does, and main's saves keep it. It skips
a terminal another mounted pane or a tab-bar tab holds, and a paired server's partition.

* fix(layout): an SSH workspace's restored tab binds only its relay session id; repair runs after the load purge (dark, layout-owner build only)

* test(layout): the reconnect gate test expects the row ptyId write; cover the repair's purge order and SSH repo predicate

---------

Co-authored-by: E2E Test <e2e@test.local>
* fix(space): separate workspace actions from row state

* fix(space): remove retired inline-delete label
nwparker and others added 29 commits October 20, 2026 21:15
…SH host to its server (stablyai#31374)

* fix(orcad-migration): a sidebar view preference never blocks moving an SSH host to its server

* fix(orcad-migration): keep repo filters on the desktop, keep later acknowledgements; e2e repro
…2) (stablyai#29834)

* fix(orcad): refuse new terminals between an update's last census and the stop

The last census before an SSH or WSL update stops the server now closes the
server's terminal admission first. Every spawn lease (acquireWorktreeTerminalSpawn,
which both spawn paths hold across the provider spawn) passes the fence, the
census waits for leases taken before the close, and an update that leaves the
incumbent serving reopens it. A reattach to a session the daemon still runs is
still admitted.

* fix(orcad): fence-exempt reattaches spawn attach-only; reopen admission when no stop was sent

* fix(orcad): stop nothing once the recount's admission fence may have lapsed
stablyai#31201)

* test(claude): real-claude suite passes on any signed-in machine and current model aliases

Model ids come from the CLI's own model list instead of dated ids, tests that need a SessionStart hook or an effort bring their own project settings, and the supervised-stop test launches and reads its transcript under one Claude home, as acquisition does.

* test(claude): one real-claude flow for the model, effort and permission pickers and the context meter

Drives the live CLI through the production send path (model list, set model, apply effort, read settings), checks the turn's context report, and relaunches into a new permission mode the way Orca changes it. CI only meets these requests through stand-ins.

* test(claude): accept the ran options a send now reports

Since stablyai#30561 a Claude send resolves with the options it ran under; the stop test only needs the send admitted, and the options flow checks the ran options are exactly the per-message pick.

* test(claude): an alias pick's dispatch report may omit the model the CLI resolved
…ai#31353)

fix(mobile): match workspace header safe-area surface
Adapted from community PR stablyai#30531 with native iOS simulator before/after validation.

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
…lyai#31354)

fix(mobile): paint terminal dock bottom safe area
Adapted from community PR stablyai#30318.

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
fix(mobile): skip identical terminal theme assignments
Preserve live TUI palette changes and contrast caches on repeated normalized host themes. Keep explicit foreground recovery repaints and published desktop contrast overrides.

Co-authored-by: bbingz <zzb@gxsmjx.com>
…1358)

fix(mobile): retain Claude chat when its pane is renamed
Distinguish a manual title override from a later shell OSC title, keeping transcript identity and live hook selection while preserving exit retirement.

test: cover restored mobile chat after manual shell-like rename

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
…31359)

fix(mobile): reuse DeepSeek whale for Build tabs
Adapted from community PR stablyai#30525 with a focused component regression and native current-shell validation.

test(mobile): use checked icon renderer guard


test(mobile): keep icon diagnostic with review evidence
Native before/after captures cover this low-impact asset mapping; retain the local fail/pass diagnostic outside the product diff.

Use checked optional type for mobile icon lookup

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
fix(mobile): keep keyboard accessory row height stable
Stretch the dismiss control within the accessory row so showing it no
longer adds height and needlessly reduces the terminal viewport.

Adapted from community PR stablyai#23511 by @innocarpe.

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…te, not 'handle stale' (switch-on builds only) (stablyai#31391)

* fix(runtime): a server's new terminal keeps answering under its own handle after main's pane adopts it (switch-on builds only)

* fix(runtime): keep handle branching on the leaf path; only callers with no leaf fallback read the adopted own handle (switch-on builds only)

* fix(runtime): with no window, a terminal's own handle keeps naming it after main's pane adopts it, as before main listed panes (switch-on builds only)
…stripping (stablyai#31348)

Align plain mobile/legacy residual metadata after snapshot-boundary query stripping; preserve genuinely transformed span metadata.

Fixes stablyai#9682. Based on the report and suggested correction by @bbingz.

Co-authored-by: bbingz <zzb@gxsmjx.com>
…lyai#31349)

Honor enabled voice settings across terminal/chat controls and capture. Refresh hold/toggle state on connection, focus and foreground while fencing stale replies.

Adversarial review: 63 existing tests plus six disable/late-finish/unmount/settings ownership probes pass; final required CI and aggregate gate pass. Existing native screenshots and microphone lifecycle exercise are attached to the PR.

Fixes stablyai#30307. Fixes stablyai#30511.
Adapted from community stablyai#30316 and stablyai#30513 by @innocarpe.

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
…cap, so a 13k-host ssh config no longer crashes the renderer (stablyai#31405)

* fix(ssh): the Remote Hosts menu lists a capped set of idle SSH hosts

* fix(ssh): the SSH Hosts settings list renders hosts a page at a time

* fix(ssh): window the SSH Hosts settings list and keep project hosts in the capped menu

Settings > SSH Hosts now windows its rows against the Settings scroller with the
shared VirtualizedList instead of paging 100 at a time, so every host stays
reachable by scrolling or the filter while only the rows near the viewport mount.
The Remote Hosts menu cap rises to 25 idle hosts, fills it with hosts that own
projects first, and the overflow row says to search the rest in Settings.

* fix(ssh): search the Remote Hosts menu's capped idle hosts in place

Web clients reach SSH hosts through this menu but have no SSH Hosts settings
pane, so a link to Settings stranded every host past the cap. The menu now shows
a search box whenever idle hosts are hidden; it narrows them by name, and the
overflow line says how many are left. Also gives the two existing status-bar
store mocks the repos field the menu now reads.

* fix(ssh): typing in the Remote Hosts menu goes to its host search

The search box is not a menu item, so arrow keys can't reach it; letters typed
while the menu has focus now land in the search instead of Radix typeahead.

* fix(ssh): the Remote Hosts search keeps focus and drives its results by keyboard

The host rows are not menu items, so the menu's arrow keys never reach them. The
search box is now a combobox over the connectable results: it keeps focus, arrow
keys move the active result (announced through aria-activedescendant), and Enter
connects it through the same path as the row's Connect button.

* fix(ssh): Remote Hosts search only reaches hosts that match, and keeps the active one in view

Every not-connected row now has to match the search, so Enter can't connect a
failed host the user didn't type. Arrow keys scroll the active result into view.
Also drops restyles on the search Input and fixes the singular overflow default.

* refactor(ssh): find hidden Remote Hosts in a searchable picker, not a search box inside the menu

A search box inside the dropdown kept fighting the menu over focus, Tab, Enter
repeat and IME input. The overflow row is now a menu item that opens a host
picker (Command in a Popover, as the style guide prescribes for single choice
with search). It ranks exact names first so any host is reachable by typing its
name, mounts at most 50 results, and says how many more match. The menu keeps
config order when nothing is capped.

* fix(ssh): closing the host search from the keyboard returns focus to the status button

The picker is anchored, not triggered, so Radix had nowhere to restore focus and
it fell to the page. A keyboard close now focuses the Remote Hosts button; a click
elsewhere keeps focus where the user clicked. Also drops the padding restyle on
PopoverContent.

* fix(ssh): the host search marks the keyboard-selected host with the palette highlight

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
…#30647)

* test(vitest): derive the Node SQLite test list from imports instead of a hand-kept list

The two SQLite list files held ~511 test paths that every PR adding a SQLite test had to edit,
which caused frequent merge conflicts. The vitest config now finds tests that directly import a
SQLite module or harness when it loads, using the classifier the boundary test already had, with
a modification-time cache so unchanged files skip parsing. The 42 tests that reach SQLite only
through a shared helper or a dynamic import stay listed in the Node runtime list.

* test(vitest): skip unused SQLite derivation and reject malformed cache entries

* test(vitest): use typed test convention for cache contract
…ger freezes the Orca window (stablyai#31381)

* fix(terminal): a folder on a dead network mount fails the terminal start instead of hanging it

Bound the local folder checks a terminal start runs (folder-workspace admission and the
worktree-folder cwd fallback) with a 5s deadline, and make the fallback check async so it
can no longer block the main process. A timeout refuses the start with the existing
folder_workspace_path_unavailable code.

* refactor(terminal): build the folder deadline on the existing working-directory validation helper

Drop src/main/local-folder-stat-deadline.ts. statLocalFolderWithDeadline now lives in
working-directory-validation.ts and reuses its abandon-on-signal waiter and UNC route lane,
so a dead SMB share caps how many folder checks can hold fs threads.

* fix(terminal): keep only the non-blocking saved-folder check; drop the folder-check time limit

Per owner decision, a hung folder waits as before. Folder status and working-directory
validation return to main; the saved-folder fallback check stays async so main never blocks.
…art (stablyai#31254)

* fix(terminal): hidden tabs reconnect to sessions that survived a restart

pty:hasPty answered from the provider's attached-this-run set, so right
after a restart every surviving session read as exited and stablyai#29459's
hidden-agent hold kept live hidden tabs disconnected until shown. On an
attached-set miss, ask the host through the provider's existing liveness
probe; providers without one (the in-process provider, its own sole
owner) keep their answer.

* test: remove liveness fixture type assertion
…ablyai#30643)

* ci: one list for Windows boundary tests and one step for ratchets

The Windows boundary step's 74 test paths were hand-listed in pr.yml and
again in WINDOWS_PACKAGE_TESTS, and the copies had drifted: six files ran
in the step but could not trigger the job. The step now prints
WINDOWS_BOUNDARY_TESTS, which WINDOWS_PACKAGE_TESTS spreads.

The ten per-ratchet preflight steps and the eight lint entries become one
run-ratchets.mjs that runs every check-*-ratchet(s).mjs it finds.

* fix(ci): preserve existing local lint ratchet scope
…kdown/CSV tabs can't overwrite unsaved edits (phase 1b-3) (stablyai#31413)

* Editor: git Discard pauses that file's autosave in every workspace; read-only markdown/CSV never pushes over edits (phase 1b-3)

- A Source Control discard holds autosave (and waits for in-flight saves) for every tab of that
  file in every workspace, keyed by its model save key, until the discard finishes or fails; the
  quiesce now returns the release, called in a finally. Watcher notices don't release it. A 120 s
  backstop only shows the changed-on-disk banner on dirty tabs and never reopens autosave. This
  closes the race where the compare fallback on older hosts reads disk just before git restores it.
- A read-only tab renders raw Monaco source whatever its type, so it counts as a code view: a clean
  read-only .md/.csv tab can no longer push disk text over a writable tab's unsaved edits.

Only removes or delays writes. One autosave timer per model moves to 1b-4.

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

* Editor discard hold review: release on a failed quiesce, cover unstaged diffs, re-show an overdue banner (phase 1b-3)

- A quiesce that throws (e.g. an unresolved CSV cell) releases its hold and rejects, so the discard
  reports the error instead of hanging with autosave off; a bulk discard frees the holds the other
  paths took.
- Holds key on the physical file (host + path), so an unstaged diff tab of the file in another
  workspace is paused and drained too; save-queue keys are unchanged.
- Past the backstop, a dirty held tab whose banner was dismissed gets it back, so the pause is
  never silent. Taking a hold no longer re-syncs; the save-time check already refuses.

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

* Editor discard hold: key the physical file with the 1b-1 path rules, not the client's URI rules (phase 1b-3)

The hold key went through the Monaco model URI, which normalizes paths by the client platform, so
on a Windows client `/repo/a\b.ts` and `/repo/a/b.ts` on a POSIX host shared a key. It now uses
the owner key plus `editorBufferOwnerPathKey` (new, in editor-buffer-identity), which applies the
1b-1 rules: POSIX backslashes kept, separators folded only for proven-Windows paths, UNC server
case, and WSL aliases on the local Windows client. Same on the tab and the discard-path side.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
* Parallelize adhoc Mac architecture packaging

* Include parallel Mac jobs in release timing reports

* Cover parallel Mac publication and failure gates

* Explicitly scope archive targets to each Mac architecture
* fix(sidebar): a slept chat workspace reads asleep and hides

* fix(sidebar): a wake during Sleep's teardown keeps the workspace awake

* test(sidebar): give the activation-race mock store the chat tab map Sleep reads

* feat(native-chat): the host marks the chats an explicit Sleep stopped

The fact "the user's Sleep put this chat to rest" now lives on the host
that runs the chat, on its session record, instead of in the window.

- New runtime capability agent-session.workspace-sleep-mark.v1 and an
  optional markAsleep on agentSession.sleepWorkspace (strict params, so
  only hosts advertising the capability are sent it).
- The Sleep close decides each chat's receipt inside the same per-session
  step that stopped it; with markAsleep it writes sleptAt in that step's
  record write, so a chat is marked exactly when it is in the receipt.
- The next send the host hands over clears sleptAt, whichever client sent
  it; the idle sweep and plain stops never mark.
- sleptAt rides the status summary (optional field), its equality, and
  the retained summary of a closed chat, so it survives restart through
  the persisted tab index.

* refactor(sidebar): read a slept chat from its host's mark, not a window record

The window no longer keeps its own saved record of which chats Sleep
stopped (explicitlySleptChatsByWorkspace). That record had to mirror every
host event that should end it (sends, replaced conversations, renames,
profile and WSL moves, removal, wakes during teardown), and each missed
mirror was a bug. The host now owns the fact (previous commit).

- Delete the record, its schema, store slice, workspace-session field
  registrations, moves, renames, purges, migrations and hydration, and the
  renderer clearing hooks. Those files are back to main.
- Opening a workspace no longer wakes its slept chats (product ruling):
  drop the chat Sleep-attempt tokens and their set-active-worktree hook. A
  slept chat wakes only when a send reaches its host, from any client.
- The status bridge mirrors each chat tab's published sleptAt into a
  store map keyed by the chat's pane key, dropped when its projection
  unmounts and never saved; a chat whose host runs it again is not slept.
- The asleep derivation, hide filter, palette, board and context menu read
  that map. Explicit Sleep sends markAsleep only to the local host and to
  hosts last probed with the new capability; a project move never does.

* fix(native-chat): preserve host-owned Sleep across fallback and legacy tabs

* fix(native-chat): clear Sleep when a new send is admitted

* chore: restore incoming documentation after merge hook formatting

* fix(native-chat): let admitted sends supersede pending Sleep receipts

* fix(sidebar): wait for host Sleep state before showing restored chats

* fix(sidebar): retain host Sleep answers when chat projections remount

* fix(native-chat): clear Sleep when queued drafts become sends

* test(sidebar): cover unknown and known chat Sleep state on the board

* test(vault): expect both host status capability checks

* test(native-chat): consolidate Sleep behavior coverage

* fix(native-chat): commit Sleep wake with send admission

* test: pin visibility while chat host is unidentified

* test: consolidate workspace sleep regression flows

* test: keep status feed regression within renderer boundary
…ablyai#31188)

* fix(mobile): remove the chat's Tools toggle and its open-everything mode

* test(mobile): keep the scroll-ownership tests' runs drawn without the Tools default

* test(mobile): the scroll-ownership harness opens settled turns as the old Tools default did
…ts hook turn only (stablyai#30620)

* fix(notifications): tie a completion banner to the hook turn it announces, not a 10 s window

A title-only working->idle edge (Claude's /btw side question fires no hook) raised a
generic "Task complete" banner because the pane's hook row was older than 10 s. On a
hook-reporting pane the hook row now decides: a row not yet announced supplies the
banner content however old it is; an already-announced row means no turn ended, so
no banner. Hookless agents keep the generic title fallback.

* fix(notifications): a pane with hooks gets its completion banner from its hook turn only

The title lanes (mounted pane and parked watcher) announced a finished task on every
working->idle title edge, even on panes whose agent reports turns through hooks. A title
spin that ends no hook turn (Claude's /btw side question, Codex's /compact) raised an extra
banner that could only be filled with the workspace name and tab title.

On a pane the hook lane is tracking, with a settled Done row from the same agent, the title
lane now defers before it claims the turn, so the hook lane's banner (with the last message)
is the only one. A BEL in the same burst waits on the hook lane's pending Done. Hookless
agents, restored rows, hooks switched off and the post-restart re-arm keep the title banner.
This replaces the previous commit's announcement memory in the dispatcher.

* fix(notifications): hook-owned turn ends settle pane bookkeeping and announce re-entered Stops

- A title idle edge that defers to the hook lane now settles the pane's turn, so quitting the agent after a /btw no longer raises a generic "Task complete" banner.
- A BEL held while the hook lane's Done is pending is superseded by that Done at fire time instead of being held until the next turn start.
- A Stop re-entered after a blocking Stop hook carries its new final message in its identity, so the hook lane announces it instead of deduping it against the first Stop.
- Title lanes ask the hook lane whether it can announce the turn end (live, agent evidence, not re-arming) instead of copying its conditions, and fall back when the agent's own hooks are switched off.
- The hook lane's non-quiet Done forwards its own snapshot, so its banner content no longer depends on the stored row's age.
- The whole-flow test now drives the real pane wiring, the real hook lane and the real dispatcher.

* fix(notifications): the host marks each real turn end, so a repeated status can't raise a second banner

The renderer told a Stop re-entered after a blocking Stop hook from a
restatement by its final message. The message also changes when the
listener's cache resets (a session reset, then an idle reminder), so a
restated finished turn raised a second, empty "finished" banner.

The execution host now classifies each provider's own turn-end event by
name and stamps an optional `turnEndedAt` on the status row. Idle
reminders, session events, replays, child re-emits and result retries
restate it; a turn-end event on an already finished row is a new end only
when the agent says it re-entered (`stop_hook_active`). Local hosts and
SSH hosts (orcad) mint it in `attachStatusTiming`; the WSL relay mints it
at admission and sends it on its envelope. The field is persisted,
forwarded over IPC and the paired-server session projection, and the
renderer's done identity is `turnEndedAt ?? stateStartedAt` (after the
existing `turnCompletedAt` precedence). `stateStartedAt` and every other
clock are unchanged.

Paired servers that predate the stamp advertise no
`agent-status.turn-ended-at.v1` capability; their panes keep the
terminal-title completion fallback exactly as before.

* test(notifications): type the old-server pane setup in the completion flow test

* fix(notifications): a blocking Stop's final banner reaches the user; /compact and quitting an idle agent stay quiet

Real-agent QA on the hook-only completion design found three gaps:

- A Stop re-entered after a blocking Stop hook could be dropped twice
  over: inside 1 s of the first banner by the replay guard (a new host
  turn-end stamp is now a real end), and by main's 5 s per-workspace
  desktop cooldown. A banner with the same notification id and changed
  text now replaces the one already shown instead of being dropped;
  every other cooldown rule is unchanged.
- On a mounted pane, a hook-owned idle edge (Codex /compact) no longer
  claimed a completion, so the burst's BEL went out as a Bell banner. The
  deferred idle edge now absorbs that BEL like a claimed completion did.
- Quitting an idle agent after a title spin raised a generic process-exit
  banner. On a pane whose hook lane owns turn ends and whose last turn is
  done, an exit ends no turn; a crash mid-turn still gets its banner.

* test(runtime): the projection reference keys on the host turn-end stamp too

* test(relay): restore the Copilot retry test while CI isolates a runner error

* test: key the hot-path projection reference on the turn-end stamp; relay retry restates the end

* fix(notifications): host turn identity per agent session; keep later blocking-Stop banners past main's cooldown

- The execution host (local main and the WSL relay, one shared reducer) now tracks which turn an
  agent session is in: Codex by its own turn id, Command Code by its transcript prompt position,
  every other agent by its prompt-opening hook. A turn end mints a new end when its turn differs
  from the last ended one, so a Codex turn woken by agent mail and a text-only Command Code turn
  get their banner; duplicates, replays, tool reopens and result retries restate it. The record
  is saved with the status row (validated on restore) and never published.
- Main's desktop and mobile notification cooldowns are back to main's code exactly.
- A later end in the same done segment (a Stop re-entered after a blocking Stop hook) that lands
  inside main's cooldown is held, latest only, and sent once after that cooldown through main's
  unchanged gates; the renderer learns main's answer to anchor it.
- One pane check gates every changed pane behavior: the pane's bound PTY runs on a host that
  stamps turn ends and its agent's continuation signal is verified (Claude, Codex). Every other
  pane, and an unbound PTY, keeps main's behavior.
- A hook-owned idle edge absorbs its burst's BEL for main's full 1.5 s; exits and replacements
  ask the owner check with the exiting agent; a title spin on a hook-owned pane opens no turn.

* test(notifications): run the whole-pane harness through main's shared desktop cooldown, not main's service module

* fix(notifications): decide hook ownership from how the host identified each turn end

The execution host now labels every classified turn end with one optional
disposition beside its end stamp, by the same rule for every agent:
identified (an unused prompt opening, a new native turn id, or a verified
re-entered Stop) mints a new end; a same-turn repeat is a proven duplicate
only when the agent supplies both a turn id and a verified continuation
flag; anything else is ambiguous and never mints. Replays and retries
restate the decided answer, and local and relay hosts carry an end only
within a done segment.

The renderer replaces the Claude/Codex pane gate with one evidence
decision: identified ends are hook-owned, ambiguous ends keep the title
lane's role, and rows without a disposition (older hosts, unclassified
agents) keep the previous behavior exactly. A title working edge on a
hook-owned pane keeps its turn bookkeeping and only the hook receipt; an
owned idle edge settles after the existing max wait, or announces through
the title path if the end turned out ambiguous. Idle and working title
edges resolve the same owner (the title's agent, else the foreground one).
The held later banner for identified continuations is unchanged.

* test(notifications): give the whole-pane harness's host event its local connection id

* fix(notifications): a settled cancellation ends the open turn, so its late Stop folds as on main

A cancel Orca inferred from Esc or Ctrl+C writes a done row with no turn-end event. The host reducer now completes the current turn key on that settled cancellation, so the same turn's late Stop is a repeat (ambiguous, no new stamp) instead of a second identified end that requested a second banner.

* fix(notifications): a settled cancellation ends the open turn on every lane, so a relay's late cancel end restates none

The desktop restates a WSL relay's end as decided. The relay never learns a
cancel the desktop inferred for a non-Claude agent, so Grok's own late
stop_cancelled reached the desktop as an identified end and gave a second
banner. One predicate now decides both lanes: after a settled cancellation
that carried no end, a restated end keeps the shown one, and a child event
carrying the cancellation also ends the open turn. The relay's own cancel
row is the main agent's, never a child's.

* fix(notifications): a relayed done segment shown without its own end keeps showing none

The restatement fold tested the previous row's cancel verdict, so the row it left behind (a late
plain stop) no longer matched and the relay's next restatement brought its end back. Fold on the
row's turn-end fact instead, which the folded row satisfies again.
…blyai#30941)

* fix(shortcuts): app shortcuts run before the terminal, whatever order they mounted in

App shortcut owners and terminal panes both listened on window capture,
so React's effect order decided who saw a chord first. After a worktree
switch the new pane registered before the tab shortcut listener and
turned a user-bound Cmd+Right into Ctrl+E; clicking a tab flipped the
order back. A keyboard router installed before React renders now runs
every app owner, in registration order, ahead of any pane listener; a
key no owner claims still reaches the terminal's own handling. Held tab
chords are claimed on every repeat but switch once.

Tracked in STA-11205 (same bug as STA-4602).

* test(shortcuts): type the router mock with key event names

* fix(shortcuts): install the keyboard router in the web client before it renders

* test(shortcuts): floating panel specs read the routed key listeners from the window stub
…tree cache is cold (stablyai#29340)

* fix(worktree): refuse a parent loop even when the worktree cache is cold

The parent cycle walk read each workspace's current instance from the
resolved-worktree cache, which the reparent itself invalidates, so it only
knew the two endpoints and accepted A->B->C->A. Read saved worktree
metadata instead.

* fix(worktree): fall back to locator metadata and tolerate loops saved earlier

* fix(worktree): skip another host's locator metadata in the parent loop walk

* fix(worktree): read loop-check identities from a read-only, ownership-scoped view

The per-host metadata accessor migrates a hostless legacy row into the
queried host, so the loop check could adopt another host's worktree and
refuse a valid parent. Read one read-only host snapshot instead, counting
a row only when its project is registered on that host (the resolved
list's ownership rule).

* fix: import host-qualified worktree meta helpers once

* test: match main's host-targeted lineage write contract
@innocarpe
innocarpe force-pushed the ios-selection-long-press-drag-20261019 branch from 9861557 to 6667a20 Compare October 21, 2026 06:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants