Commit Graph
2 Commits
Author SHA1 Message Date
Jinwoo Hong 9021133929 Add the inert workspace layout model, Loader and Serializer (#26748)
* feat(layout): add the inert workspace layout model, Loader and Serializer

One model of a workspace's tab layout (all tab kinds, groups, split trees,
pane bindings, titles/colors/pin, sleeping records) in src/shared/workspace-layout,
with a Loader from today's stored session partition and a Serializer back to it.
The structural rules check and the pure pane/group tree helpers move into the
same folder so the module can run them; their callers import the new paths.
Nothing in the app calls the new module yet.

* fix(layout): avoid toSorted in shared code and drop an unused helper

The mobile bundle includes src/shared and Hermes lacks Array#toSorted.

* fix(layout): one stored home per layout fact; Loader reports every rule it applies

The row's terminal id, preview flag, worktree id and execution host are now
derived on save instead of kept a second time. Groups never stay empty. The
Loader reports row/tab-bar disagreements, re-minted ids, worktree id and host
disagreements, keeps a row stored in two workspaces where the tab bar names it,
and gives a pane id repeated in a second tab a new unbound pane. The rules check
runs on what the Serializer writes. Fixtures become test fixtures, and a test
lists every model field with the one fact it holds.

* fix(layout): derive each tab's host from its owner and keep one preview fact per editor tab

An editor tab names its file's owner (an SSH target or paired runtime), every
other tab its partition. Where the stored tab and file preview flags disagree
the tab loads permanent, so a draft can never be replaced. A row terminal that
differs from the one derived from its focused pane is now reported.

* fix(layout): one binding kind, one title, one host rule; records follow a reassigned pane

A legacy row with no pane layout now loads as a one-pane tab bound to its
terminal (reusing the leaf its records name), so the null-panes branches and
legacyPtyId are gone. A pane given a new id because two tabs shared it takes
its sleeping and incarnation records, scrollback and focus with it. A terminal's
live title is one fact (the tab-bar label wins on load). Every tab-bar entry
names its execution host by one rule. The fact-location test is now keyed by
on-disk field and checked by mutating every source.

* fix(layout): one change report by construction; records without copies

The Loader now picks values by fixed precedence only and reports one
field-by-field diff of the stored session against what the next save
writes, over the disk-field tables (now in workspace-layout-disk-fields.ts,
with session fields derived from SESSION_FIELD_OWNERS). Sleeping and
closed-tab records live in their workspace without the pane key, tab and
worktree id their key and workspace already give; the Serializer fills them.
Tab-bar entries are written in the one tab order.

* fix(layout): name the change report's key-list parameter's type

* refactor(layout): key pane data by leaf, check the model itself, one legacy record

- Each pane's data (terminal, incarnation, title, sleeping agent) is one leaf record keyed by
  leaf id, and scrollback beside it is keyed the same way. Pane keys are derived only when
  saving. The Loader gives a repeated leaf id a new id before attaching records, so
  rekeyPaneRecords is gone; records naming no pane are carried as stored.
- The rules check runs on the model directly (ungrouped tab, empty group, group tree that
  disagrees with the groups, pane data without a pane); the disk-format rules stay separate.
- The row-ownership marker and topology revision move into one legacy record outside
  WorkspaceLayout, updated by updateLegacyPersistence after each apply.
- Fixtures include a terminal tab whose tab-bar id and terminal id differ.

* fix(layout): join the group ids the tree-mismatch detail names

* fix(layout): leave the stored revision map as is when no repo advances
2026-10-09 15:05:22 -04:00
Jinwoo Hong 7f0e067256 test(e2e): layout oracle scenarios with window, headless serve and SSH (#26674)
* test(terminal): check the runtime's workspace layout against structural rules

A pure rules check over the runtime's session partitions: a terminal in two
panes, a pane in two tabs, a tab outside exactly one group, tab rows and tab
bar disagreeing, two tab orders, and ids changing for the same pane, tab or
group across an update. Wired into the seeded layout model test after every
step, plus late-spawn cases (one known on main, kept as an expected failure).

* test(e2e): layout oracle scenarios with window, headless serve and SSH

Drives layout journeys through window gestures, CLI and paired-client runtime
commands, and after every step checks the runtime's layout against the
structural rules, the window's DOM, the paired-client and CLI views, the
expected end layout, restarts (relaunch, cold daemon, in-app daemon restart)
and per-pane terminal markers. Findings main still has are listed with their
cause; anything else fails. Includes headless orcad/Electron serve scenarios
for the serve issues and a Docker SSH scenario.

* test(terminal): check layout rules on every write and race client commands with the window

The layout model now checks the structural rules on every in-memory
workspace-session write, not only at each step's end, naming the writer
of any breach. Three documented two-writer transients (main mints a tab
row before the window writes its tab bar) are allow-listed by rule, op
and writer; they must still heal by the step's end.

New model ops race a stale window save against a paired client's tab
close, a host-created split, and main's PTY-exit retirement. A new file
runs main's updateMobileSessionPaneLayout (old phone pane trees) against
the real Store; the case with no topology revision is an it.fails: main
drops a live pane and its binding there.

* test(e2e): oracle expected outcomes, transient sampling, remount counts, more scenarios, latency

Every step checks the expected panes, which panes went away and that the rest
kept their terminals; a 100 ms sampler runs the rules between steps; xterm
creations are counted per pane and an unexpected recreation fails. Adds host
navigation, window reload (graph republication), quit right after a change
(quit-time save), an old client's stale pane tree, and a latency measurement
(command to drawn update, idle and under an output flood; no pass/fail).

* docs(e2e): describe the oracle's added checks and latency measurement

* test(e2e): #12723 SSH folder scenario, CI lane for the SSH oracle spec, short socket dir

Adds an SSH folder-workspace (no repo) scenario that checks the layout lives in
one partition across relaunch; registers the SSH oracle spec in the Docker SSH
lane's selection lists; headless hosts put long-TMPDIR profiles under ~/.orca-lo
instead of /tmp.

* ci(e2e): run the headless layout oracle in the job that builds orcad; Linux relaunch findings

The headless oracle spec needs out/orcad, which only the orca serve mode-switch
job builds, so it runs there (and fails rather than skips in CI if orcad is
missing). Records what the first Linux CI run found on main: after a relaunch
main can hold tab rows with no tab bar past the settle wait, and Electron serve
on Linux also lists a renamed terminal's title as null after a restart.

* test(e2e): join partition lists in the SSH folder scenario's log lines

* test(e2e): retry the latency spec's reads through a brief terminal-ownership gap

Linux CI saw terminal.list answer terminal_surface_ownership_unavailable while
the window re-published its terminals; reads retry for up to 10 s, commands do not.
2026-10-08 21:18:21 -04:00