The daily full E2E run on main fails these specs every time because the
product moved on and PR CI only runs specs a PR touches:
- editor header menu renamed to File actions (#30129)
- terminal attention: an on-screen bell leaves no unread marker (#28637)
- terminal file drops carry their source host (#26830)
- single-choice questions auto-submit (#26288)
- phone terminal rows carry customTitle (#30037)
- gh/git preflight re-reads the login shell PATH (#25183)
- board drag starts after the target column is in view
- chat history anchor read after scroll settles
* WIP: establish local chat draft and first submission ownership
* WIP: retain first submission through preparation and settlement
* Keep new chat drafts local through their first submission
* fix(native-chat): release staged drafts and preserve explicit choices
* test(native-chat): keep distinct draft actions independent
* fix(native-chat): defer cross-window draft announcements
* Fix local chat draft settlement and recovery ownership
* Adopt committed draft presence after a refused allocation
* Simplify first-send recovery and preserve hosted send policy
* Remove launch prompt import cycle and tighten refusal tests
* Align draft storage contracts with typechecked callers
* Preserve definite create refusal feedback and issued recovery fixtures
* Revert the first-Send draft machinery, keeping the synchronous composer focus
The redesign opens the new chat's editor at the action and lets the ordinary at-rest create run in the background, so the local draft record, saved first submission, reserved first-Send sender and pre-session upload capability are no longer needed.
* fix(native-chat): open a New Chat's composer at once, before its host answers
The new chat's tab and composer waited for the host to admit the chat, so keys typed right after New Chat went to whatever had focus. A New Chat the user asked for now opens its chat tab immediately on the host it was routed to, with focus in the composer. Its ordinary launch asks the host in the background, the same probe it already ran before create, and the chat shows that launch's starting, failed and Retry states.
- A launch that carries a prompt still asks first, so a refusal hands the prompt to a terminal as before.
- A refused chat nobody typed in closes and its terminal opens in its place; a chat with typing keeps it.
- A paired host that would run the chat with more access than asked for gets no create; the chat's permission notice is the only one shown.
- A chat closed while its host was being asked issues no create.
- While the chat starts on a paired server, uploads wait and the model picker stays disabled until the server names what it seeds.
* Keep chat launch recovery and composer actions with their owners
* Clarify paired launch admission fixtures and recovery checks
* Apply chat attachment guidance at both paste entry points
* fix(native-chat): finish the immediate New Chat on top of the + menu's chat rows
- The + menu hands focus to the new chat's composer when it closes, instead of a frame later.
- A chat picked by name that its host refuses stays a chat and says why, including the WSL or SSH reason; it never becomes a terminal.
- A chat the user typed in before its host refused keeps its text, and the agent's terminal opens behind it rather than taking the screen; a paired server's refusal says a terminal opened.
- The paired permission check runs once, right before create, for every launch; the chat's own permission notice is the only message.
- A chat closed while its host was being asked shares the existing pre-create gate, so nothing is created.
- Session attachment notice copy lives with its only caller; tests that mirrored the implementation are trimmed.
* fix: preserve chat ownership through launch admission and fallback
* test: type launcher projects without an assertion
* fix: preserve matching refusal details in terminal completion
* fix: preserve fallback terminal placement and appearance
* test: preserve host launch failure notice during integration
* style: format native chat launch changes after integration
* fix: disable paired chat attachments until startup completes
* fix(menus): a closing menu no longer takes focus back from what its item opened
A New Chat chosen from the New Tab menu kept only the first typed key: the
menu stays mounted while it animates out, and Radix focuses its content when
the pointer leaves an item, so the remaining keys went to the closed menu.
Dropdown and context menu items now cancel that pointer focus while their
menu is closing. Trims three hunks back to main's form to stay in budget.
* fix(native-chat): host notices wrap instead of cutting off their instructions
The paired-permission notice now shows on a New Chat as soon as it opens, and
its "Update Orca on this host" instruction was cut off with an ellipsis and no
tooltip. Host notices now wrap like every other composer notice.
* wip(layout): applyDefaultTabs command in the layout model (dark)
* wip(layout): applyDefaultTabs takes the window's proposed ids; main refuses a taken one
* wip(layout): main closes or keeps an exited split pane by the window's rule (dark)
* wip(layout): a create or split sent again after its close is refused (dark)
* wip: typed test doubles
* wip(layout): main writes a worktree's default tabs in one command and starts them, window or none (dark; twin e)
* wip(layout): main writes a worktree's default tabs in one command and starts them, window or none (dark; twin e)
* wip(layout): main provisions setup and default tabs even when the agent fails to start; no window takes over (dark)
* wip(layout): pane start status in frames; a failed start keeps its pane; a terminal close stands after a failed write (dark)
* wip: lint fixes (max-lines, test throws)
* test(layout): a terminal close stands after a real failed profile write; switch off still refuses
* fix(layout): checked types in the close-failure rule and its tests
* fix(layout): a failed start keeps its pane whatever failed; default tabs start once; a tab's last pane keeps its card (dark)
* test(layout): the agent's kept pane counts as the workspace's terminal; main starts it whatever failed
* fix(layout): the switch-off create path gains no await
* fix(layout): a failed start never writes back a pane the user closed (dark)
* fix(layout): a failed start's pane goes through main's create rules, so a closed tab stays closed (dark)
* test(layout): D1a's switch-on close tests assert the intent main receives (both builds)
* test(layout): the close fixture answers main's command without naming the method
* fix(layout): a re-created pane binds as designed; the id-only ratchet reads a shared import (dark)
* fix(layout): a pane's start status may sit on a pane still bound to its old terminal (dark)
* fix(layout): a pane's start status ends when its pane binds again by any path (dark)
* test(layout): same-id restore ends a status; a replayed split brings back a closed pane by design
* fix(terminal): keep shipped exit and default-tab behaviour with the layout switch on
An exited pane closes through the legacy retirement unless the keep rule holds; the
default tab's title and color still reach the window and the phone's live tabs.
* fix(terminal): one id guard for every pane write; keep panes on an unvouched -1
Main's taken and closed-id guard now runs where every pane write goes, so a runtime create
replayed after its tab closed is refused before it spawns. An exit code nothing vouches for
keeps the pane, as the window does; a kept pane sends the phone no retirement. Converts the
switch-on tests that asserted the deleted window hand-off and title bridge.
* fix(terminal): keep the closed-tab guard on every pane write, held-id checks on client commands
A paired host starts each pane of a client's tab as a create under the tab's id, so a held tab
id is refused only for a client's own layout command.
* fix(terminal): a provider-reported -1 keeps the pane, as the window does
Only a requested stop or a host-confirmed death closes a pane on a negative exit code. Pins that
the closed-tab guard never fires in the shipped build.
* chore(terminal): satisfy the exhaustive-switch and test typing checks
* test(terminal): give the replay test's live process a cwd
---------
Co-authored-by: E2E Test <e2e@test.local>
* fix(sidebar): the desktop list keeps a paired-server create, stays current while hidden, and shows creates started elsewhere
- A workspace created on a paired server is held in the list until main's
feed carries the server's row, as local creates already are, instead of
vanishing between the create reply and the server's list update.
- The window keeps reading main's sidebar feed after the list first mounts,
so switching back to Workspaces never first draws rows main changed while
the list was hidden. Main serves the window's reads until it closes anyway.
- A creation main runs that this window holds no request for (a phone's or
the CLI's) draws a generic "Creating workspace…" row instead of an empty
gap, so the desktop and phone show the same list.
* fix(sidebar): a creation the window just ended stays off its list while main's feed still lists it
The window drops its own entry on the create reply, but its last copy of main's
feed can list the creation one read longer, with no workspace named yet; for
that read it drew the generic row. The window now keeps the ids it ended off
main's creations until an install no longer lists them, at most ten seconds.
* test(sidebar): give the creation tests' feed domains a project catalog
* perf(cmd-j): preload the jump palette so its first open renders without React's reveal delay
* fix: reuse lazy retry wrapper for preloaded components
* fix(native-chat): a chat's model, effort and Fast belong to the chat, not to a model
Structured chats kept their option values per model on the client, so a value
the host saved without a model (a resumed chat that never picked one, Codex
Fast saved on the conversation) had nowhere to live. The client either dropped
it or labelled the chat with the listing's default model to hold it.
- One flat record per chat holds model, effort, speed and Fast independently;
a model pick changes only the model and the host reconciles the rest.
- Controls come from the model they apply to, separately from the values; with
no model known, a saved Codex speed still shows and Standard turns it off.
- The options answer marks values a catalog default supplied
(current.defaulted). A resumed chat never names or sends such a model as its
own; a new chat still shows the default the host says it runs. An answer
without the mark reads as before.
- Every send, including a new chat's first message, carries the chat's own
model and effort and held picks, never a default read off the picker.
- A held launch pick supersedes only an earlier pick of the same option.
Tracked in STA-10947.
* fix(native-chat): a new chat's first send still pins the default model it shows
A new chat that never picked a model carries the default its picker names, as
before, so it keeps the model it actually ran after the CLI's default changes.
Only a resumed chat's defaulted model is never named or sent.
Tracked in STA-10947.
* fix(native-chat): narrow the host catalog before reading whether its listing names the default
The flag lives only on a listed catalog, so the check confirms the catalog is not the unknown
answer before reading it; the first-frame test reads the chat's flat model value.
* fix(native-chat): a reopened chat whose saved model was refused records the model it now runs
After a retired saved model is reset, the options answer no longer marks the model the child runs
as a default, so the next send records it instead of replaying the refused model on every reopen.
* fix(native-chat): a new chat names and pins the default it shows, and a model pick keeps only what the model offers
A chat this view started names the default model the host fills in, whichever of the model list and
the options answer lands first, and its first send carries the shown model and effort. A resumed
chat still names and sends only its own values. A model pick, held or live, keeps an effort, speed
or Fast only where the new model offers it, so a value the model cannot take is never shown, sent
or remembered.
* test(native-chat): give the no-Fast model fixture its required default flag
* fix(native-chat): an open chat's model pick leaves reconciling to the host
A live pick no longer drops values on the client; the host's reply, when it carries its full
value set, replaces the chat's own values as a read does. Only a pick held before launch, with no
host yet, keeps just what the picked model offers.
* fix(native-chat): a pick reply only adds the values it names
A pick's reply carries only the values the host set or was asked for, so it can't speak for the
rest: it no longer removes values it leaves out. Joined tests pin real reply shapes and a new
Claude chat in a fresh Claude home naming and pinning Opus High.
* fix(native-chat): a tier the drawn model doesn't offer shows as Standard
A Speed value another listed model offers but the named model doesn't is drawn as Standard, which
is what the host reconciles it to, while the chat's record keeps the host's value. Effort, a tier
no listed model offers, an unlisted model and a stand-in model's controls still show what the host
holds. After a lost pick reply the picker no longer shows the old tier as a raw extra choice.
* fix(native-chat): a never-sent chat names and pins its default after a reopen
The options answer no longer marks the default model and effort as stand-ins for a chat this host
started that has sent nothing, so a chat reopened after a restart or on the phone names them and
its first send pins them, as before. A chat taken from history, or one that has sent, keeps the mark.
* fix(native-chat): narrow the missing record as null and type the new-chat fixture's handle chain
* test(native-chat): the unloaded-turn context test's record store answers null for a missing record
* fix(native-chat): a chat's speed shows what its next turn runs
A Codex tier the drawn model lacks shows as Standard, which is how Codex runs it, except Flex, which
Codex passes regardless: established Flex keeps showing, and after a model pick the host hasn't
answered it shows no selection until the reply or a read. Each Codex turn sends the same effective
tier without rewriting the chat's saved one, and a new chat's create carries held picks as the
picker draws them, settling them so they aren't applied again after publish.
* fix(native-chat): one speed rule for picker, host and turns; never-sent needs proof; create keeps held picks
A Codex tier the model doesn't list shows and runs as Standard under one rule shared by the picker,
the host's live reconciliation, each turn and the at-rest answer, which keeps the saved tier for a
model that offers it. An older host's Flex shows as it reports it. A chat counts as never sent only
when no turn, rewind or untrustworthy journal says otherwise. A new chat's create carries held picks
as the picker draws them but keeps them as the user made them until create succeeds, so a refused
create and retry still has the user's own pick, and a newer pick still applies after publish.
* test(native-chat): type the resting journal mock and cut overlapping option tests
One typed resting-journal helper replaces six inline mocks and the broad object parameters the
low-evidence check rejects. Cuts tests the joined flows already pin: the resumed list-or-answer
ordering test, the restart-after-first-send view test, the held effort before a model pick, and
the model-pick and restart loop of the saved-Fast test.
* test(native-chat): cut option assertions the joined flows already pin
Drops the Codex Standard row (the joined tier test pins it), the dispatched-source and literal
effort-choice checks, and the resumed view test restart and model-pick steps; folds the pick table.
* fix(native-chat): an at-rest pick reply and answer name the speed the next start runs
The reply to a pick made at rest now carries the tier the chat model runs, through the same rule as
the at-rest answer, so an older app whose follow-up read fails shows what the next turn sends; the
saved record keeps the pick. A saved tier no listed model offers (a Flex from the user Codex config)
is the agent configured tier at rest too, as the live rule and the picker already read it. Restores
the pin that an accepted Codex reply naming Standard shows with no later read.
* fix(native-chat): a reopened chat with no saved model names its default at once
A reopened chat that never saved a model showed just "Model" until a send started its agent, while
main names the list default straight away. The picker now names the default the host marks for every
chat, as main does. Whether a send carries that default is a separate rule: a new chat sends it, so
its first send pins it; a reopened chat does not, so its next start still runs the agent's own.
* fix(native-chat): a chat with no saved model keeps the model its first turn ran
A chat that never saved a model followed whatever the agent's default was at each start, so a chat
resumed from history or a terminal could quietly switch models after a restart. When a message is
handed to the agent on such a chat, the host now saves the model that turn ran: the message's own
model when it carried one, otherwise the model the agent reports running. A default the host filled
in, no model at all, or a model the agent would refuse as a choice on later sends, is never saved. The write sets the model only while none is saved, inside
the record store's transaction, so a pick made meanwhile always wins and effort, speed and Fast are
untouched. One host step covers Claude, Codex, ACP agents and Pi. No new saved field.
* fix(native-chat): a chat with no saved choices keeps the model and effort its first turn ran
A Codex message that left the model or effort unset was filled from the model list's defaults, not
from what the running thread uses. Once the host saved the model a chat ran, the next Codex turn
carried that model alone and ran the list's default effort for it (a configured xhigh became
medium), and a message carrying only an effort ran the list's default model while the chat saved
the thread's. A Codex message now resolves an unset model or effort to the thread's own, and only a
switch to a different model takes that model's default effort, the same rule the other agents use.
When a chat with no saved model records what its first turn ran, it now records the effort, service
tier and Fast that turn ran too, in the same one-time store write: each only where the chat has none
saved, only from the message's own value or the agent's real report, never a filled-in default, so
a later change to the agent's configured effort no longer pairs the kept model with an effort it
never ran. No new saved field and no wire change.
A Codex follow-up sent while a chat's first turn runs is compared with what that turn actually runs,
so it steers instead of waiting for a new turn just because the host saved the model meanwhile.
Also fixes the delivery loop's optional recording hook type.
* fix(native-chat): key what a child ran by its provider child type
* fix(native-chat): record each option a turn ran where none is saved, and drop what a model pick can't run
A Codex message that only switched the tier set the thread's effort as a lasting override, so a later
switch to a model with no listed efforts was refused; the effort is again written only where the
model brought one or an earlier override needs replacing.
Recording what a turn ran now works per option: a chat that already saved its model still records
the effort it ran, and so on for each option it has none saved for. Tier and Fast come only from the
agent's own report, since a message steered into a running turn runs that turn's, and Fast is kept
only when the agent reports it actually routed on: an off state may only mean Fast is unavailable.
A model pick drops a saved effort, or Fast, that the newly picked model's listed row does not offer,
in the one host step both resting and live picks go through, so no later send carries a choice the
agent would refuse. The saved tier is kept, as before: it runs as the model's effective tier.
* fix(native-chat): save only options a turn really ran, and only ones the chat's model runs
A chat with a saved model recorded the effort its agent's settings named, not the one the turn
ran: with "effortLevel": "high" in Claude's settings and Haiku saved (no effort control), the chat
saved high and every later send was refused. A Codex thread effort its saved model doesn't list
did the same.
What a turn ran now comes from the agent's own dispatch: Claude reports the choices it applied and
the effort the CLI resolved for the model it applies, never the settings level alone; Codex reports
its final request over the thread's own model and effort, and a steer reports its running turn.
This evidence lives only in memory on the dispatch result; agents without it keep using their own
reports.
One admission rule now decides every saved option, inside the ordered record write: under a model
the saved catalog lists, an effort must be one it offers and Fast must be one it replays (Claude
needs positive support). A late record of a turn that ran on another model than the one the chat
now has saves none of that turn's options. A model pick drops inherited options its new model
doesn't run, an explicit pick or a new chat's option the model doesn't offer is refused with the
existing option refusal, and unknown catalogs or unlisted models pass as before. The saved tier
keeps its effective-tier rule, and nothing already saved is cleaned up later.
* fix(native-chat): an attach that founds a chat admits its options against the saved catalog too
* fix(native-chat): Claude and Codex always name what a turn ran, and picks are judged inside the write
The handover type was misnamed, which broke the typecheck. Claude and Codex now attach their
dispatch evidence even when it names nothing, so a Claude turn dispatched before its settings
readback never records the settings effort it did not run; the agent's own report still names the
model and stands in only for agents without dispatch evidence. Recording leaves the saved-model
and set-if-unset checks to the store's one admission rule. An explicit effort or Fast pick is now
checked inside the ordered write, with the same refusal, and only at rest: a running chat's own
menu can offer more than its saved catalog row (Pi's probe menu stops at high).
* test(native-chat): Codex admission tests accept the dispatch's run evidence
* fix(native-chat): a new chat's options are admitted against the folder list its picker showed
* fix(native-chat): a new chat's admission reads the list its picker read, resolved once by the create
* chore: re-run CI against main's formatting fix (#30918)
* chore: re-run CI against main's formatting fix (#30948)
* test(native-chat): explain the seed-options stub host cast
* fix(native-chat): attach keeps its two-argument call when there are no attach options
* Revert "fix(native-chat): attach keeps its two-argument call when there are no attach options"
This reverts commit e633d2065b.
* test(native-chat): create-path attach assertions check the params argument, not the arity
* fix(claude): keep Orca's background Claude runs off the user's MCP servers and hooks
The model picker and / menu listings, chat naming, and commit message, PR,
branch and Jira text generation each start a short Claude run. Every one but
the / probe loaded the user's MCP servers and hooks, so each run started every
server (one cut off a Notion OAuth refresh and lost the sign-in) and fired
SessionStart hooks.
Every background run now passes --strict-mcp-config and disableAllHooks.
A user's own inline --settings merges into the one settings value Claude
keeps, and their --mcp-config still loads. When Claude refuses strict mode
because an enterprise MCP config is present (which already keeps user
servers out), the run is retried once without the flag.
Tracked in STA-11095 and #30411.
* fix: centralize background Claude generation and retry policy
* fix: rebuild Claude background retries from generated arguments
* test: include branch in generation retry fixture
* fix: preserve user settings arguments in background generation
* test: define retry fixture streams across runtimes
* fix(codex): opening or resuming a Codex chat waits for Codex instead of failing after 30 s
The handshake (15 s) and thread/start / thread/resume (30 s) had their own deadlines because
the Codex start ignored the host's startup signal. It now honours it, as Claude's start does, so
those requests carry no deadline: the host's startup limits, a close, a Stop or quit end a start
that hangs, and the abort reaps Codex. Calls after the start keep the 30 s default for now: they
run in the session's queue, which a Stop or a close waits behind.
* test(codex): keep the handshake wait test in its own file under the line limit
* fix(codex): the one-shot app-server runner takes the same no-deadline option as the chat connection
* test: accept nullable Codex request timeout in probe fake
* fix(codex): bound direct opens at their caller
* Remove obsolete Codex startup timeout scenario
* fix(mobile): keep the chat composer's controls inside its row
The model pill never shrank (React Native items default to flexShrink 0), so a
long model name slid under Mic and Send. The permission pill now shows only its
mode icon, resolved from one shared mode->icon-name table that desktop uses too.
* fix(mobile): resolve the permission icon only when a pill renders
* style: format the permission pill test
* fix(desktop): keep the permission icons a static record
* fix(mobile): reopen the phone chat's live feed from its last row when it ends
The phone opened a chat's live transcript stream once per connection and never reopened it when the
stream ended or failed while the connection stayed up, so the chat silently froze on old rows. Move
desktop's reopen-from-cursor read into src/shared and run the phone on it too, with an app-foreground
retry.
* fix(mobile): retry the chat feed on the app-resume signal client-context already relays
Module mocking react-native in the shared vitest setup is rejected; publish the existing revival
nudge instead, the way desktop's host-contact signal feeds its read.
* test(mobile): let the phone's coalesced live read apply each frame before comparing
Every orcad turned on the SSH host browser bridge, so since open-url began going through it
(#30849) a user-started serve with no desktop refused with SSH-settings advice. Only a client's
managed launch enables the bridge now; a started server answers browser_host_unavailable (#30782).
Co-authored-by: E2E Test <e2e@test.local>
* fix(ssh): an old SSH host converts while a relay shell named only on its tab still runs
The conversion check counted legacy pane-key aliases as terminals waiting to
reconnect. An alias only routes hook events to a pane whose terminal the lease
check, the bridge or the terminal gate already accounts for, so it no longer
blocks the move.
* Tighten the alias census comment
* Register the tab-only relay spec: typed deployment lookup, relay fence baseline, PR route
---------
Co-authored-by: E2E Test <e2e@test.local>
* fix(native-chat): save every chat reply and tool output whole; bound only the copy sent to clients
Orca cut Codex replies, every agent's reasoning, plans, prompts and all tool
output to a 16 KB head when it saved them, and threw the rest away. The
limit existed so a reconnecting client is not replayed a huge row, but it
was applied at save time, so every transport limit became permanent data
loss.
The journal now keeps what the agent said. One outbound form, applied to
every history page, live batch and catch-up, sends a client the same 16 KB
head of each tool output and diff it always got, and marks the item
`shortened`. A new capability-gated `agentSession.item` RPC returns the
whole item, and the desktop and phone show "Show full output" / "Show full
message" when the copy was cut. Items past half a page are shortened and
marked instead of replaced with "too large to show".
Two bounds stay, both named: a 4 MiB ceiling per text (head and tail),
applied where every provider's rows enter the write queue so one write can
never stall a chat, and a preview on Grok/OpenCode/OMP/Pi calls while they
run, whose closing row is built from the whole value the translator held.
* test(native-chat): an over-budget history item is shortened and marked, not replaced
* fix(native-chat): format, keep the sink and projection under the line limit, update method count and tests
* fix(chat): preserve complete writes and keep detail current
* test(chat): align final tail reservation with captured content
* Prototype source-batch journal ownership for Claude
* Pin the resolved iterator promise in ownership experiment
* Revert "Pin the resolved iterator promise in ownership experiment"
This reverts commit bdb49ab738.
* Revert "Prototype source-batch journal ownership for Claude"
This reverts commit e5ed387f0c.
* Preserve large received chat content while pacing input
* Verify received chat writes wait for journal persistence
* Close test journal databases before removing temporary roots
* Exercise item detail in the cross-version chat wire manifest
* Pin complete saved output and fitted client publication
* Exercise Pi exit retention with real journal write pressure
* Settle terminal web test unmount before DOM teardown
* Reuse existing readers and contract support in session tests
* Reuse measured chat item sizes in history pages
* fix(chat): derive page form type from reader contract
* fix(chat): open phone user messages while loading full text
* test(native-chat): reuse imperative context provider construction
* test(native-chat): format full-content disclosure fixture
* ci: preserve unit shard diagnostics after a timeout
* ci: retain unit shard output when tests time out
* ci: expose unit shard watchdog and output stages
* Revert "ci: expose unit shard watchdog and output stages"
This reverts commit 0b4991b144.
* Revert "ci: retain unit shard output when tests time out"
This reverts commit 87eec4c04f.
* Revert "ci: preserve unit shard diagnostics after a timeout"
This reverts commit 1888c656bb.
* test: verify complete Claude tool output without large failure diffs
* Simplify reply extraction in cross-version session test
* Restore full message recovery for grouped phone thoughts
* Target recovery button in grouped thought fixture
* test(native-chat): count agentSession.item alongside main's new structured method
* fix(chat): reuse strict session subscription schema
* test(chat): open Codex before checking received content
* review: shorten a test comment; the methods file header names layout.apply
Co-Authored-By: Claude <noreply@anthropic.com>
* wip(h5b): a lost remote layout stream reopens with the local backoff
* wip(h5b): server frame id translation (pending the binding handle)
* wip(h5b): frame syncs take a source; the mirror gates frame-drawn server workspaces and indexes handles; ids translate with session.tabs handles
* wip(h5b): one layout stream per capable paired server, handing workspaces between frames and the mirror
* wip(h5b): tests for server frames; layout.getRemoteSnapshots returns only paired servers (L18)
* wip(h5b): re-gate a server workspace when its routing arrives after the snapshot; e2e hook and spec
* wip(h5b): late-owner test; e2e spec on the switch-on leg
* wip(h5b): server streams follow every paired capable server, not the focused one; a known-kind guard without a cast
* wip(h5b): server-frames tests read real paired-server state; no casts
* fix(h5b): a handed-back workspace is the mirror's alone; frozen frames draw only gated workspaces
* fix(h5b): a workspace rejoining the gate takes the full build
---------
Co-authored-by: E2E Test <e2e@test.local>
Co-authored-by: Claude <noreply@anthropic.com>
The deploy passed `--max`, which is gcloud's service-wide cap. Cloud Run divides
that cap among the revisions taking traffic, so during the 20/80 stage a
candidate under a cap of 5 got one instance while the serving floor kept four,
and the spread check could never see the full floor (deploy M, 1 of 5 for six
minutes). Candidates now pin `--max-instances` per revision, and the deploy
resets a service cap below two full revisions to Cloud Run's default before it
creates any candidate. The rollout overlap of two full directors is already in
the checked Cloud SQL connection budget.
* feat(native-chat): a queued message's Save can replace its images (host, wire and capability)
* feat(native-chat): edit a queued message's images on desktop: remove, attach, paste or drop
* refactor(native-chat): share the queued edit's image rules and drop the rarely-shown Save reasons
* fix(native-chat): a queued edit's refusal says the edit wasn't saved, and its wire takes a full image set
An expired upload refused on Save read "The command didn't run": the queued
message update fell through to the command write kind. It now has its own
kind whose sentence is "Your edit wasn't saved." The images param caps at
MAX_BLOCKS, matching the shared edit rule that counts them with the text.
* fix(native-chat): a queued edit keeps image markers right and owns drops and pastes on its card
- Removing a kept image drops its [Image #k] marker from the edit's text and
renumbers the later ones (shared rule, reused by the phone), so the agent
never reads a marker that names the wrong image.
- A file dragged from Orca's own tree onto the editing card goes to the edit,
like a Finder/Explorer drop; the chat box overlay no longer covers the card.
- Cmd/Ctrl+V with focus on the card's buttons goes to the edit, not the chat box.
- A "changed" answer says "Your edit is in the chat box" only when something
went there; otherwise it says the edit wasn't saved.
- The empty hint counts an uploading image as content and is not shown for
agents without image input; the list grows only while thumbnails show.
- Every hand-off releases the edit's preview URLs.
* fix(native-chat): an in-app drop on the editing card goes through the chat box's owner check
The edit no longer runs its own copy of the in-app drop check, which resolved
the target host through a path that can read the Active Server setting. The
chat box's claim checks the drop and routes it by the chat's owner as before,
then hands the files to the editing card's sink when the drop landed on it.
Also: the kept-image id list is readonly where it is read, and the marker
rule looks up the remaining images in a Set.
* fix(native-chat): only an edit that takes images keeps its card's pastes, and no drop joins a Save in flight
The pane paste bridge no longer skips every paste inside a queued editor: the
card's own paste handler already claims image and file pastes, which the bridge
respects. A menu paste is left to the card only when the edit takes images (the
card is a drop owner), so an older host or an agent without image input behaves
as before, and text pasted with focus on the card's buttons goes to the chat box.
An in-app drop handed to the card while its Save is in flight adds nothing.
* fix(native-chat): a failed capability re-check keeps the host's last answer, and an edit takes no files before its lease
A re-check after regained contact that failed wrote "unknown" over a known
answer for the same host, so features the host had reported (such as editing
a queued message's images) vanished on a connection blip, mid-edit. A failed
check now keeps the last answer; a different host still starts at unknown.
Paste, Attach and OS or in-app drops into a queued edit are now blocked while
it acquires its lease, as typing already is.
* fix(native-chat): an image change is never saved as text alone when the host stops reading as taking images
Reverts the shared capability hook to keep main's behavior: a failed re-check
reads as unknown. Instead the queued edit derives from live state that an edit
whose image set changed can't be saved while the host doesn't read as taking
images, so a connection blip never saves renumbered markers over the old
images or drops added ones. Save comes back once the host answers again.
* fix(native-chat): keep queued image markers and drop feedback in sync
* fix(native-chat): derive queued image markers without changing edit text
* test(native-chat): the pane drop test's intake append returns the paths it didn't take
* fix(native-chat): route menu paste to the focused queued edit
* fix(native-chat): honor queued edit paste ownership before focus redirection
* fix(native-chat): preserve native paste without clipboard access
* fix(native-chat): keep paste in the owning queued edit
* The window shows each layout records frame record by record, without reading its session back (dark, layout-owner build only)
A records frame no longer re-serializes the window's whole session and runs the
Loader. Panes' terminal bindings and live titles apply alone, to the tabs the
frame names; any other record builds only its own workspace's slices, with the
view and facts read from the store. Only a workspace the store does not show
from frames yet (startup, a reopened stream, a first visit, one sent whole)
takes the full build.
Tests install an apply check that, after every flush, compares the store with
the full build for the same cache and pre-flush store; the shared layout-apply
transport now sends main's records frames and fails a flush that differs.
* A frame of only tab rows shows without reading the window's session back or running the Loader (test, layout-owner build only)
* The account-restart test's fake binding runtime carries per-pane binding generations, so it passes with the switch on (test only)
* The account-restart test types its store writes, so its five old e2e type errors are gone (test only)
* A workspace build reads the editor selection and groups through the save's own writers, as the full build does (dark, layout-owner build only)
* A tab released from the window's hold shows the binding main wrote meanwhile (test, layout-owner build only)
* The frame-apply check still checks a flush's other workspaces when it builds one in full, and the shared transport keeps a test's own check (test only)
* The frame-apply check is proven to catch a mismatch beside a full build, and the shared transport restores only the check it installed (test only)
* Name the pane tree the window compares by what it holds: the tree without its panes' bindings or live titles (dark, layout-owner build only)
---------
Co-authored-by: E2E Test <e2e@test.local>
Guards the finding behind #31132: on a host with no window, a profile written only through
main's own commands (create, split, close, sleep, and a pane never started) reloads with the
layout-owner switch on and keeps every tab row, pane, binding, tab-bar entry and sleeping
agent. Main lists every tab it creates in the tab bar, so the load sweep never needs a
shutdown list to keep them.
Co-authored-by: E2E Test <e2e@test.local>
In the layout-owner build, main's load binds the pane again from its tab row while the
terminal is alive, so the replacement host publishes the pane ready and never pending.
The serve-restart spec waited for a pending frame that this build correctly never sends.
It now expects pending only in the current build, and asserts no pending frame in the
layout-owner build. A unit test pins the precondition on a windowless host: main's own
writes leave no shutdown list, and a real store load restores the binding.
Co-authored-by: E2E Test <e2e@test.local>
* feat(layout-owner): a layout-owner server advertises that it takes a paired desktop's layout intents (dark)
status.get adds layout.intents.v1 only in an ORCA_LAYOUT_OWNER=main build. It versions the
layout.subscribe frame shape and the layout.apply contract a paired desktop will rely on.
Tests pin the layout methods open to a paired desktop and closed to phones, and that an older
desktop still reaches every session.tabs mutation on such a server.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(layout-owner): read the paired-desktop frames and seed the server without casts
Co-Authored-By: Claude <noreply@anthropic.com>
* review: the capability comment says only what the code does, and tests pin each next check
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: E2E Test <e2e@test.local>
Co-authored-by: Claude <noreply@anthropic.com>
* fix(sidebar): the phone reads projects and project groups from main's sidebar feed, installed with the rows
The phone built its project sections from a catalog the desktop window published through a
side channel (mobileRelay.projectSections). That copy changed on a timer after the event that told
the phone to re-read it, and a re-read asked for during a read in flight was dropped, so the phone
could keep an old grouping (a project under the wrong group, a group missing) indefinitely.
Main now publishes the catalog (repos with their group and order, project groups, projects and
host setups, every host's including paired servers') as a projectCatalog domain of the sidebar
feed, from its own store and its own reads of each paired server. The phone reads it from the same
install as the rows. mobileRelay.projectSections stays, answered from the same builder, for pages
that predate the domain; a page on a desktop without the domain still reads it, now re-reading
when an invalidation lands mid-read. The phone e2e list helper runs the page's real refresh
lifecycle instead of re-reading on every assertion, and covers a group move while a read is held.
* test(mobile): stand AppState in for the screen hooks' react-native mocks, and tolerate a world with no project rows in the parity catalog
* fix(sidebar): main files a server's copy of a grouped project under its group, and reads a server's catalog only on catalog events
- The window gives an ungrouped paired-server repo the group of an existing copy of the same project
(applyInheritedProjectGroups). That function now lives in src/shared and main's catalog builder
folds each server's repos through it, so the phone files the copy where the desktop does.
- A paired server's change watch now says whether the event can carry a catalog change. Only
ready, a replay and reposChanged mark the catalog stale; worktreesChanged (pins, unread, sort
stamps) reads rows only.
- The parity fixture now builds the server side from raw server answers, folded through the
window's mergeFetchedReposForHost and through main's own server read and builder, with a server
copy of a grouped project and a partial saved manual order. It fails without the inheritance.
* fix(sidebar): parse a paired server's catalog rows with a schema instead of reading keys reflectively
* fix(mobile): sync the feed ref after render and re-read queued project sections without an await loop
* fix(mobile): spell the AppState shape the refresh reads, so the e2e helper pulls in no React Native types
* test(sidebar): give the merged builder test's feed domains a project catalog
---------
Co-authored-by: E2E Test <e2e@test.local>
Follow-up to #30415. A watch event that superseded a pending overflow existence
probe left the tab's entry behind forever, and the dirty-tab disk-check request
map never removed anything. Superseding now deletes the entry, and closed tabs'
disk-check requests are dropped by the scan's existing open-tabs subscription.
The disk-check map uses a global sequence so a reopened tab can't reuse an old
read's number.
* ci: the manual switch-on leg runs the headless serve, paired-host and serve-restart specs
With H1's switch-on server template, these exercise main as the layout owner with no window.
Spec paths are exact: the changed-spec job greps each file, so prefixes only work for ssh-orcad-.
* ci: keep the Windows serve job out of the switch-on leg; it builds switch-off
* ci: the switch-on leg also runs the server-workspace tab composition and serve-restart binding specs
---------
Co-authored-by: E2E Test <e2e@test.local>
#30037 made the headless row builder emit customTitle on every terminal row; the Linux baseline
recorded before it (#27688) never listed the field, so the spec fails on main with either layout
switch.
Co-authored-by: E2E Test <e2e@test.local>
Main runs one pass at startup and each window load, with or without a window
(desktop, orca serve, orcad): every live local terminal no pane names is bound
to the pane its record names while main still holds it free, else shown in a new
tab bound to it. The window no longer asks main to adopt terminals; it only reads
that main holds them. The window's layout.apply loses its adopt field.
Main also derives activeWorktreeIdsOnShutdown, remoteSessionIdsByTabId and
activeConnectionIdsAtShutdown from its own layout, terminal records and SSH
connections, replacing the window's copies in every save and the quit checkpoint.
Co-authored-by: E2E Test <e2e@test.local>
* perf(editor): watch only active, recent and at-risk workspaces
Background workspaces no longer hold a whole-tree native file watch. The
active workspace, the 3 most recently visited ones, and any showing a
deleted/renamed tab keep theirs; every other workspace's subscription is
released (local, WSL, SSH via the relay, and runtime hosts alike).
When a dropped workspace is watched again, its tabs are verified against
disk through the existing paths: one overflow resync once the watch is in
place reloads clean tabs and refreshes git status, Files and markdown
lists; dirty tabs go through the restored-tab disk check, which shows the
changed-on-disk banner. Saves stay guarded by the host-side disk check, so
nothing is held while unwatched.
Also: watch requests are shared per directory and host; root events reach
every workspace on that root and SSH payloads carry their connection id;
late SSH and local/WSL capacity-retry installs send an overflow resync.
* fix(editor): catch up combined reviews, deletions, late installs and Files trees after a rewatch
- Overflow catch-up drops cached combined-diff bodies for the workspace and
re-reads every loaded section of a mounted review (snapshot-backed too).
- Clean edit tabs are probed on the owning host first; a file proven gone is
marked deleted and keeps its last content instead of reloading into an error.
- Dirty tabs are checked from the overflow itself, so the check runs after the
watch is installed, including late capacity/SSH installs.
- Local/WSL Files trees also take the renderer-synthesized catch-up.
- Deleted/renamed-tab pins are capped at 3 beyond the active and recent set.
* fix(editor): catch-up keeps unsaved combined-diff edits and drops stale existence answers
- Cached Changes views reset only clean loaded sections; dirty sections keep
their drafts and baselines (also for per-path invalidation).
- A mounted review re-reads loaded and in-flight sections on catch-up.
- Existence answers carry a per-tab generation; a newer probe or watch event
for the tab supersedes an older answer.
- A tab is marked deleted only on a per-path ENOENT, never on a whole-request
not-found or an old host's scalar fallback.
* fix(editor): re-check dirty tabs requested mid-read, prove runtime deletions, chunk existence probes
- A disk check requested while one is in flight is followed by one fresh read
(coalesced) instead of being answered by the older reply.
- The restored-tab check probes runtime-owned files on their host; a per-path
ENOENT marks the tab deleted. After a retry ceiling it stops, clears the
suspension and shows the changed-on-disk banner.
- Overflow existence probes are split into host-sized chunks, in order.
* fix(editor): a reload that finds the file gone keeps its content; stale absence answers re-read
- A tab that already read its file and now gets a path-specific ENOENT on reload
is marked deleted and keeps its last content, with or without an existence
probe, so older paired servers are covered.
- The restored-tab check applies its request generation after the absence
probe too, so an older answer cannot resolve a newer request.
Behind ORCA_LAYOUT_OWNER_IS_MAIN, a CLI or phone file open is applied by main as an
openEditorTab layout command, so a host with no window can open files. The window
reports each workspace's active group as an optional view fact on syncWindowGraph,
and shows main's tab once the frame that draws it arrives. Editor language
detection moves to shared so main can name a file's language.
Co-authored-by: E2E Test <e2e@test.local>
* fix(renderer): a twin repo id refuses instead of following the Active Server focus, and a listing for a repo with no row is this app's
findRepoOwner no longer lets focus pick between same-id repo rows: runtimeTargetForRepoOwner refuses a
twin, as resolveOwner does, and getRuntimeEnvironmentIdForRepo names no server for one. repoHostId
stamps and routes a repo with no row as this app (or a web client's only server), never the focused
server.
* fix(renderer): a fork names its source's host, and GitHub routing keeps its twin rule until it can refuse
* fix(renderer): a fork captures its source's server transport, and a GitHub edit on a twin refuses instead of using this computer
* fix(automations): a new-per-run workspace is created on the run's captured host
* fix(automations): pass only a captured run host, so a twin without one refuses
* test: stamp the server repo row through a store helper, prune the focus-read baseline
* feat(settings): one-list Remote Orca Servers page and bulk-manageable SSH Hosts
Remote Orca Servers: replace the three mode cards, Advanced fold, duplicate
managed/SSH-access lists and troubleshooting accordion with a single list
(one row per server: status, one action, overflow menu), an always-visible
'New projects open on' row, and Share / Cloud VM cards that open dialogs.
SSH Hosts: group by source, filter, checkbox + shift-range selection, one
confirm for bulk remove (skipping hosts with a managed server or
workspaces), bulk Test. 'Sync ~/.ssh/config' no longer clears removal
tombstones; 'Restore removed hosts (n)' is a separate confirmed action.
* fix(settings): address review findings on servers and SSH hosts redesign
- SSH filter stays clearable when the list shrinks under the threshold
- Cmd/Ctrl-A only selects hosts when pressed in the list itself
- group legacy no-source hosts with main's import predicate (now shared)
- capture the shift-range anchor before queuing the selection update
- blank server names de-duplicate instead of colliding
- Share dialog Done refreshes link count and resets name editing
- Install / SSH-route dialogs reload SSH hosts when opened
- default-host row shows the selector when the default is a WSL host
- managed 'Not running' no longer masks connecting/revoked/update states
- servers without a dropped transport can be re-checked
* test(e2e): WSL host spec matches repo.rm ending a removed project's shells
repo.rm now stops the project's terminals on the host (#29964), so the
'orphan left by older builds' setup can no longer be built; use a shell
only the host knows of instead. Resolve the project path so macOS
/private/var matches what the host reports.
* fix(settings): carry 'Orca on <host>' naming and paired-twice warning into the new server list
Ports #31099 and #31100 into the redesigned rows, install dialog and SSH
host dialogs.
* test(e2e): keep the WSL spec's project path as created
* A respawned terminal's pane rebinds in one write, and the window shows a binding-only frame without a rebuild (dark, layout-owner build only)
Main: when the window's spawn for a restored pane finds its process gone, the spawn that holds
the pane's reservation keeps the dead binding and its fresh bind swaps it (the restart swap), so
the pane is never published unbound: a same-id respawn publishes no frame, a new id one. A failed
spawn still retires the dead binding. Other callers and window-owned builds retire early as before.
Window: a frame that differs from the shown one only in panes' terminal bindings updates the
store's pane trees as a rebuild would, falling back to a rebuild for holds and shared terminals.
* Dead-owner respawn tests expect the layout-owner build's one swapped bind instead of the early retire
* Main publishes one records frame per layout operation, and the window resubscribes on a missed frame (dark, layout-owner build only)
Main: a durable mutation (a layout.apply, the renameTabs batch, a binding write, a repair) is
one operation. It names the partitions it wrote, and at its end the layout stream publishes
once: one `records` frame holding only the changed records (tab rows, workspace groups, a pane
tree per tab, a binding record per pane, editor files, browser tabs, removed workspaces), each
keyed by main's own ids. Writes no operation scopes (window saves) still publish when their
task ends. The snapshot and every records frame carry a stream epoch. Each pane's binding
carries `ptyGeneration`, which rises on every binding write in this process, so a same-id
respawn is one frame with one binding record. Readers beside the stream get the end of each
operation and the stream's own per-partition loaded model (`workspaceLayoutReads`), so they
need no second Loader pass, with or without a window.
Window: the cache applies records frames to its full layouts, so the existing flush and the
binding-only path are unchanged. A records frame of another epoch or out of sequence, a
malformed frame, or one the cache cannot apply resubscribes at once for a fresh snapshot.
Tests install main's model check, which applies each frame to what subscribers held and
compares it with a fresh projection; dev builds log a mismatch, release builds run none.
* E1 review fixes: keep a swapped binding, bound the window's resyncs, guard operation listeners (dark, layout-owner build only)
- A spawn whose swap bind persisted no longer retires that pane's binding when a later commit
step fails; a pane on an SSH host keeps the early retire, since its dead owner lives there.
- The window resubscribes at once at most three times before a frame applies cleanly, then takes
the loss path's backoff. A skipped frame of a newer type still advances its position.
- A throwing operation listener is logged; it no longer fails a committed mutation.
- Tests freeze the shared partition model a reader gets; the dev-build model check skips frames
that only change live titles. Generation and model-check comments say what is true.
* Keep the layout stream's backoff reset after a single resync; only a resync storm holds it
* A layout stream that recovers from a resync storm reopens on the normal delay again
* Move the replaced pane owner into its own module so the stable owner no longer imports its replacement (dark, layout-owner build only)
---------
Co-authored-by: E2E Test <e2e@test.local>
* fix(sidebar): a read Done yields to a pane still Working, re-tiered on any unread write (dark switch)
* fix(sidebar): re-tier through the merged producer's settled change
* fix(sidebar): key the shared list's order, lineage and retention by host identity (dark switch)
* fix(sidebar): repeat a nested project group's path in each host section (dark switch)
* fix(sidebar): hold Recent project order under the pointer on the desktop (dark switch)
* refactor(sidebar): keep the inline-lineage cast where it was
* fix(editor): follow-ups to disk-checked saves: repeatable stale reloads, Markdown whitespace, Undo of Reload
- A stale cached copy can reload again once the disk baseline moves or a mount sees fresh text.
- A clean Markdown tab keeps a trailing-whitespace-only draft across a reload of the same text.
- Undo of Reload re-reads the file's tabs, so typing in a tab that joined after the reload wins.
* fix(editor): Undo of Reload skips tabs that left the model; keep whitespace drafts only when disk is unchanged
* fix(sidebar): one agent phase history in the transition detector, so a compaction raises no unread and a title-started turn pushes its finish
The detector now reads hookless agents' titles as well as hook rows, so a pane
has one phase. The sort-stamps producer consumes the detector's classified moves
instead of rebuilding phases, and raises unread from the detector's news: a
session boundary's done still orders as a finish but is no news. A turn a title
started and the first hook row finished is now a moved finish, which the
headless phone push announces.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(sidebar): a pane leaves its old workspace before it is seeded in a new one
The detector emitted the seed before the leave, so the stamps producer moved
the pane into the new workspace and then deleted it there. Leave first, drop the
producer's own leave, and delete the now test-only nextAgentPaneState.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): narrow the seeded workspace in the detector order test
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: E2E Test <e2e@test.local>
Co-authored-by: Claude <noreply@anthropic.com>
Every editor save carries the disk version its text came from and the host
refuses the write when disk moved on; only the banner's explicit Overwrite
writes without that check. Older hosts compare from the client first.
Co-authored-by: Claude <noreply@anthropic.com>
build-orcad-template.mjs takes --dark-test-template to let ORCA_LAYOUT_OWNER=main
through for test servers; the template and out/orcad carry the switch marker, and
packaging refuses a marked template. The e2e template steps build a switch-on server
when the workflow runs with layout_owner=main.
Co-authored-by: E2E Test <e2e@test.local>
A settings save that changes what the visibility filter reads (external/agent
defaults, workspace folder and its history, nesting) now moves the workspace
rows revision, whichever path wrote it. A paired-server answer to a fetch that
started before a contact gap keeps its rows but no longer marks them live.
Co-authored-by: E2E Test <e2e@test.local>
* fix(renderer): name both entries when a refusal comes from one server paired twice, and mark them in Settings
* fix(renderer): name twins only when they are the workspace's only publishers; generic text when a host is named
* fix(renderer): name twins only when they publish the workspace at one place
* fix(ssh): say where Orca runs instead of "managed server" in the UI
Users read "managed server" as hosting run by someone else, so they declined
moving their SSH hosts. Every user-facing string now says "Orca on <host>" or
"this host's Orca server", in all six locales; Settings > "Orca on your SSH
hosts" and the setup line say the code and files stay where they are and only
Orca's list of projects, tabs and terminals moves to the host. CLI command names,
logs and identifiers are unchanged.
Fixes#30880
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): reassure only when something moves; name the host's label in its row
Review follow-ups: the reassurance shows only while Orca sets itself up or
moves a host, not on every reconnect; the converting line no longer repeats it;
the row names the SSH host's current label; the section description and the
Move anyway dialog stop repeating themselves; translate the Restart dialog's
buttons; drop "managed" from the remaining raw causes and the external
automation refusal.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(editor): a rename retargets only editors whose own owner is the renaming host
Each open tab is matched to the move by the owner it captured when it opened (its
server and SSH host), not by looking its worktree id up again, so a same-id twin's
editor on another host keeps its path and its unsaved draft.
* fix(editor): a server route that names no machine never strands the renaming host's own tab
* fix(editor): only an SSH host a tab names excludes it from a rename; a local route can be this app's view of one
* fix(editor): a rename's fallback host check reads both sides at once, never a stale pre-rename host
* fix(editor): a rename matches tabs by the rows' owners, never the Active Server focus
* fix(editor): a tab from another workspace moves only when that workspace is on the host the rename went to
* fix(editor): a rename's cross-workspace check uses the File Explorer's own host resolver on both sides
* test: shared switch-on fixtures so 71 layout-owner test files run with the switch on
- layoutApplyTestTransport answers layout.apply (accept, apply to main's model with its frame, pending, refuse)
- main's loaded layout as snapshot frames, shared from the frame-sync test
- tab-layout maps for the editor store harnesses; missing store methods in the main harnesses
- workspaceRepos/registerWorkspaceRepos so main's load keeps seeded layouts
- ORCA_LAYOUT_OWNER=main runs vitest with the switch on
- the 4 rollback-build tests stay switch-off, labelled
* test: keep switch-off assertions exact, settle only after layout commands, reset the transport per test
* test: parse the transport's command without Reflect.get; name the staged-session type
* test: pin the test transport in the layout.apply census; route the autosave fixture as unit support
---------
Co-authored-by: E2E Test <e2e@test.local>
* fix(mobile): resend lost Workspace option changes on reconnect, draw feed-owned row fields from the feed, retry a failed feed read
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(mobile): end a feed retry streak only on install, draw the feed's comment
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: E2E Test <e2e@test.local>
Co-authored-by: Claude <noreply@anthropic.com>
The append reset old-0's mtime to wall-clock time. Once real time passed the
busy fixtures' date (2026-10-20T10:00Z), old-0 sorted newest and was read by
the unfiltered first pass, failing the no-ordinary-read assertion.
Co-authored-by: E2E Test <e2e@test.local>
* fix(pairing): pairing a saved server again refreshes its entry instead of saving it twice
* fix(pairing): only Settings refreshes a saved server in place; other add paths refuse the duplicate
* fix(pairing): a same-key refresh keeps the server's workspaces and tabs; raw add refuses; match any endpoint key
* fix(pairing): refuse a code for a hidden saved server; one choke point publishes verified status only for the pairing still saved
* refactor(pairing): verified-pairing publish in its own module; an unreadable store skips the stamp instead of failing a saved pairing
* fix(pairing): match a host key however its Base64 is spelled
* fix(pairing): a refresh keeps the saved key spelling so the host fingerprint stays put