mirror of
https://github.com/stablyai/orca.git
synced 2026-11-06 03:57:35 +00:00
fe3cb24a04445d2b5e30aba9a38013ba6fa677ae
93
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7cabd3a8dc |
Layout groundwork (switched off): every question about which pane holds a terminal asks main's layout (#32002)
* refactor(runtime): every reader of a terminal's pane goes through paneOfPty (L80) With the layout-owner switch on, main's layout answers which tab and pane hold a terminal, and nothing reads or writes the PTY record's tabId/paneKey copies or their graph stamp. Switch-off reads the copies exactly as before. * refactor(runtime): read a terminal's pane through the runtime's paneOfPty method * fix(runtime): main's pane index answers paneOfPty before any window graph A headless host never installs a window graph, so the query asked a publisher that did not exist and every terminal read as paneless. The index is now made on first query; it publishes only once a graph is installed, as before. * test(runtime): pin that a query with no window publishes nothing * test(runtime): a terminal summary reads its pane once (L80) * feat(runtime): pin bind-first at registration, and one own-handle rule with or without a window (switch-on only) * test(runtime): census and tests that every create path binds its pane before it registers (L80) * test(runtime): a bind-first fixture that stands in for the spawn commit (L80) * test(runtime): fixtures bind a terminal's pane before registering it, as the spawn commit does (L80) Also, with the switch on: skip the bind-first check for a paired server's workspace, release a closed tab's terminal ownership once main has dropped the pane, and publish a pane's agent status from the terminal main's layout binds it to. * feat(runtime): an exit after main's close still finds the pane it retires (switch-on only, L80 Q2) Main's layout write records every pane it drops whose terminal no pane holds any more, in a new optional session field closedTerminalPanesByPaneKey (same TTL and cap as closed-tab records). Exit cleanup reads main's layout first, then that record by terminal id and incarnation, and removes the entry it used. Older builds ignore the field. * refactor(layout): one dropped-leaf step every live layout edit runs (switch-on only, L80 Q2) The step receives each dropped pane (host, worktree, tab, leaf, terminal, incarnation), computed across the whole layout so a moved pane is not dropped, and runs only for a commit that declares a live edit. Today it records the closed-pane entries; a drop is recorded once per terminal. * fix(runtime): a phone or headless tab close still stops and releases its live terminals after main's layout drops the panes (switch-on only, L80) The headless close reads each row's live terminal before main's close drops the panes it is found by, and a renderer-retired tab releases the owned terminal main left in no pane, as the explicit close already did. Close tests now commit through main's layout and bind panes first. * fix(runtime): a local workspace's terminal tab left in a paired server's partition is main's pane (switch-on only, L257) Older builds could save a local worktree's tab rows into a runtime partition (#18202); its terminals are local, so main's index takes those tabs for workspaces the router homes locally. A tab or terminal the local partition also holds is indexed once, from there; SSH and server-owned workspaces are skipped as before. * test(layout): the legacy-close comparison leaves out the closed-pane record only layout-owner builds write (L80) * test(pty): a pane whose failed start withdrew its tab resolves to terminal_not_found in layout-owner builds (L80 #7) Main's layout no longer holds the pane, so there is no pane to answer for; window-owned builds keep the disconnected stub from the exited terminal's record. * test(runtime): orca-runtime suites bind panes first, as the real store and spawn commit do (L80) Fake PTY spawns commit the binding into the pane the runtime wrote, stores list their partitions, and SSH sessions sit in their ssh:<conn> partition; terminals registered directly bind first. * fix(runtime): main's layout names a pane's terminal only when the workspace and stamped incarnation match (switch-on only, L80) A record made before its adoption registers (a host restart) or a local id reused after one matched a saved binding by id alone, so a phone or paired desktop could be handed a second handle for the pane before the terminal's own handle arrived and stay on it. * test(runtime): a restored pane is only unnamed in the adoption gap, and no local record rescues a paired server's tab (L80) * test(runtime): recovery, close and routing suites set up terminals as production does (L80) A recoverable pane is one the host inventory dropped with no exit, live terminals register rather than appear only in the window graph, and SSH stores route as the real Store does. * test(runtime): suspects 3, 4 and 5 bind, register and apply as production does; PR-1's main-layout closes in the part-10 and part-11 close tests (L80) * test(runtime): a fixture store applies main's layout commands as a profile does; PR-1's own closes in the part-05, part-08 and UUID-leaf close tests (L80) * test(runtime): the profile-store stand-in moves to its own fixture, keeping bind-first under the line limit (L80) * test(runtime): orphan adoption reads the pane from main's layout with the switch on, not the record copy (L80) * test(runtime): a phone's pane close and a pinned tab close are main's layout closes with the switch on (#31571), in the no-widening and surface-close tables (L80) * test(runtime): a paired phone close commits in main's layout with no relay ack when the switch is on (#31571) (L80) * fix(terminal): a parked pane resumes its agent once main has closed the other pane that held the session (layout-owner build, L80 P1) * test(runtime): the parked-pane suite's restart holder is a saved pane with no registered terminal, under the line limit (L80 P1) * fix(terminal): a spawn commit that binds a reused local terminal id unbinds it from the saved pane of the older process (layout-owner build, L80 P2) * test(runtime): the legacy OSC row test uses the UUID leaf main's layout holds and registers its live terminal with the switch on (L80 E5) * test(runtime): the pty-exit echo close of a dead headless tab is expected to fail with the switch on until W4's close PR (L255) * fix(terminal): a terminal record that arrives, changes incarnation or moves workspace after main composed its panes re-composes them, so a restored pane lists once (layout-owner build, L80) * fix(terminal): a pane bound to a terminal a paired server runs keeps that binding and host, and is never indexed as a local spilled tab (layout-owner build, L80/L257) * fix(terminal): a pane main owns no longer proves its terminal runs; the host's inventory and the terminal's record decide, so a dropped shell reads disconnected (layout-owner build, L80) * fix(terminal): an SSH pane whose host says its shell ended keeps its leaf on a negative or failed code, as the window does, so recovery starts a shell in it; a stale handle learns the connected replacement (layout-owner build, L80) * fix(agent-launch): a terminal main's layout still binds counts as running through a host listing gap, so a launch never starts a second shell in its pane (layout-owner build, L80) * fix(terminal): a disconnected terminal main still binds stays a stop target, never reads stopped unproven, and keeps an exit wait waiting until a certified exit (layout-owner build, L80) * test(terminal): the wait unit fixture answers the exit-settled dependency * test(terminal): realistic fixtures for three phone-row tests: unique leaf ids per pane, and a window that draws main's own panes (layout-owner build, L80) * fix(terminal): a stale handle learns a pane's replacement only when it was resolved for that pane, its own terminal ended, and the replacement is connected there now (layout-owner build, L80) * test(session-tabs): switch-on variant for a window's chat placement (main's layout order rules); headless phone tab order is L283 * test(terminal-close): switch-on close reds name their ledger rows (L255, L256, L259a, L259b); a same-id respawn's commit stamps its incarnation; the crossing terminal binds first * test(terminal): the crossing registration is refused by main's workspace fence (L256 switch-on variant); the placement test's tabs are main's saved layout * refactor(terminal): the stale-handle guard reads an issue-time pane stamp on the handle record, not a second map; moved-pane, restart and single-reader tests (layout-owner build, L80) * test(session-tabs): switch-on variants: the desktop's tab name is main's rename, and a mobile-created tab's live shell is the one its commit registers (layout-owner build, L80) * fix(terminal): a re-issued handle keeps its issue-time pane while no pane holds its terminal (review) * fix(terminal): stop targets and exit waits use main's holds check (workspace and incarnation), and any exit event settles a later wait (review) * test(session-tabs): name the switch-on placement and split-group reds L288 and L289 * fix(terminal): an exit drops a closed pane's record in the same write that retires the pane, and an unbind folds into the binding write (layout-owner build, L80) The exit used to remove main's closed-pane entry in a session write of its own, before the retirement write. It now reads the entry, and the retirement write for that pane drops it, re-checked by terminal id and incarnation. An exit that keeps the surface (relay contact lost, intentional stop) keeps the entry until a confirmed exit or its TTL. A reused terminal id's unbind is now a pure computation that applyPtyBinding applies through its existing writes. Two split-pane tests still expected a -1 exit to close the pane; they now follow the ruling that any negative code keeps it. Static-gate fixes: test fixtures and tests use typed member access instead of Reflect, and no longer use type assertions. * test(runtime): the shared fixture store announces its session writes and the L283 store lists its partitions, as a profile Store does; L289's test passes on that store (layout-owner build, L80) The fixture store bindPaneFirst fills now tells write listeners of every session write, direct or by a layout command, so main's post-commit merge runs as it does on a real Store. The notice is not enumerable, so a test's own `{ ...store }` copy, which keeps its writes elsewhere, does not inherit a subscription it would never fire. With it, the targeted split-group create (L289) passes at this head, so its it.fails mark goes. L283's test runs on a store that names its partitions and stays it.fails("L283"). * fix(terminal): recovery checks the caller's handle after the cheap guards again, so the switch-off build re-issues no handle on a refused recovery (layout-owner build, L80) The stale-handle switch point had moved the handle lookup ahead of the pane, terminal and workspace checks. That lookup can re-issue a handle, so a switch-off recovery refused on those checks gained a side effect base never had. The lookup runs after them again, and the switch-on replacement check lives in one method. * test(runtime): the layout-owner start fixture's spawn commits the pane binding before main registers the terminal, as the host does (L80 switch-on variant) The window-tab start test came in with the main merge after bind-first; its fake spawn returned a terminal no pane was bound to, so registration refused it. The fixture's spawn now binds the pane first, as every other bind-first fixture does. * fix(terminal): a new process under a reused terminal id starts with no exit code, so an exit wait on it cannot settle on the old process's code (layout-owner build, L80) The record of a reused id kept the earlier process's lastExitCode, and with the switch on an exit wait settles on any recorded code once the terminal reads disconnected. A new incarnation now clears the code and cause. Also drops the L283 test's partition-list option: the fixture store already lists its partitions. * test(runtime): switch-on variants for four tests the full local run never loaded (L80) - A restored pane's host lists the terminal it started; a pane main owns vouches for none. - The other pane holding a slept session is in main's layout, so an unverifiable holder blocks the resume as it does through a window. - Main sleeps a workspace whose panes it lists itself, even with a renderer attached (#31571). - A graph sync no longer reconnects a disconnected terminal of main's pane; the inventory decides. * test(runtime): the phone Markdown route tests use #31667's realistic store, and the untitled cases wait on L268 with the switch on The window's tab uses main's id (the file path), and the store lists its partitions and saves each open editor's unified tab and group, as the window does (the fixture from #31667). With the switch on, the three untitled cases fail on L268, main's row merge dropping the untitled flag, which #31667 fixes; they are it.fails until A1b's history includes it. * refactor(runtime): two merged files back under the line limit, by moving A1b's additions to their helpers (L80) After the main merge, the holder-pane check and a closed tab's live-terminal lookup pushed two files past the line limit. The holder check is now paneHoldsSession (main-terminal-surfaces.ts) behind one method, and the closed tab's lookup is one parent-class method. Behaviour unchanged. * test(runtime): the headless chat tests open the chat on the publish path, as every product path does; L283 stays it.fails for the main-owned reorder openChat recorded the chat only in the runtime's snapshot, so main's layout never held it and the phone's reorder was refused as an unknown tab. On the publish path main's layout holds it; the reorder is accepted but does not survive the row rebuild after a close (L283, reopened). * test(runtime): bind-first census follows the one spawn-commit step (#31596); the unbound-registration test keeps its spawn from the suites' stand-in commit After the main merge both start lanes register in spawn-commit-assembly.ts, after savePtySpawnPaneBinding (or, adopted, on the live owner's pane); the census lists that file. A phone create now writes its pane before the spawn, so the suites' stand-in spawn commit bound it and the test of a path that registers unbound passed for the wrong reason; that test's controller now opts out of the stand-in commit. * test(runtime): convert four switch-on tests the disguised full runs hid Real switch-on runs showed four files whose fakes registered terminals unbound or confirmed a stop without its exit. Bind first, stamp the source as main does, and deliver the exit a confirmed stop delivers. Switch-off unchanged. * test: type the two converted fakes instead of casting them The PTY controller fake now takes the controller's optional pane fields, and the switch-on store is a typed RuntimeStore, so neither needs an assertion. |
||
|
|
5dbd23364b |
perf(layout): index records once while patching session lists (#31935)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
f8aa6854c7 |
A phone's new terminal lands after the tab it names, and its split selects the moved tab as the window does (dark, layout-owner build only) (#31924)
* fix(session-tabs): a tab main's groups lack keeps the place its writer listed it, so a headless phone lists a chat first (layout-owner build, L283) On a host with no window, a rebuild writes its rows under a group id main's layout lacks. The row writer's reorder treated every row of that group main does not hold (a chat, a browser page) as a stray and appended it to the active group, so a chat the user moved first came back last, in both the groups and the tab list. Strays now go after the id they follow in the writer's order (else first), the same rule a group main does hold already used; the window publication path shares it. * fix(session-tabs): a phone's split move and its create after a split tab go through main's layout on a windowless host (layout-owner build, L288, L289) On a host with no window, a phone's split move only rewrote the runtime's own rows, and its new terminal anchored after a split tab reached main's layout with no anchor. Every write of the rows takes main's groups and order, so the split collapsed back into one group and the new tab landed after the trailing tab. The split is now main's splitGroup command (the moved tab selected in its new group, as a desktop drop), and the create passes the anchor's tab to main's createTerminalTab, as a window's create does. An older phone sends no anchor and still gets the end. * test(session-tabs): type the split move's async reply in the dispatch stub, and read an optional group map (L289) * fix(session-tabs): a phone's split goes to main's layout only for a tab main holds, as the window sends it (layout-owner build, L289 review) With the Store's write notices, today's runtime-only split already reached main's groups through the session write; what it lost on a windowless host was the selection: the moved tab and its new group did not become the active ones, as they do after a window's drop. The split stays main's splitGroup for a tab main's groups hold, with the window's checks: a drop main's groups make a no-op sends nothing (the window's pane-column rule, now in shared), and a refusal is dropped silently. A row main does not hold (a chat or browser tab) splits as today, with no error. The create anchor and its layout-owner hand-off move to their own module. The surface test fixture's session writes now notify listeners, as the Store's do. * chore(session-tabs): log when main refuses a phone's split, as the window logs its own refusals * fix(session-tabs): a phone's terminal created after a chat or browser tab main holds lands after it, in its group (layout-owner build, L288 follow-up) Since browser and chat tabs went main-first, main's layout holds them, but a windowless create handed main the row id (agent-session:<id>), which is not main's tab id, so main put the new tab at the end of its first group. The anchor is now resolved to main's own tab, as a window's create names it; main's read of a workspace's layout is one method the tab-move path shares. * fix(session-tabs): one move path: main's tab move selects a split tab in its new group and skips the window's no-op split (layout-owner build, L289) A phone's split now goes only through main's tab move (#31812); this branch's own split routing is gone. For a split only, main's tab move now does what the window's drop does: it sends nothing for a drop that changes nothing (the window's pane-column check, reused), and after main applies the split it selects the moved tab and makes its new group active. Refusals keep that path's codes. * fix(session-tabs): resolve a create's anchor to main's tab inside the tab-move module, so only the old-client boundary's listed importers read old-client rows (L288 follow-up) * perf(session-tabs): read main's layout for a create's anchor only when main places it, so switch-off and appending phones skip that load * fix(session-tabs): restore the row-to-tab lookup the split selection and create anchor use, after #31925 dropped its import (layout-owner build, L288, L289) #31925 removed the L280 bridge in main's tab move, and with it the move's last use of resolveSessionTabRow, so it dropped the import; this branch's split selection and create-anchor lookup still resolve a row to main's tab through it. A row main's layout does not hold is now refused by main's move (tab_not_found) as #31925 rules; the phone test asserts that. * test(session-tabs): the L283 phone-order test opens its chat as the host does and uses a store that announces its writes (layout-owner build) Since #31925, main moves every tab of a workspace it owns and refuses a row its layout does not hold, and since D3b a chat the host opens is saved in main's layout. The test now opens the chat through the host's publish path and gives the runtime a profile-like store (partitions listed, writes announced), so the reorder reaches main's layout and its commit reaches the rows. * test(session-tabs): type the L283 test's profile-like store as the Store contract declares it * revert(session-tabs): drop the stray-placement change; main's layout holds chats and moves every row now (L283) Since D3b a chat the host opens is saved in main's layout, and since #31925 a phone cannot move a row main does not hold, so the rebuilt-group stray this placed no longer reaches a phone; the remaining strays are brief, and appending them gives the same order. withStrayIn and its callers return to main's behaviour, with its unit test. The phone-order spec stays as a guard that a windowless chat moved first stays first after a rebuild. |
||
|
|
ea60ec5d7c |
Layout-owner builds: a phone's tab move is always the server's own; no ghost editor tabs after restart (#31925)
* Layout groundwork (switched off): main derives agent tab titles from its own hook status With the layout-owner switch on, main gives agent tabs their generated titles from the hook-server status it owns, on the desktop, headless serve and orcad: one renameTabs per status batch, so one write and one frame. It keeps the window's rules (setting, custom and quick-command titles, first title wins, dispatch labels). The window stops sending generated titles for tabs main holds; a paired server's tab, or one main has no tab-bar entry for, is still titled by the window as today. The title rules move to src/shared (agent-row-primary-text, and the new agent-generated-tab-title-rules) so both sides share them; switch-off behaviour is unchanged. * feat(layout): main applies a phone's or paired client's tab move itself, and a tab move names the tab it follows so a stale index can't misplace it (layout-owner build, L193) * fix(layout): a move in a group showing a tab main lacks goes the window's way, a twin's agent tab keeps its window title, and orcad subscribes titles through the store attach (layout-owner build) * chore(layout): main's tab move reaches the old-client translation through its compat entry * chore(layout): mark the window-only-row move fallback as bridge code (ledger L280) * test(layout): attaching the hook store follows its rows for main's tab titles once * chore(layout): the old-client move translation is a compat entry of its own, with no cyclic re-export * fix(layout): every tab a group shows is main's, so a client's tab move never falls back to the window (layout-owner build) - Main moves a client's tab itself in a workspace it owns; a row it holds no tab for (a combined diff) keeps no place, and an order must still name each row shown once (L280). - The window publishes an editor row under its tab id only; a file no group shows gets none. - Main's layout load gives a saved browser record with no tab its tab, as the window's hydration repair did, and the window no longer mints one for a workspace main owns. - Main's layout load drops the saved tabs the window's restore drops (view-less diff, conflict and check tabs, old html previews), through one shared rule; the next save writes it back (L286). - Main lists conflict-review and check-details tabs as the plain file rows the window lists, so older clients keep them once the window's rows are gone (L287). * fix(lint): drop an import the shared restore rule left unused * test: import the move translation through the folder's public path * fix(layout): review follow-ups for the move fallback removal (layout-owner build) - A transient tab stays at load while a view is held for its file, as the window's frame build keeps it, not only for its own tab id. - The repaired browser tab goes to the saved active group only while it holds a tab, as selectHydratedActiveGroupId picks it; tests that the id is stable across loads and restarts, that a window-owned build restores it once, and that a paired server's record keeps the window's repair. - Two switch-on tests keyed editor drafts by path; drafts are keyed by editorFileKey (an owned id in a layout-owner build), so they now use it. * fix(layout): the repaired browser tab skips a saved active group this load empties * test(layout): directly dialed SSH browser record is main's; a server's SSH workspace keeps the window repair * fix(layout): drop view-less editor tabs only in loads that carry main's views, so main's own edits keep a live diff * test(layout): a direct SSH partition gets the same browser repair and drop with no host contact |
||
|
|
4165c9545c |
Layout groundwork (switched off): main tells the window whether its start resumed a pane's agent, so the restored banner and reopen count come back (#31908)
* feat(terminal): the window's store shows main's pane start status (layout-owner build) A status-only frame changes one store entry and no pane tree, so the pane's view stays mounted. * refactor(terminal): keep the frame sync within its line budget * feat(terminal): every pane main starts shows 'starting' from before it is written (layout-owner build) * feat(terminal): a pane main is starting waits and attaches; a failed start shows why (layout-owner build) Fixes owner-visible row 15: a failed agent or setup start no longer opens a plain shell. * test(terminal): iterate store listeners directly * fix(terminal): an exit that reports no code keeps its pane, as lost contact (layout-owner build) * test(terminal): a pane main started attaches to main's terminal at its own grid * test(terminal): detach census tests assert main's move and frame with the switch on * fix(terminal): a restored pane paints history only from its terminal's host, never the window's copy (layout-owner build only) * fix(terminal): main releases a painted cold restore only for the pane's current terminal (layout-owner build only) * test(terminal): typed ack args in the painted cold-restore test * test(terminal): exited-pane tests assert main's close with the switch on These 8 have failed switch-on since #31130 moved the exit close to main. * feat(layout): main starts the panes a client's startPane or wake names, through its one restored-pane starter (layout-owner build only) The model's start and stop effects reach the runtime once the command is written, never the reply. startPane starts a pane whose binding has a start status, and a slept pane from its record. A wake keeps a pane's resume record until its start binds; a failed start keeps it and shows why. With the switch on, main owns a running agent's wake even with a window open. * fix(layout): a window's wake of a pane it resumed itself is record-only, so main does not start it again (layout-owner build only) * test(layout): a client's startPane starts the pane before any other trigger * Revert "fix(terminal): a restored pane paints history only from its terminal's host, never the window's copy (layout-owner build only)" This reverts commit |
||
|
|
01db1b89af |
Main titles agent tabs and applies phone tab moves itself, and tab moves name the tab they follow (dark, layout-owner build only) (#31812)
* Layout groundwork (switched off): main derives agent tab titles from its own hook status With the layout-owner switch on, main gives agent tabs their generated titles from the hook-server status it owns, on the desktop, headless serve and orcad: one renameTabs per status batch, so one write and one frame. It keeps the window's rules (setting, custom and quick-command titles, first title wins, dispatch labels). The window stops sending generated titles for tabs main holds; a paired server's tab, or one main has no tab-bar entry for, is still titled by the window as today. The title rules move to src/shared (agent-row-primary-text, and the new agent-generated-tab-title-rules) so both sides share them; switch-off behaviour is unchanged. * feat(layout): main applies a phone's or paired client's tab move itself, and a tab move names the tab it follows so a stale index can't misplace it (layout-owner build, L193) * fix(layout): a move in a group showing a tab main lacks goes the window's way, a twin's agent tab keeps its window title, and orcad subscribes titles through the store attach (layout-owner build) * chore(layout): main's tab move reaches the old-client translation through its compat entry * chore(layout): mark the window-only-row move fallback as bridge code (ledger L280) * test(layout): attaching the hook store follows its rows for main's tab titles once * chore(layout): the old-client move translation is a compat entry of its own, with no cyclic re-export * test(config): the boundary message test names the old-client folder's configured entries |
||
|
|
99d18f7a2f |
Browser, chat and simulator tabs main-first: the window asks main for each change and draws only main's update (dark, layout-owner build only) (#31516)
* 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)
* wip(d3a-2): a windowless host lists a file main opens on session.tabs at once
* wip(d3a-2): the record key's fields are declared where the key is made
* wip(d3a-2): main publishes an editor tab's view, and a window rebuilds the open file it lacks from it
Main's in-memory editor view now carries the file facts an open file needs; it rides the tab row
(PublishedTab.editorView) and a frame build rebuilds a diff, conflict, preview or check-details tab.
Inert with the switch off: views are set only by layout.apply (switch builds) and read only in frame-sync.
* wip(d3a-2): a switch-on window sends editor opens, closes and promotions to main and draws the tabs from its frames
Layout-owner build only; the switch-off path is unchanged. An open proposes the tab id and sends
openEditorTab with the edit file's record or the tab's view (a reopen also its index); the open
file is written at once and the tab is selected once its frame draws it. Callers that act on the
tab (focus request, link reveal, group focus) wait for that frame through layout-owner-pending-tabs.
A close sends closeTabs; the tab goes with the frame and the file's content after it, and a
refusal keeps both. A promotion sends promotePreviewTab and the frame clears the flag. Deleted:
the drawn editor senders, their before/after captures, the editor drawn-tab holds and the
window-record keep rule (a transient tab is kept by its frame view).
* wip(d3a-2): a switch-on close keeps its selection and a reopen, a link names its group, and main stamps an open's time
Layout-owner build only. After main's frame drops a closed editor tab, the window selects the
group's previous tab and its surface through activateTab and focusGroup, after closeFile's
content cleanup rather than before it; a reopen sent while the close was in flight keeps the
file. openFileForLink's proposed view names the group the open asked for. The window's
openEditorTab sends no createdAt: main stamps the tab's time (ledger L186). Preview promotion
writes no tab itself: the window ends a preview only where a paired server owns the layout.
* wip(d3a-2): editor tests assert switch-on tabs through main's command and frame (test only)
Each store and hook test that assumed an immediate editor-tab draw now runs with main's model
through the T1 transport in apply mode and its frames (tabs-drawn-from-main.test-fixture), and
asserts the tab, its selection and its content after main's frame. The fixture fills the frame
slices a partial editor-and-tabs store lacks.
* wip(d3a-2): main takes a restored move as a command, renames a view tab's path, and keeps one tab per file per group
Layout-owner build only (the switch gates every change; shipped builds unchanged). moveTabToWorkspace no
longer skips a tab "the window's save got there first" (L160): window saves no longer carry the move, so
a repeat is tab_not_found; tab_exists and invalid_params stay. A move or rename may carry a non-plain
tab's new view (its file under the new path or owner), which replaces editorViews[tab] (L180). Main
refuses a moveTab or moveTabToWorkspace that would put a second tab for a file into a group (tab_exists),
and its loader folds a saved session's duplicate into the first, as the window's restore does
(dedupeEditorTabsWithinGroups): the window's frame dedupe hid the second, which no close could reach.
* wip(d3a-2): a switch-on restored move and disk rename are main's commands, and the frame that shows them rekeys the content
Layout-owner build only; the switch-off path is unchanged. A restored editor tab re-owned by its
file's workspace (L157, L158) sends moveTabToWorkspace (a re-own in place sends renameEditorFiles)
under the ids and group the window proposes, with each view tab's new view; a disk rename (L180)
sends renameEditorFiles per workspace and draws nothing first. The window's content rekey (open
file records, drafts, buffers, the editorBuffers echo gate, pendingLiveDiskVerification and
pendingSelfMoveEcho) waits in layout-owner-content-rekeys and the frame flush that first shows main's
change applies it, in the same store update as the frame (a flush now writes one patch). A change no
tab shows is rekeyed on main's answer; a refusal drops it and changes nothing. The move coordinator
and the restored-owner loader wait for that outcome; a refused rename rolls the disk move back.
A rename picks a runtime-owned or floating file's owned id, as main's frames restore it.
Deleted: recordRestoredEditorMoveInLayoutOwner, recordRenamedEditorFilesInLayoutOwner, the
reparent's before/after capture and the drawn-tab holds for moves and renames. Tests that assumed
the window moves or renames first now run through main's T1 transport in apply mode and its frames.
* wip(d3a-2): the static gate's casts and layout-writer ratchet hold for the switch-on move, rename and gap fixes
Layout-owner build only. The content rekey's pure store change stays in rekey-open-files-action
(its writer count drops 4 -> 2, pruned); a twin tab main routes to a paired server ends its
preview through patchTab, the existing tab writer, instead of a new one; a frame-only editor tab
takes its label through the frame build's tab-label facts, read from its view or file record; the
moved file's activation names the moved tab, writing no surface field. Test casts are typed or carry
a SAFETY note.
* wip(d3a-2): the switch-on rename takes the window's pure rekey as an argument, so the two modules do not import each other
Layout-owner build only; no behaviour change. Clears the static gate's no-cycle finding.
* wip(d3b): a switch-on window sends browser, chat and simulator opens, closes and page changes to main and draws them from its frames
Main places the chats its host lists (twin h), a local cancel keeps no tombstone (L155), and the
listing no longer writes chat tab structure where frames draw it (L153).
Co-Authored-By: Claude <noreply@anthropic.com>
* wip(d3b): a switch-on browser, chat or simulator close waits for main's frame, lands an emptied workspace as today, and its tests read main's frames
* wip(d3b): the stale-listing and cross-version chat tests check a switch-on local cancel through main, with a presence precondition and per-test resets
* wip(d3b): main places a tab whose group it lacks in the workspace's first group, not beside the window's anchor (L150)
* wip(d3b): tests for main's chat tab commands from a host's chat changes
* wip(d3b): main's windowless chat tab and chat fork tests apply a switch-on host's chat tab through main's layout
* wip(d3b): the switch-on close and open keep their layout writes in the files the writer ratchet counts them in, and a drawn browser close carries its position instead of a rebuilt layout
* Editor tabs main-first: the window sends each editor action to main and draws editor tabs only from main's frames (dark, layout-owner build only)
With ORCA_LAYOUT_OWNER_IS_MAIN on, editor open (preview replace, open to the side), reopen at
its position, close, close-all, promote, the resolved conflict placeholder, a restored-editor
move and a disk rename are layout commands the window sends before it draws anything. Window
content (open file records, drafts, buffers, the autosave gate) is written at once for an open,
and rekeyed in the same store update as the frame for a move or rename. Main publishes each
non-edit tab's view, keeps views in memory across applies and partition changes, stamps the
tab's time, folds same-file duplicates, and republishes a windowless host's session.tabs after
its own file open. The switch-off path is unchanged.
* wip(d3b): a switch-on close main answers workspace_not_found drops the tab's content as tab_not_found does, and placement reads a missing active-group map safely
* wip(d3b): switch-on browser and chat tests open and close through main's command and draw from its frame (test only)
* wip(d3b): a switch-on chat listing writes no tab structure of a workspace main's frames draw; a host chat title changes only the drawn tab's label
* wip(d3b): main's phone rows show a diff tab and a markdown preview as the window's do, with the preview's source named by the window's open-file id
* wip(d3b): the cleanup close's page-read guard counts its named linear passes, so a per-entry rescan still fails switch-on (test only)
* wip(d3b): the static gate's compat entry, focus-read ratchet and formatting hold for the switch-on test conversions and phone rows
* test(d3a-2): the buffer view index follows a rename main answers
* wip(d3b): placement reads the active-group map the real store always holds; the browser test store seeds it
* wip(d3b): a switch-on background browser open leaves the worktree's remembered browser tab unset, as with the switch off
* wip(d3b): a switch-on chat open selects the chat in its group without moving the focused group, as the window's own open does; its test counts the chats main draws
* wip(d3b): the converted simulator, launch-cancel, cleanup-close and chat-listing tests assert what the window's draws asserted, through main's command and frame (test only)
* wip(d3b): main fences a chat's create on its close even when the close fails for want of the chat host, so a cancelled launch never shows
* wip(d3b): a frame keeps the window's hidden chat launch slot after its neighbour, so a resumed chat keeps its saved place through main's frames
* wip(d3b): a declined chat's terminal is placed after the chat and pinned as it is in one main move, and shown before main closes the chat
* wip(d3b): a switch-on chat close the host refuses asks main to open the chat again, as the window's re-read shows it with the switch off
* wip(d3b): a paired server's workspace, its row naming the server, opens and closes browser and chat tabs without main's layout (test only)
* wip(d3b): a refused chat close comes back only while this machine's host still lists the chat, and a close main answers workspace_not_found removes the window-only tab
* wip(d3b): the twin chat close test names what each build stops: no host off, main's host on (test only)
* wip(d3b): the refused-close reopen takes only a chat agent it can launch
* wip(d3b): a frame's browser selection keeps an unchosen workspace's unset through the restore projection's own rule, not a second write
* wip(d3b): the simulator and paired-server tests type their mock stores without assertions (test only)
* wip(d3b): the switch-off declined-chat placement runs as before, with no wait; only the switch-on move waits for main
* chore(d3b): drop the frame-binding helper the main merge left without a caller
* chore(d3b): drop the import the deleted helper used
* wip(d3b): a chat this machine's window creates is placed only by the window's own open, so a reserved chat slot keeps its place (switch-on)
* wip(d3b): main reads a chat tab change against its layout when its queue reaches it, through the one rule the switch-off save uses, so a close right after an open closes it
* wip(d3b): a declined chat's terminal moves to its index among the tabs main draws, not past a hidden launch slot, so it still replaces the chat (switch-on)
* wip(d3b): main's phone-row comments say which rows the window's own row wins, and a test shows a window-listed diff keeps its dirty state (comments and test only)
* wip(d3b): a switch-on chat open returns only the tab's id and chat, the fields main's frame keeps; no caller reads more
* chore(d3b): the unit-routing census registers the browser suites' drawn-from-main store fixture as a support bridge (test only)
* chore(d3b): the chat tab rule's comment wraps and names its two callers (comment only)
* wip(d3b): main's chat tab queue asks a worktree's host only once it has a saved session, and one failed change no longer stops the ones after it (switch-on)
* wip(d3b): a chat tab batch whose changes cannot be read is logged, and the batches after it still land (switch-on)
* test(d3b): a CLI-chat resume's tab is opened once, by the window through main, after the tab before its hidden slot (test only)
* test(d3b): an SSH worktree has no host to run a chat on, so none of the chat open, close or fix paths reach it (test only)
* chore(d3b): the content tab placement reads folder workspaces through main's workspace-record selector, as the new ratchet requires
* fix(merge): put back main's
|
||
|
|
ed13ca1e6f |
Layout groundwork (switched off): main starts and restores every pane, and the window only attaches and paints the host's history (#31567)
* feat(terminal): the window's store shows main's pane start status (layout-owner build) A status-only frame changes one store entry and no pane tree, so the pane's view stays mounted. * refactor(terminal): keep the frame sync within its line budget * feat(terminal): every pane main starts shows 'starting' from before it is written (layout-owner build) * feat(terminal): a pane main is starting waits and attaches; a failed start shows why (layout-owner build) Fixes owner-visible row 15: a failed agent or setup start no longer opens a plain shell. * test(terminal): iterate store listeners directly * fix(terminal): an exit that reports no code keeps its pane, as lost contact (layout-owner build) * test(terminal): a pane main started attaches to main's terminal at its own grid * test(terminal): detach census tests assert main's move and frame with the switch on * fix(terminal): a restored pane paints history only from its terminal's host, never the window's copy (layout-owner build only) * fix(terminal): main releases a painted cold restore only for the pane's current terminal (layout-owner build only) * test(terminal): typed ack args in the painted cold-restore test * test(terminal): exited-pane tests assert main's close with the switch on These 8 have failed switch-on since #31130 moved the exit close to main. * feat(layout): main starts the panes a client's startPane or wake names, through its one restored-pane starter (layout-owner build only) The model's start and stop effects reach the runtime once the command is written, never the reply. startPane starts a pane whose binding has a start status, and a slept pane from its record. A wake keeps a pane's resume record until its start binds; a failed start keeps it and shows why. With the switch on, main owns a running agent's wake even with a window open. * fix(layout): a window's wake of a pane it resumed itself is record-only, so main does not start it again (layout-owner build only) * test(layout): a client's startPane starts the pane before any other trigger * Revert "fix(terminal): a restored pane paints history only from its terminal's host, never the window's copy (layout-owner build only)" This reverts commit |
||
|
|
ba8f237ade | fix(mobile): phone terminal and chat tab names match the desktop (#31384) | ||
|
|
cfd9e9c82c |
Editor tabs main-first: the window asks main for every editor tab change and draws only main's update (dark, layout-owner build only) (#31438)
* Editor tabs main-first: the window sends each editor action to main and draws editor tabs only from main's frames (dark, layout-owner build only) With ORCA_LAYOUT_OWNER_IS_MAIN on, editor open (preview replace, open to the side), reopen at its position, close, close-all, promote, the resolved conflict placeholder, a restored-editor move and a disk rename are layout commands the window sends before it draws anything. Window content (open file records, drafts, buffers, the autosave gate) is written at once for an open, and rekeyed in the same store update as the frame for a move or rename. Main publishes each non-edit tab's view, keeps views in memory across applies and partition changes, stamps the tab's time, folds same-file duplicates, and republishes a windowless host's session.tabs after its own file open. The switch-off path is unchanged. * test(d3a-2): the buffer view index follows a rename main answers * test(e2e): the old-phone baseline records a windowless server opening the file, as main does since #31291 (test only) |
||
|
|
bbc8303b47 |
A running agent's resume note is not a sleep, so switch-on builds report it connected (dark, layout-owner build only) (#31485)
* fix(layout): a running agent's resume record is not a sleep in switch-on builds The shared layout load put every pane resume record into leaf.sleeping, so main's terminal index read a live or quit capture as a slept pane and terminal.list reported running agents as disconnected and unverifiable. Records of slept panes (isPaneSleepRecord) now load to leaf.sleeping and running agents' records to leaf.resume; both save to the same sleepingAgentSessionsByPaneKey entry, so the disk format is unchanged. The disk-field census names both holders, and a model rule refuses a pane holding both. * fix(layout): a waking start wakes only slept panes; a recordOnly wake drops either record * fix(layout): main's shutdown reconnect facts read a running agent's resume record as awake |
||
|
|
065e6873f6 |
A paired desktop shows the held-terminal banner and Reopen for a server's held terminals (switch-on builds only) (#31275)
* A paired desktop shows a layout-owner server's held panes on the existing banner; Reopen asks the server (switch-on builds only) The server's frames carry status 'held' on a pane a newer build's terminal service may still run. The desktop now lists those panes on its existing newer-build banner, with the same wording and states. Reopen calls the server's new `layout.reopenHeldPanes` under the server's ids: the server stops its newer service, as the desktop's own Reopen does, and starts each pane still held. The desktop starts nothing; the server's next frames clear the status and bind the pane. A refusal or failure shows the banner's failed state. Served and advertised (`layout.reopenHeldPanes.v1`) only by a layout-owner build, to paired desktops (not phones). A server without the capability, or that publishes no status, shows no banner, as today. * test(layout): held server pane stays Reopening until the reattach frame, stays failed after a refusal, and shows nothing without the reopen capability * fix(layout): a held pane's Reopen acts only on its own host and on main's published status (switch-on builds only) - Reopen needs the clicked pane's key; a pane that already left the banner's store does nothing, so a server banner clicked as its frame drops the pane never stops this machine's newer service. - Local versus server is decided by the pane's host, not by whether it has a release. - The banner portal for a pane with no terminal handle exists only in a layout-owner build. - The server reopens only panes its published layout shows held, the status the desktop saw. * test(layout): Reopen ignores a pane rebound to a new terminal whose held entry is still stored * test(layout): the dropped-pane Reopen test checks for the reopen method, since the view report shares the RPC client |
||
|
|
6e589bb4ba |
Layout-owner build: phones see main's terminal and editor rows (A2, dark) (#31276)
* 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 * test(layout): terminal reply fixtures captured from the window-owned build, and main's switch-on proofs * wip(layout): main's terminal surface index feeds the runtime graph (dark) * wip(layout): main's panes stand in for the window's terminal list (dark) * test(layout): the window sends main-listed panes as mount facts (dark) * test(layout): surface proofs use public runtime calls * chore: lint * feat(layout): close by handle closes in main's layout; the close-time relist goes (dark) * feat(layout): main's binding outranks a caller's pane claim on the terminal record (dark) * test(layout): window-list tests stay on the rollback build; switch-on mount-fact cases (dark) * wip(layout): main's terminal index reads E1's shared projection and follows its operation ends (dark) * refactor(layout): the main-surfaces host lives beside the session-tabs readers, keeping the graph file under its line cap (dark) * fix(layout): main's panes skip the window-era membership fence, follow workspace routing, and list a terminal when it registers; window close only hands the graph back (dark) * refactor(runtime): explicit-close PTY stops move unchanged to their own module, for the file's line cap * test(layout): the cold-restore table runs in a layout-owner build too: one push per registration * test(layout): the parked and automation pane tests assert the same window report in both builds (mount facts with the switch on) * chore: one layout-owner switch import after the restack on D1a * feat(layout): main's terminal.list rows carry the host's verdict; a pane with a status is never live (dark) * chore(layout): fit main's surfaces under the lint gates after the D1a restack * feat(layout): every pane status D1a publishes keeps a row from reading live; remote host answers pass through (dark) * feat(layout): a drag-out moves main's own binding and refuses the home copy's last pane; the window's terminal claim is not consulted (L127, dark) * fix(layout): a tab close by a starting pane's handle stops the tab's terminals; a starting pane is never exited on the inventory alone; terminal.list lists every saved pane main owns; the pane claim lookup leaves the output path (dark) * 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 * fix(layout): main's index reads routing only for a workspace two partitions hold, the fence reads ownership once, a legacy pane keeps its claim; switch-on tests assert main's answers after a window closes (dark) * refactor(runtime): a worktree stop's terminal collection moves unchanged to its own module, for the file's line cap * fix(layout): main's index routes every workspace as the layout stream does, re-reading routing only on a write or a terminal registration; a worktree stop names every terminal main binds; a starting pane is unverifiable (dark) * feat(layout): a workspace main owns publishes its terminal and editor session.tabs rows from main's layout; the window still supplies browser, chat and view-only rows until D3b (A2, dark) * chore: lint * test: type the row fixtures * chore(layout): main's session.tabs projection is an old-client entry, imported only by main's terminal surface index * fix(layout): main's session.tabs rows carry the tab's name, the window's agent status and markdown file id, list rows in tab order, and keep groups and selection consistent (A2, dark) * refactor(layout): the projection's group order moves unchanged to its own old-client module, for the file's line cap; lint * feat(layout): every pane status main publishes keeps a row from reading live; a resuming pane is unverifiable like a starting one (dark) * test(layout): a windowless server's layout change reaches a subscribed phone's session.tabs rows (dark) * fix(layout): a window-only row keeps its place in the rows, the window's selected row lands in a group that selects it, and a workspace with no main group keeps the window's groups (A2, dark) * fix(layout): a pane main has no binding for yet keeps the window's terminal in its phone row, so an SSH restore lists it pending (A2, dark; ledger L243) * fix(session-tabs): phones show the window's live selection, focused pane, dirty state and titles over main's saved view; exited terminals stay gone; stray rows join the active group (switch-on builds only) * fix(session-tabs): main's groups stand with main's selection; certified exits only; a selected stray row joins main's active group --------- Co-authored-by: E2E Test <e2e@test.local> |
||
|
|
70b2197c97 |
The window shows tab, title, move and divider changes only from main's update, never drawing them itself (dark, layout-owner build only) (#31257)
* 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) * A tab move shows from main's frame alone; the window no longer re-applies it or adopts main's group id (dark, layout-owner build only) On main's answer the window only selects the moved tab in its new group, a view choice the frame does not carry. While main's stream is down it still moves the tab itself, as the frame sync's closed-stream rule says, but under its own group id: the reopened stream's snapshot redraws main's copy. The three switch-on census files for tab moves now pass without a switch mock: the context menu's move routes to the tab bar's move, the drag hook's pane-column split goes to main in a layout-owner build, and the tab bar's own move is pinned on a paired server's workspace. A new frame test pins each move through main and its frame. * A tab's rename, color, pin, view and a pane's title show from main's frame alone; the window no longer applies them again (dark, layout-owner build only) * A divider drag sends main the ratios it moved, not the window's tree, and an in-tab pane move shows from main's frame; the ratio hold is gone (dark, layout-owner build only) * Divider ratio params and their command tests get their own modules (file size) * Layout-writer baseline pruned: the tab move no longer writes the layout from the window * The window never draws a tab prop, pane title or in-tab move itself; while main's stream is down the reconnect's snapshot shows them (dark, layout-owner build only) * View-mode test seeds its paired-server workspace without a type assertion * A moved tab is selected in the frame flush that shows the move, unless the user selected since; main's answer no longer selects it (L120, dark, layout-owner build only) * Frame sync stays within its line budget * Frame sync writes its patches through the moved-tab selection, within its line budget * The moved tab's selection reuses activateTab's own store update, so no new layout writer is added (L120) * Format the tab-move frame test * A tab moved while main's stream is down is selected by the reconnect's snapshot that shows the move (L120) * A generated tab title main gave is drawn by its frame alone; the window writes only titles left to it as today, and the rename test checks the phone graph republishes (dark, layout-owner build only) * A divider ratio names the split it means, and main refuses it where that path holds another, so the window snaps back to main's ratios; a drag back to main's ratio before it answers is sent too (dark, layout-owner build only) * test(layout-owner): tab moves drawn from frames make no window tab or group writes * test(layout-owner): a reopened tab shows its title and color from main's frame * test(layout-owner): the tab bar's preview pin through main ends the preview and counts a use * test(layout-owner): read main's generated-title renames without a type assertion * test(layout-owner): type the sent renames so the title helper reads them without an assertion * The frame-apply check selects the moved tabs the flush selected, so the tab-move tests exit clean and a wrong selection still fails (dark, layout-owner build only) * test(layout-owner): the title batch's live labels are checked in both builds; only the generated title differs * Main keeps a tab's first agent-generated title unless the window's title replaces it, so a second prompt while main's stream is down no longer renames the tab (dark, layout-owner build only) * test(layout-owner): a revealed terminal's tab name is drawn from main's frame, not its reply (dark, layout-owner build only) --------- Co-authored-by: E2E Test <e2e@test.local> |
||
|
|
94ebd4a5c0 |
Main starts each restored terminal pane once, at its last size, and names sleeping and held panes (layout-owner build only) (#31171)
* 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 layout command that changes a pane's terminal raises its binding generation (dark, layout-owner build only) * fix(layout): main shows unshown live terminals again when a client attaches or the terminal service is replaced (dark, layout-owner build only) Headless servers had no retry: a terminal the startup pass declined stayed tabless until the next restart. The pass now also runs when a client authenticates and after the local terminal provider is installed or replaced, one pass at a time. A terminal whose pane record fails to save after main bound it is no longer reported as left without a pane. * feat(layout): main stores each pane's last grid on its tab layout (dark, layout-owner build only) A new main-only setPaneGrid command records the grid a pane's terminal was last sized to, on its leaf. It is saved as the layout's optional lastGridsByLeafId, which older builds drop on read, so a downgrade loses only the hint. It moves with its pane. * feat(layout): main remembers a resized terminal's grid on its pane and starts the pane at it (dark, layout-owner build only) Each accepted resize, from a window or a client, is written once per burst as the pane's last grid. A pane main holds starts at that grid, after a size the caller measured and before its group's size, then 120x40. * fix(layout): a re-created pane binds as designed; the id-only ratchet reads a shared import (dark) * fix(layout): a pane's grid resized just before quit is saved, and a grid change publishes nothing (dark, layout-owner build only) The desktop and serve quit and orcad's shutdown write any grid still inside its one-second burst before the final store flush; a quit with none waiting adds no await. A grid is main's own record: it is not in a published pane, so a resize burst sends no layout frame, and its write patches only the pane's tab. * feat(layout): a pane's status names sleeping, resuming and held, and what a status on a bound pane means (dark, layout-owner build only) * fix(layout): a pane's start status may sit on a pane still bound to its old terminal (dark) * fix(layout): main starts fresh a restored pane bound to another workspace's terminal (dark, layout-owner build only) The window's restore rule, now in main for every host: a pane whose stored terminal id was minted for another workspace is unbound, so it starts fresh, and that terminal, if live, is placed in its own workspace. The id check moves to shared, which the window now imports. * fix(layout): a start refused because a newer build's terminal service may still run the pane shows it held, not failed (dark, layout-owner build only) * fix(layout): a pane's start status ends when its pane binds again by any path (dark) * feat(layout): main's one start of a restored pane with no live terminal, shared by every non-window trigger (dark, layout-owner build only) Restores from the pane's own stored terminal and resumes a local agent record; concurrent triggers share one start, and a start that lost to another starter resolves to the winner's terminal. Not wired yet. * feat(layout): a client's first view and a CLI start of a restored pane go through main's one starter (dark, layout-owner build only) Twin j: the phone's first-view start of a pending row moves into its own runtime layer and, in the layout-owner build, runs through RestoredPaneStarter, which A1's send, wait, read and wake call as ensureRestoredPaneStarted. It restores from the pane's own stored terminal, resumes the agent the host recorded there, and resolves once the terminal is registered. With the switch off, the phone's start is unchanged. * test(layout): same-id restore ends a status; a replayed split brings back a closed pane by design * test(layout): a window restore, a client first view and a CLI send of one restored pane start one terminal (layout-owner build) * fix(layout): the restore pass releases a pane from another workspace's terminal through main's layout command, and two files stay under the line limit (dark, layout-owner build only) A new main-only releasePaneTerminal command unbinds a pane from the terminal it names and stops nothing, so the pass writes no layout outside a command. The stored terminal layout schema moves to its own file, and a pane's own size (measured, else its last grid) is one helper. * test(layout): drop a needless spread in the pane release test * feat(layout): main shows a restored pane whose terminal is gone as sleeping, and a window treats a status-only frame as a binding change (dark, layout-owner build only) After a census that answered, each local pane bound to a terminal the host no longer runs gets status 'sleeping' and keeps the id it restores from; any bind ends the status. Relay and paired server panes are their own host's to judge. A window rebuilds nothing for a frame that only changes a pane's status. * test(layout): restored history reaches each start across two host restarts with no client acking the cold restore A guard, not a fail-first test: the cold-restore ack only drops the daemon's in-memory copy (daemon-pty-session-control.ts) and the re-anchored checkpoint carries the history (daemon-pty-spawn-result.ts), so a host with no client keeps it. C1c moves the ack into main. * test(layout): the activation gate and worktree-reopen tests assert the same behaviour through main's command and frame (layout-owner build) In a layout-owner build main places live terminals and draws them through its frames, so the gate's adoption tests now check the window makes no tab or binding of its own, adopts only what main's frames hold, and resumes no agent over a live terminal main has not placed yet. The worktree-reopen tests feed main's frame for the restored pane and answer layout.apply, checking one tab bound to the live terminal and one resumed tab across reopens. * fix(layout): a restored SSH pane reattaches over its relay, and the restore starter returns the terminal it started (layout-owner build) An SSH terminal id names its connection, not a workspace, so the rule that drops another workspace's terminal no longer releases it or starts the pane fresh. The starter now resolves to the id its start registered instead of re-reading the stored layout, whose bind write can still be in flight, and a tab saved with no layout counts as one held pane. * 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(layout): the restore starter's start result types a missing terminal id as none * fix(layout): a workspace's first tab waits for main's placement of its live terminals, so activation opens no extra shell beside one (layout-owner build) A window activating a workspace before main's placement pass had read the host census found no tab and asked main for the first one; main admitted it into the still-empty workspace, then the pass added the live terminal's own tab beside it. An initial create now waits out a pass in flight. * fix(layout): the restore starter never resolves to another workspace's live terminal, and a first view of a pane main dropped is refused (layout-owner build) * test(layout): the first-tab placement test overrides the worktree map with its declared type * fix(layout): the first-surface wait lives with main's placement pass and keeps the runtime file under the line limit * 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(layout): a workspace's first surface waits only for the placement pass running when it arrives, and the switch-off build applies commands as before * 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(layout): a quit's pane-grid writes never hold the stop past one second, and orcad awaits nothing for them when none wait The grid flush before the final save (desktop and serve quit, orcad stop) is now raced against a 1 s budget; a grid it misses only means that pane restarts at the default size. With the switch off no grid is ever recorded, so the flush is null and orcad no longer awaits even that. * 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. * fix(layout): a quit's grid flush swallows its own failures, keeps its budget timer live, and the quit's no-await note names it * chore(terminal): satisfy the exhaustive-switch and test typing checks * test(terminal): give the replay test's live process a cwd * fix(layout): a quit writes pending pane grids before the SSH shutdown, and a workspace's first tab waits at most 0.5 s for main's placement pass The quit's grid flush (still capped at 1 s) now runs before beginSshShutdown, so nothing is awaited between it and the final save; with no grid waiting, both start synchronously as before. A first tab waits for the running placement pass at most 500 ms, then is admitted. The quit test's SSH mock now names the module the quit imports. * test(layout): the quit tests prove the final save starts in the same turn as the SSH shutdown, in order after the PTY kill * chore: keep main's bytes for files the merge's format hook rewrote (native-chat formatting is #31184's) --------- Co-authored-by: E2E Test <e2e@test.local> |
||
|
|
997d34b043 |
Layout groundwork (switched off): main creates, starts and closes terminal tabs and panes itself (#31130)
* 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> |
||
|
|
ea16d1b11c |
Key a layout frame's editor file records the same in every build, so a reader built without the switch applies a layout-owner host's order (dark, layout-owner build only) (#31140)
Co-authored-by: E2E Test <e2e@test.local> |
||
|
|
1bfb45654f |
A layout-owner server tells a paired desktop it takes layout changes (dark, layout-owner build only) (#31131)
* 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> |
||
|
|
cf94325fb5 |
fix(layout): main shows live terminals no pane shows and derives what the next startup reconnects (dark, layout-owner build only) (#31118)
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> |
||
|
|
03e9a03e9e |
feat(layout-owner): main opens CLI and phone files itself, in the window's live group (dark) (#31123)
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> |
||
|
|
a2e0f20b1e |
Main sends one layout update per action, with only what changed (dark, layout-owner build only) (#31109)
* 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> |
||
|
|
4e998861e1 |
test: shared switch-on fixtures so 71 layout tests pass with main owning the layout (#31076)
* 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> |
||
|
|
b822132a75 |
Agent titles from one status batch reach main as one rename (dark, layout-owner build only) (#31048)
* Send a status batch's generated tab titles to main as one rename (dark, layout-owner build only) * Share the window's as-today refusal rule with the title batch; cover a failed batch and a paused workspace * Move the renameTabs request schema to its own module, under the file's line limit --------- Co-authored-by: E2E Test <e2e@test.local> |
||
|
|
764e09bf2b |
No empty extra terminal tab from an old saved terminal record (dark, layout-owner build only) (#31045)
* fix(layout): main sweeps a stored terminal row the window would not reattach, so no empty extra tab shows (dark, layout-owner build only) A stored terminal row with no tab-bar entry became a tab in main's layout even when nothing would reattach it, so with main owning the layout the window showed an empty extra terminal tab that today's build never shows. Today's window drops such a row at startup: it keeps one only when its startup reconnect plan names a terminal for it (the row's own terminal in a workspace live at shutdown and not on SSH, where that id is only a wake hint; an SSH relay session id; or a split row's own pane), and removes the rest with their pane layouts. With the switch on, main's Store load now applies that same sweep once, beside the existing tab-level binding repair, so main's layout, frames and saves never hold the row. Sleeping agent records stay, as they do in today's window. A selection naming a swept row moves as the window's sweep moves it. The window-parity tests now restore with today's window as the oracle and load as main does with the switch on (its startup sweep, then the Loader), so they hold under either build; the ghost-tab entry is removed and each kept and dropped shape has a profile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(layout): the startup sweep keeps an SSH row whose own terminal the window may reattach (dark, layout-owner build only) The window ignores an SSH row's own terminal id only when it finds the worktree listed; an SSH folder workspace or an unlisted SSH worktree reattaches by that id. Main cannot see the listing at load, so the sweep keeps such a row rather than risk deleting a terminal the window keeps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(layout): the startup sweep keeps a row any partition's shutdown list names live (dark, layout-owner build only) The window reads one shutdown list merged from every partition, so a stale list in one partition can mark another's workspace live. The sweep now keeps a row live by its own partition's rule or any partition's list, so it never drops a row the window keeps. A session from before the tab bar is swept as the window sweeps it (parity profile added). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: E2E Test <e2e@test.local> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d47e3de088 |
Three small fixes in the layout-owner build: repo reads, dragged-out pane folder, cancelled chat reader (dark, layout-owner build only) (#31003)
* Main routes a floating terminal or removed project without reading every project (dark, layout-owner build only) In a layout-owner build, finding a worktree's save partition checked for a project registered on two machines by listing every project on every call, including ids that cannot belong to a project. Skip that list for ids with no registered project, and reuse the list a hydration pass already read. * A dragged-out pane with no shell yet opens its new tab in the pane's folder (dark, layout-owner build only) In a layout-owner build, main builds the dragged-out pane's new tab and the window shows main's tab, so the window's folder for the pane never reached it and the new shell started in the source tab's folder. The window now sends the pane's folder with the move as an optional field, and main uses it when the moved pane has no terminal yet. An older main drops the field and keeps today's folder. * A chat cancelled while it is being created stays closed when the late reply arrives (dark, layout-owner build only) In a layout-owner build, cancelling a new chat on this computer closes it on the host without writing a cancellation record, and the host still answers the in-flight create as successful. That answer then rebuilt the closed chat's conversation reader, which nothing would ever close. The cancel now notes the closed chat in memory, and the create answer skips the reader for it. No message between the window and the host changes. --------- Co-authored-by: E2E Test <e2e@test.local> |
||
|
|
40ac8e5f9b |
fix(remote): keep a paired host's undelivered tab close across a reload (#30758)
* fix(remote): keep a paired host's undelivered tab close across a reload * fix(remote): show a tab again when its held close expires * fix(remote): one choke point shows every held close the store drops * fix(remote): keep held closes while the pairing is unknown; tidy dispositions * fix(remote): save a held close for a long path-named editor tab |
||
|
|
59a9da22a0 |
refactor(pane-layout): retire the unread intent tag; renamedLeafIds is the one layout delta (#30734)
No host ever read intent: 'gesture'. The client stops sending it, the params schema strips it from older clients, and docs/reference records the rule that a snapshot never carries intent. |
||
|
|
6c40f9190d |
Editor files that share a path across two hosts: the tab lookup, the load report and the phone close match the right file (dark, layout-owner build only) (#30569)
* fix(layout): two owners' files at one path keep their own host, load report entry and phone retire (layout-owner build only) The bare-path tab's file lookup took the first record of its path, the load report keyed file records by path alone, and a headless host's phone retire found and removed records by path. * fix(layout): a path-id preview tab's flag stays on its own owner's record, and a stale phone id retires nothing (layout-owner build only) |
||
|
|
613c5986e3 |
Editor records keyed by owner, phone active file, and a bound on frame holds (dark, layout-owner build only) (#30473)
* fix(layout): editor file records keyed by path and owner, so two hosts' files at one path stay apart (layout-owner build only) Opening a runtime-environment file at a path a local file already held replaced the local record, the stored-session patch and unsaved drafts matched by path alone, a move took every owner's record of the path, and closing a bare-path tab removed both owners' records. * fix(mobile): a windowless host's editor tab under its owned id shows as active, and retiring it clears the active file (layout-owner build only) * fix(layout): a layout change's tab and pane holds end 15 s after it was sent when main never answers (layout-owner build only) * fix(layout): main's view reads and prunes keep an active file under its owned id (layout-owner build only) |
||
|
|
ecd83b7b48 |
Resume terminal conversations in native chat without waiting for History (#29150)
* fix(native-chat): identify the owner when resume create is refused * test(native-chat): consolidate transcript refusal cases * test(native-chat): keep ownership coverage at the launch boundary * test(native-chat): update adoption fixture for early ownership admission * test(native-chat): align resume admission assertions with host ownership * test(native-chat): keep RPC coverage focused on ownership proof * test(native-chat): assert blank request presence before optional source * fix(native-chat): admit the actual CLI source during chat creation * fix(native-chat): satisfy source admission lint checks * test(native-chat): provide the waiting hook prompt * test(native-chat): isolate source admission fixtures from host homes * test(native-chat): canonicalize adoption fixture directories * test(native-chat): preserve early ownership admission in source stack * Resume CLI conversations directly in native chat * fix(native-chat): preserve resume refusal behavior and honest transcript feedback * fix(native-chat): retain import read evidence in typed result * Use the header test fixture record type * Consolidate launch owner imports and correct feedback assertion * Keep existing resume translation in runtime catalog * fix(native-chat): preserve admitted source and prepared execution home * fix(chat): retain source evidence for dispatched adoption * fix(chat): keep source capture within module limits * Keep deferred chat publication in the shared launch owner * test(chat): allow host evidence in create routing * Satisfy launch publication checks and scope slot cleanup * Preserve canonical tab membership on projected reorders * Use recorded launch ownership for surface recovery * Reserve deferred chat order through existing group actions * Pass the active layout owner into launch cancellation * fix(native-chat): admit an unsaved transcript under a linked account folder A missing transcript stayed unresolved while its account root resolved through symlinks, so containment failed and a not-yet-saved conversation showed a load error instead of the unsaved notice. * refactor(native-chat): move the newest-item import window into its own module Keeps journal-legacy-import.ts under the line limit after the source identity observer landed on top of the streaming import. * fix(native-chat): restore the refusal error import lost in the main merge * test(native-chat): pass the layout owner in the layout-owner cancel test main's new test predates launch cancellation taking the active layout owner. * fix(native-chat): admit Pi transcripts against the root Session History walks A PI_CODING_AGENT_DIR that already ends in sessions, or any custom folder, made admission expect <dir>/sessions while the lookup searched <dir>, so a found conversation was refused. * refactor(native-chat): admit and look up CLI transcripts in one store per account Each agent's import described where an account keeps its conversations twice, once for the path check and once for the lookup, and the two could disagree (the linked-folder and Pi custom-root refusals). The import now names one conversation store per account; the create path resolves it once and both the hook-path check and the fallback lookup judge that same store. OpenCode's store is its exact database. * fix(native-chat): keep a History resume that joins a terminal resume alive when the terminal closes A History click that joins a pending terminal-tab resume now takes the launch over: the terminal hand-off runs at join time and the create resumes by conversation id, so closing the terminal before create no longer abandons the shared launch. * refactor(native-chat): move the launch source hand-off into its own module * fix: pick the chat that adopts a header resume the way History does |
||
|
|
eaa1ee131d |
A CLI-started terminal shows once in its workspace, and a pane close main has no pane for closes in the window (dark, layout-owner build only) (#30391)
* fix(layout): a workspace only main held shows main's tab on the window's first visit (dark, layout-owner build only) * fix(layout): a live terminal no pane shows goes through main, which names the tab it holds; a pane close main has no pane for closes here (dark, layout-owner build only) * test: no type assertions in the CLI-first tests * refactor: the switch-off build keeps today's live-terminal mint as it was * review: frame sync under the line limit; the first-visit test names exactly main's tabs * fix(layout): adopt takes the recorded pane only while it is unbound * fix(layout): a refused live-terminal adoption is declined, not counted as shown |
||
|
|
ecb1d6859c |
refactor: compatibility folders name their entry modules instead of an index barrel (#30269)
Deletes remote-layout-snapshot/index.ts and workspace-layout/old-client/index.ts. The compat-boundary check now lists each folder's entry modules, so deleting the folder plus its listed importers' import lines still removes the path. |
||
|
|
2a8b4c31a5 |
Phone and old-client tab lists keep editor tabs under owned ids (dark, layout-owner build only) (#30216)
With the layout-owner switch on, the session-tabs projection and the windowless host's editor projection matched an editor tab to its file by path only, so tabs under an owned id got no row (or lost their tab id, color and pin). Both now use the shared editorTabShowsFile matcher, which is path equality with the switch off. |
||
|
|
25fb579c12 |
fix(layout): refuse a layout change on a workspace a WSL move has paused (dark, layout-owner build only) (#30198)
* fix(layout): refuse a layout change on a workspace a WSL move has paused, and say why (dark, layout-owner build only) * fix(layout): refuse without a toast; keep the move message in main |
||
|
|
95d6018323 |
Main keeps editor tabs under owned ids and in-place re-owns in step (dark, layout-owner build only) (#30206)
A restored tab re-owned inside its own workspace now tells main through renameEditorFiles with the file's new record; main skips it for paired servers. Main's load, save, close and tab-host read an editor tab's file by its owned id as well as its path, behind the layout-owner switch. |
||
|
|
31f766515d |
Main records when a workspace's default tabs were applied (dark, layout-owner build only) (#30176)
* feat(layout): main records a workspace's default-tabs mark from the window (dark, layout-owner build only) * fix(layout): record the default-tabs mark after the tab creates it covers (dark, layout-owner build only) * test(e2e): strict default-tabs mark and exact tab count across a crash (dark, layout-owner build only) |
||
|
|
483a17becc |
Reopening a file in its open tab sends the main process the changed file record (dark, layout-owner build only) (#30197)
* fix(layout): a reopen that rewrites an open tab's file record reaches main (dark, layout-owner build only) * fix(layout): compare reopened editor records without a new renderer import |
||
|
|
b9b1b85261 |
test(layout-owner): an older session's moved editor tab gets its workspace's id on save and from the first frame (#30172)
Sessions saved before #29930 can hold a moved editor tab whose worktree id still names the workspace it came from. Pin that the saver writes the workspace's own id and that the window's tab carries it after main's first snapshot. Tests only. |
||
|
|
bf97836ae0 |
Pane titles stay layout: the window stops writing tab names as pane titles, and main loads them as the window does (dark, layout-owner build only) (#30136)
* refactor(shared): move the pane title sanitizer to shared, unchanged So main's layout Loader can apply the same rule the window applies on load. * fix(layout): pane titles stay layout in a layout-owner build (dark) The window stops writing a revealed terminal's tab name as its pane title, and main's Loader drops a lone pane title that only repeats the tab's name, as the window's load does. Both behind ORCA_LAYOUT_OWNER_IS_MAIN. * fix(layout): the Loader compares a lone pane title with the tab bar's quick command label too (dark) As the window's row hydration does when the row has no label. * fix(layout): the Loader compares a lone pane title with the tab bar label, the live title (dark) |
||
|
|
c2021ebc47 | fix(layout-owner): main records a quick command tab's label on create (dark, layout-owner build only) (#30112) | ||
|
|
d9a61432c8 |
Main's layout Loader keeps one pane per terminal on restore (dark, layout-owner build only) (#30110)
* fix(layout): main's Loader repairs a terminal bound in two panes of one tab (dark, layout-owner build only) * fix(layout): a window save stores main's repaired pane tree, so the duplicate-terminal repair survives the save (dark) * test(layout): the SSH duplicate-terminal case builds an SSH-hosted tab |
||
|
|
609f3425ad |
Record simulator tab opens and closes in main (dark, layout-owner build only) (#30034)
* feat(layout): record simulator tab opens and closes in main (dark, layout-owner build only) * chore(layout): note why sleep/wake params copy OnTab |
||
|
|
4f5d464ca6 |
Open into the window's emptied pane beside a split branch in one layout command (dark, layout-owner build only) (#30039)
* Open into the window's emptied root beside a split branch in one layout command (dark, layout-owner build only) A tab opened into a group the window kept empty reaches main with the split that places it, now also when the group's neighbour is itself split (named by the branch's first and last groups) or is another empty group main lacks. Main makes the group with its tab beside that branch. Switch-off builds send no such command. * Comment the refusal of a branch main's tree does not hold (dark) |
||
|
|
fa962dd1cd |
Record chat tab opens and closes in main (dark, layout-owner build only) (#30019)
* Record chat tab opens, closes and host-listed chats in main (dark, layout-owner build only) A chat tab the window opens, closes, or adds and removes from a host listing of this machine's chats now reaches main as openAgentSessionTab, closeTabs or setTabProps under the window's ids, so the window's save no longer teaches main a chat tab. openAgentSessionTab takes the window's place, creation time and new split group, and the same open again changes nothing. Switch-off builds are unchanged. * test(layout): a paired server's chat tabs send nothing to main (dark) * fix(layout): keep a windowless host's chat-tab saves as they were; send synced chat opens in tab order (dark) Only a window's openAgentSessionTab (main marks it drawn on layout.apply) takes the new rules: a repeat changes nothing, a taken id is refused, and a workspace with no group takes the window's. A windowless host's own saves mint a fresh group and keep a chat whose id another tab holds, as before. A host listing's new chats are recorded in tab order, so each open's anchor already reached main. * refactor(layout): one rule set for a chat tab open; the windowless caller names only a group it holds (dark) Drops the per-sender drawn flag. The windowless save passes its remembered active group only when the workspace still holds it, so it mints a fresh group as before. Refusing a taken chat tab id is gated on the build switch: shipped builds still save the duplicate id. |
||
|
|
a349bfa6cc |
Agree with main on first-group ids, tab order and the preview flag (dark, layout-owner build only) (#29987)
* Agree with main on a workspace's first group and the tab order, and stop writing a preview flag main never stores (dark, layout-owner build only) Switch-on builds only; switch-off behaviour is unchanged. Group ids: a window save made before main's frame with a workspace's first tab carries none of that workspace's tab-bar records, so the Loader rebuilt the tab in the group it names for the workspace while main had minted a random one: every workspace's first save replaced main's group. Main now names a workspace's first group as the window's root group and the Loader do (`workspaceRootGroupId`), or takes the sender's when the window drew the tab in a group main lacks (the window keeps its emptied group); the window's `ensureGroup` names it the same way. The detector reads a lone group with no stored tree as that group's leaf, as main's own save writes it. Empty groups: the window never saves a group it holds empty, so a save cannot bring back one main closed (locked by a test). Tab order: a reorder the window makes itself (an anchored CLI or agent create, a reopened tab's old slot) is recorded in main as `moveTab` steps at the store boundary; callers that already asked main pass `layoutOwnerHandled`. The reopened-editor record this replaces is deleted. A paired client's move (`ui:moveSessionTab`) goes to main first through the tab-bar drop path. Preview: activating a tab removed its preview flag by writing `isPreview: false` on every tab; main stores no flag, so every activation changed the tab in main's model. The window now drops the flag instead. * Record a reopened tab's place in main every time, leave a move into a tab's own group alone, and keep a preserved preview untouched (dark, layout-owner build only) Review fixes, switch-on only: - A reopened tab's place is recorded as one moveTab whenever its old position is known, as the deleted editor-only record did, now for every kind; the reorder it may make is not recorded twice. - A move into the tab's own group (a paired client can send one) goes to today's no-op instead of a moveTab that reordered the tab. - Activation that preserves a preview returns the tab unchanged. Tests: switch-off first group stays minted; tabOrderMoves refuses a duplicate order. * Send a paired client's tab reorder to main as its whole order (dark, layout-owner build only) A client reorder names the group's full order, but it went to main as one moveTab of the named tab, so [a,b,c] -> [b,a,c] naming c moved nothing anywhere. It now takes the store's reorder path, whose record turns the whole order into main's moves (tabOrderMoves), the one rule for every window-side reorder. Moves and splits still go main-first. |
||
|
|
5b69634c0a |
Agree with main on main-made terminal tabs and sleeping-agent records (dark, layout-owner build only) (#29967)
* feat(layout): main-made terminal tabs drawn with main's facts; resume records reach main record-only (dark) In layout-owner builds only: - A terminal tab main made itself (drag-out, CLI or host terminal, live-surface adoption) is drawn with main's createdAt and creation fields from the frames, and the window sends no create for it. - An editor tab main records takes the time the window drew it. - Every change of the window's agent resume records (live and quit captures, dropped records) reaches main through record-only sleep and wake commands, which stop or start nothing. - A terminal row's remount generation is declared the window's. * fix(layout): real wake reaches main before the record drop; drag-out keeps a frame-drawn tab (dark) In layout-owner builds only: - A resumed pane's wake is sent before the window drops its record, so main's wake still finds the pane sleeping. - A drag-out whose new tab main's frame already drew shows the pane in that tab instead of throwing "Terminal tab already exists". - A main-made tab keeps the window's shell, cwd, host-runtime and launch agent where main's copy names none (a runtime create records a shell only when its launch named one). - No record-only wake for a tab the window closed; main dropped its records with the tab. - Tests for the resume-record follower. * fix(layout): a dragged-out chat pane's new tab opens in chat view in main (dark) Main's movePaneToNewTab now gives the new tab viewMode 'chat' when the moved pane is its source tab's chat pane, as the window's own drag-out does. In a layout-owner build the window adopts the row main's frame drew, so without this the tab opened in terminal view. * fix(layout): a bound pane with a resume record starts as a no-op; resume records only for held tabs (dark) - startPane checks the pane's live terminal before its resume record: a running agent's live or quit record does not make its pane asleep. A pane with no terminal and a record is still refused. - The window sends record-only sleeps, like record-only wakes, only for tabs it still holds; a capture from a stale agent status no longer names a tab main already closed. |
||
|
|
37c314bcc9 |
Send a browser tab's window times to main with its open (dark, layout-owner build only) (#29988)
* fix(browser): keep slept workspace browser tabs off the reopen-closed list
Sleeping a workspace closed its browser tabs as if the user had closed them, so
Reopen Closed Tab could bring back a tab that was only slept. Sleep now closes
them with a 'sleep' reason that skips the reopen stack while still releasing
host pages, doc grants and webviews as before.
* feat(layout): send a browser tab's window timestamps with its open (dark)
A layout-owner build's openBrowserTab carried no createdAt, so main stamped the
tab-bar entry and the browser record with its own apply time and the window's
next save diverged on createdAt for both. The window now sends both times, as
createTerminalTab does; only switch-on builds send them.
* Revert "fix(browser): keep slept workspace browser tabs off the reopen-closed list"
This reverts commit
|
||
|
|
f7a8e633af |
Send a terminal tab's own changes to main first, from every caller (dark, layout-owner build only) (#29904)
* feat(layout): route a terminal tab's own changes to main first (dark) Rename, color, agent and vault titles, view mode and an ended launch agent now reach main as layout commands from every caller, in layout-owner builds only. Main's create also takes the window's createdAt, and frames keep the window's pane mount generation. * fix(layout): toggle from the mode sent to main, give each agent title its own answer (dark) A second view toggle before main answers the first now flips from the mode already sent, so the tab ends where a window-owned build would. An agent-title batch applies each tab's title when main answers that tab, and a paired server's tab at once. Drops the frame merge's generation field: the window's own save already carries it. Reverts unrelated formatting. * fix(layout): clear a pending view mode only on its own send's answer (dark) * chore: restore three files the commit hook reformatted outside this change * fix(layout): send one agent title per tab while main has not answered (dark) The window keeps a tab's first generated title, so a second prompt's title sent before main answered the first would leave main on the second and the window on the first. |
||
|
|
9cc5d6b427 |
Keep an editor tab Orca moved to its file's workspace across restart; record editor renames in main (dark) (#29930)
* fix(editor): keep an editor tab Orca moved to its file's workspace across restart The move saved the tab under the file's owned id and left the tab's workspace pointing at the old one. Restore rebuilt a local file under its bare path, so the tab named no open file and was dropped. Restore now maps a saved owned id to the id it rebuilds, and the move gives the tab its new workspace. * refactor(editor): delete reorderFiles, which nothing calls * chore: prune the renderer layout-writer baseline * fix(layout-owner): keep diff, preview and conflict records when a frame redraws editor tabs Switch-on builds only. A frame rebuilds the window's tabs through the restore builders, which drop every diff, conflict and check tab (restore has no record for them), and the store patch dropped every open file the frame did not carry. Frames carry only edit-mode files, so the next frame after opening a diff closed it in the window while main kept it, and a markdown preview kept its tab but lost its record. The rebuild now keeps a transient tab whose record the window holds, and a held record goes only with the last tab showing it; a record no tab shows (a conflict review's selected file) stays. * feat(layout-owner): record renamed open files and closed conflict placeholders in main Switch-on builds only; switch-off behaviour is unchanged. A rename or move in the explorer rekeys open editor, preview and diff tabs in the window and main learned it only from the window's save. The window now records the rekey as a `renameEditorFiles` command, under the ids it chose (bare path, owner-qualified id, diff id), one per workspace, with each edit-mode file's new record. It records rather than asks first: the rekey is atomic with drafts and the autosave echo gate, and its answer gates the move coordinator. Main's path-keyed `editorFilesRenamed` transition, which nothing produced and which could not match owner-qualified or diff ids, becomes this command. A git status poll that sees a deleted-side conflict resolved drops its placeholder record but leaves the tab to the tab reconcile; frames then brought main's edit record back. Main now closes that tab. * test: type the rename command as a window layout command * fix(editor): map a moved tab's owned id on restart only; main records a renamed file only for a tab it shows * fix(editor): restore a moved tab's file under the owned id the tab names Restart and frames now agree: neither rekeys the tab, and a frame that redraws one workspace never points its tab at a same-path file in another. A rename that changes nothing leaves no empty file list. * test: a moved tab's owned id stays apart from a sibling's same-path file in any frame |
||
|
|
64e544a442 |
feat(mobile): Resume in New CLI from a phone chat tab (#29783)
* feat(mobile): Resume in New CLI from a phone chat tab A chat tab on the phone had only Close in its long-press menu, so a chat that would not load left no way to reach its conversation. The phone now offers Resume in New CLI, like the desktop tab menu: the chat's host prepares the conversation and the phone opens a copy in a new terminal tab. The resume and fork startup builders move to src/shared so the desktop and the phone build the same command. * test(mobile): type the fake terminal create instead of casting its calls * fix(mobile): pass the host's fork source as is; match the quoted args in the test * test(mobile): give the fake host client the untyped mock the other client fakes use * style: format the shared resume builder and phone fork files * Fix mobile chat CLI fork creation on owning host * Fix mobile chat fork lint and split create owner helpers * Keep chat fork beside phone anchor across tab groups * Combine mobile terminal runtime type imports * Repair mobile create CI contracts after chat fork extraction * Classify chat fork preflight refusals and preserve mobile create recordings * Keep route-owned create cleanup quiet after unmount * Preserve typed runtime repair refusal in chat fork preflight * Preserve checked store through mobile chat fork preflight * Register mobile chat fork runtime fixture before test setup * Remove unused mobile create recording value * style(mobile): format the chat fork create files; move fork reply messages into the fork feedback module Formatting pushed the create hook past its line limit, so the fork-only reply message, partial-success warning and host capability check now live beside the other fork messages. Same order and copy as before. * Keep quick command mutation aligned with formatted request * Keep mobile chat CLI forks in terminal view * Pass mobile chat resolution its existing controller arguments * Match the mobile create fixture mock layout * Keep mobile chat CLI forks open when placement is unavailable * Brace placement contract fixture checks * test(mobile): keep the prompt controller mock under its line limit after the main merge * Expose pane admission in the runtime test fixture type * Complete the chat fork contract controller fixture |