Skip to content

Commit 91d93e5

Browse files
authored
Delete old-persistence migration tech-debt (#232)
2 parents a422838 + e32207b commit 91d93e5

36 files changed

Lines changed: 158 additions & 1377 deletions

docs/specs/alert.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ Public `status` is a projection — first match wins:
3333

3434
Persist `status`, `watchingEnabled`, `todo`, and sanitized `notification`. Restore `todo` and `notification`, then restart WATCHING only if `watchingEnabled` is true. Restore must not recreate protocol progress, command-exit arms, or a fresh ring; replay filtering in `docs/specs/terminal-escapes.md` prevents old terminal output from firing notification side effects again.
3535

36-
Source of truth: `migrateTodoState` in `lib/src/lib/alert-manager.ts` defines the legacy-TODO-value migration to boolean.
37-
3836
## Attention
3937

4038
`attentionSessionId` is set only by explicit user actions that plausibly mean "I am looking at this Session":

docs/specs/dor-browser.md

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ Two independent axes define a browser pane:
2525
| Render | `ab-screencast`, `ab-popout`, `iframe` |
2626

2727
The render axis is a pane parameter, not a separate surface type. The `dor` CLI
28-
still reports `iframe` or `agent-browser` as a legacy/informative surface type;
29-
that is derived from `renderMode`.
28+
still reports `iframe` or `agent-browser` as an informative surface type derived
29+
from `renderMode` (never stored).
3030

3131
Source of truth: `lib/src/components/wall/BrowserPanel.tsx`,
3232
`lib/src/components/wall/browser-surface.ts`, `lib/src/components/Wall.tsx`
@@ -46,15 +46,12 @@ type BrowserPanelParams = {
4646
wsPort?: number;
4747
binaryPath?: string;
4848
syncEngaged?: boolean;
49-
poppedOut?: boolean; // legacy migration only
5049
};
5150
```
5251

5352
Invariants:
5453

55-
- `renderMode` is canonical. Legacy params (`surfaceType: 'iframe'`,
56-
`surfaceType: 'agent-browser'`, `poppedOut`) are migrated by
57-
`resolveRenderMode`.
54+
- `renderMode` is canonical.
5855
- `url` is the canonical target across render swaps and relaunches. Agent-browser
5956
mirrors the newest non-blank active tab URL into params; iframe persists only
6057
navigations initiated by Dormouse chrome.
@@ -513,7 +510,7 @@ Source of truth: `lib/src/lib/platform/types.ts`,
513510

514511
When changing browser-surface behavior:
515512

516-
- `renderMode` is canonical. Never reintroduce `surfaceType: 'iframe' | 'agent-browser'` or `poppedOut` as stored stateextend the `resolveRenderMode` migration instead, and update the kind-mapping table in `docs/specs/glossary.md` if adding a render mode.
513+
- `renderMode` is canonical. Never reintroduce `surfaceType: 'iframe' | 'agent-browser'` or `poppedOut` as stored state; update the kind-mapping table in `docs/specs/glossary.md` if adding a render mode.
517514
- Never move a browser surface's DOM. Lath's leaf div is never re-parented, so an `<iframe>` never reloads and the screencast canvas never moves mid-click (which would break click synthesis) — do not add any re-parenting/reordering that would defeat this.
518515
- A new agent-browser subcommand must be added to `AGENT_BROWSER_ALLOWED_SUBCOMMANDS` (`lib/src/lib/platform/types.ts`); the host-side allowlist is the security boundary, not the CLI.
519516
- External-binary spawns go through `spawnAndCapture` (`dor-lib-common`), never raw `child_process`see `docs/specs/dor-cli.md`Spawning External Binaries.

docs/specs/glossary.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ Today every Pane holds exactly one Surface, but the model reserves multiple Surf
3333

3434
Three related enums name a Surface's kind at different layers. This table is the canonical mapping:
3535

36-
| Surface | Persisted `surfaceType` (`docs/specs/transport.md`) | `renderMode` (`docs/specs/dor-browser.md`) | CLI `SurfaceType` (`dor`, legacy/derived) |
36+
| Surface | Persisted `surfaceType` (`docs/specs/transport.md`) | `renderMode` (`docs/specs/dor-browser.md`) | CLI `SurfaceType` (`dor`, derived) |
3737
|---|---|---|---|
3838
| terminal Session | `'terminal'` (the default, omitted from the row) || `terminal` |
3939
| browser, iframe renderer | `'browser'` | `iframe` | `iframe` |
4040
| browser, screencast renderer | `'browser'` | `ab-screencast` | `agent-browser` |
4141
| browser, popped-out renderer | `'browser'` | `ab-popout` | `agent-browser` |
4242

43-
For browser Surfaces `renderMode` is canonical; the CLI column is derived from it for backwards-compatible `dor` output and is never stored.
43+
For browser Surfaces `renderMode` is canonical; the CLI column is derived from it for `dor` output and is never stored.
4444

4545
A **Session** runs the full six-axis model below. A **browser Surface** participates only where a web view meaningfully can:
4646

@@ -70,7 +70,7 @@ A Surface never floats free: it sits in a **Pane**, every Pane belongs to exactl
7070
| **Window** | One or more Workspaces. The OS frame (the standalone Tauri window) or the host frame (a VS Code window). | host (Tauri / VS Code) |
7171
| **Workspace** | A named set of Panes and their Surfaces, plus the layout that arranges them (Lath layout snapshot + doors). Exactly one **Wall** renders one Workspace. | `lib/src/components/Wall.tsx` at render time; persisted per `docs/specs/transport.md` |
7272

73-
A **Workspace** is the durable grouping a user thinks of as "a window's worth of panes." It has a `WorkspaceId`, a user-facing `name`, the Surfaces it contains, and the layout that arranges them. The pre-workspace model had exactly one implicit Workspace per Window; the model now allows several.
73+
A **Workspace** is the durable grouping a user thinks of as "a window's worth of panes." It has a `WorkspaceId`, a user-facing `name`, the Surfaces it contains, and the layout that arranges them. The model allows several Workspaces per Window, though the app mounts one at a time today.
7474

7575
How many are visible at once is host-specific:
7676

@@ -241,7 +241,7 @@ Use glossary names instead of these. The left column retains a meaning only wher
241241
| **session** | The durable identity of a **terminal Surface**. Do not use it for the Activity projection (that is `ActivityState`, not `SessionUiState`), nor for the agent-browser daemon's lowercase `session` string (`dormouse.1.<key>`), which is not a Dormouse durable unit. |
242242
| **terminal** | Keeps its meaning for the `xterm.Terminal` instance. Prose meaning "the whole thing" is **Session** (a terminal Surface). |
243243
| **surface** | A glossary term, not retired: the durable occupant of a Pane (a terminal Session or a browser Surface). Use **Session** only for the terminal kind; use **Surface** when a statement holds for both. |
244-
| **panel / pane / leaf** | Prefer **pane** for the layout slot; **leaf** is Lath's tree node for the same thing (they map 1:1). "panel" survives only in legacy persisted-blob field names. |
244+
| **panel / pane / leaf** | Prefer **pane** for the layout slot; **leaf** is Lath's tree node for the same thing (they map 1:1). "panel" survives only in React component names (`TerminalPanel`, `BrowserPanel`, `IframePanel`). |
245245
| **tether** | Remote-control term only: a display showing "tethering to \<device\>" has ceded terminal size authority to a remote viewer (`docs/specs/remote-api.md`). Not a layout term — do not use it for Pane/Door relationships. |
246246

247247
Remote-only vocabulary (**Viewer**, the wire-level `DirectoryEntry` projection) is defined in `docs/specs/remote-api.md` § Terminology.

docs/specs/layout.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ Extreme case: a single door with a very long title, with more doors on both side
147147
148148
A **Workspace** is one Wall's worth of Surfaces (terminal Sessions and browser surfaces) plus its layout, with a user-facing name. The standalone Window hosts several Workspaces but mounts only one — the **active** Workspace — at a time. Each Workspace owns its own Content (Lath layout) and Baseboard (doors).
149149

150-
What exists today lives behind the `dormouse.flags.workspaces` flag (`WORKSPACES_FLAG_KEY` in `lib/src/lib/feature-flags.ts`, **off by default**): the in-memory workspace model and its container verbs (`createWorkspace` / `closeWorkspace` / `renameWorkspace` / `setActiveWorkspace` in `lib/src/lib/workspace-store.ts`), the union projection (`computeWorkspaceUnion` in `lib/src/lib/workspace-union.ts`), and Window persistence (`PersistedWindow`, `docs/specs/transport.md`). `setActiveWorkspace` changes the active id in the model but does not yet re-render the Wall, and the single-Workspace cap is still in place. With the flag off, the app persists a bare `PersistedSession` and runs exactly one implicit Workspace — byte-identical to pre-workspace behavior.
150+
What exists today lives behind the `dormouse.flags.workspaces` flag (`WORKSPACES_FLAG_KEY` in `lib/src/lib/feature-flags.ts`, **off by default**): the in-memory workspace model and its container verbs (`createWorkspace` / `closeWorkspace` / `renameWorkspace` / `setActiveWorkspace` in `lib/src/lib/workspace-store.ts`), the union projection (`computeWorkspaceUnion` in `lib/src/lib/workspace-union.ts`), and Window persistence (`PersistedWindow`, `docs/specs/transport.md`). `setActiveWorkspace` changes the active id in the model but does not yet re-render the Wall, and the single-Workspace cap is still in place. With the flag off, the app persists a bare `PersistedSession` and runs exactly one implicit Workspace.
151151

152152
The strip UI, real switching, and lifecycle UX are staged in [Future](#future) — this spec's `## Future` is the single rollout ledger for the feature; other specs link here.
153153

@@ -264,7 +264,7 @@ Swaps session **content** between two panes — the layout shape is unchanged. A
264264
- **Neighbor** — the sibling still exists: split beside it on the original edge (at 50/50).
265265
- **Fallback** — split beside a caller-supplied live reference (the selected pane, else the first pane) via `autoEdge`; into an empty tree the leaf becomes the root.
266266

267-
A Door persisted before Lath has no token; the Wall synthesizes a neighbor-tier one from its legacy `{neighborId, direction}` fields (`legacyTokenFromDoor`). A door dragged out of the baseboard skips the token entirely and inserts at the hit-tested drop position the user chose (`onExternalDrop``lath.insertLeaf`).
267+
A door dragged out of the baseboard skips the token entirely and inserts at the hit-tested drop position the user chose (`onExternalDrop``lath.insertLeaf`).
268268

269269
## Inline rename
270270

@@ -295,15 +295,15 @@ For a terminal Surface the pane ID is its session ID. `TerminalPane` calls `getO
295295

296296
### Session persistence
297297

298-
Layout, scrollback, cwd, minimized items, user-pinned titles, untouched state, and alert state are saved to persistent storage via a debounced save (500ms). The layout persists as the native Lath format (`lathLayout`; `docs/specs/tiling-engine.md` → "Persistence and migration"); the legacy dockview `layout` key is no longer written. Derived command/app labels shown on minimized doors are display-only and are not persisted as user-pinned titles. Every Lath store commit (add/remove/resize/swap/meta, including the active-pane the layout records) *schedules* the debounced save; content changes (terminal output, activity/TODO, pane title/command state, minimized-door changes) only *mark the session dirty*; a 30s heartbeat persists only when the session is dirty, so an idle app stops writing. Saves are flushed immediately and unconditionally on PTY exit, `pagehide`, and extension shutdown requests — the correctness net for any dirty-trigger gap. The dirty-gating mechanism and the store-level identical-value backstop are specified in `docs/specs/standalone.md` §Persistence.
298+
Layout, scrollback, cwd, minimized items, user-pinned titles, untouched state, and alert state are saved to persistent storage via a debounced save (500ms). The layout persists as the native Lath format (`lathLayout`; `docs/specs/tiling-engine.md` → "Persistence"). Derived command/app labels shown on minimized doors are display-only and are not persisted as user-pinned titles. Every Lath store commit (add/remove/resize/swap/meta, including the active-pane the layout records) *schedules* the debounced save; content changes (terminal output, activity/TODO, pane title/command state, minimized-door changes) only *mark the session dirty*; a 30s heartbeat persists only when the session is dirty, so an idle app stops writing. Saves are flushed immediately and unconditionally on PTY exit, `pagehide`, and extension shutdown requests — the correctness net for any dirty-trigger gap. The dirty-gating mechanism and the store-level identical-value backstop are specified in `docs/specs/standalone.md` §Persistence.
299299

300-
In standalone, each Workspace's snapshot is wrapped in a Window snapshot that records every Workspace (name + layout) and which one is active, so all Workspaces — not just the mounted one — survive a restart. VS Code persists one Workspace per webview exactly as today (one snapshot per `WebviewView` / `WebviewPanel`). The persisted container types (`PersistedWorkspace`, `PersistedWindow`) and their migration live in `docs/specs/transport.md`.
300+
In standalone, each Workspace's snapshot is wrapped in a Window snapshot that records every Workspace (name + layout) and which one is active, so all Workspaces — not just the mounted one — survive a restart. VS Code persists one Workspace per webview exactly as today (one snapshot per `WebviewView` / `WebviewPanel`). The persisted container types (`PersistedWorkspace`, `PersistedWindow`) live in `docs/specs/transport.md`.
301301

302-
Saved snapshots are read through `readPersistedSession()`, which accepts the canonical object shape and defensively parses a JSON-stringified blob before validation and migration. This keeps malformed storage inert while covering hosts that hand back serialized JSON instead of the parsed object.
302+
Saved snapshots are read through `readPersistedSession()`, which accepts the canonical object shape and defensively parses a JSON-stringified blob before validation. A present-but-unreadable blob is logged and discarded, so malformed storage starts fresh rather than blocking startup, while hosts that hand back serialized JSON instead of the parsed object are still covered.
303303

304304
On startup, recovery is priority-based:
305-
1. **Resume** (webview hidden/shown, live PTYs): request PTY list + replay data from platform, `resumeTerminal()` for each (500ms timeout). Saved pane and door titles are seeded back via `setTerminalUserTitle()` (see `docs/specs/transport.md`) so persisted placeholder labels never replay as user pins. If the saved session covers every live PTY, restore the saved Lath layout when its leaf set matches (a pre-Lath save's legacy dockview `layout` blob is migrated into that single channel at the session read boundary) and reattach saved minimized items as doors. This still counts as a live resume when every live session is minimized, so recovery must not fall through to cold restore just because the visible `paneIds` list is empty.
306-
2. **Restore** (app restart, cold start): the Wall's `seed` hydrates from the restored Lath layout (the read boundary already migrated any legacy dockview `layout` blob one-way into it), else falls to (3); `restoreTerminal()` for each pane with saved cwd + scrollback, spawning each PTY with the current default shell selection
305+
1. **Resume** (webview hidden/shown, live PTYs): request PTY list + replay data from platform, `resumeTerminal()` for each (500ms timeout). Saved pane and door titles are seeded back via `setTerminalUserTitle()` (see `docs/specs/transport.md`) so persisted placeholder labels never replay as user pins. If the saved session covers every live PTY, restore the saved Lath layout when its leaf set matches and reattach saved minimized items as doors. This still counts as a live resume when every live session is minimized, so recovery must not fall through to cold restore just because the visible `paneIds` list is empty.
306+
2. **Restore** (app restart, cold start): the Wall's `seed` hydrates from the restored Lath layout, else falls to (3); `restoreTerminal()` for each pane with saved cwd + scrollback, spawning each PTY with the current default shell selection
307307
3. **Fallback/manual pane creation**: when no saved layout can be safely applied, add multiple panes as splits from the previous pane, and spawn each PTY with the current default shell selection
308308
4. **Empty state**: create a single new pane with the current default shell selection
309309

@@ -401,7 +401,7 @@ When changing layout behavior:
401401

402402
- Changing a command-mode binding or the mode-switch gesture: update the shortcut table here **and** `docs/specs/shortcuts.md` in the same edit.
403403
- Pane-header changes: this spec owns placement and sizing only. Bell/TODO behavior and visual states belong to `docs/specs/alert.md`; the mouse-override icon and banner to `docs/specs/mouse-and-clipboard.md`; the derived label to `docs/specs/terminal-state.md`.
404-
- Persisted-shape changes (`PersistedPane` / `PersistedDoor` / layout blobs) belong to `docs/specs/transport.md`add the migration note there.
404+
- Persisted-shape changes (`PersistedPane` / `PersistedDoor` / layout blobs) belong to `docs/specs/transport.md`update it there.
405405
- New pane chrome uses tokens from `lib/src/components/design.tsx` (see AGENTS.md Design); never raw color classes.
406406
- Pane spawn/kill/tween motion is owned by the Lath animator (`docs/specs/tiling-engine.md` → "Animation"); layout.md owns only the interaction behavior around it.
407407
- Anything workspace-strip or switching related stays under `## Future` (workspaces-rollout) until built.

docs/specs/standalone.md

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -246,19 +246,6 @@ trigger missed; the cache is boot-seeded from disk in `hydrate`, so the compare
246246
is valid from the first write. Source of truth: `session-dirty.ts`,
247247
`use-session-persistence.ts`, `standalone/src/tauri-session-store.ts`.
248248

249-
**Migration.** On the first boot after this change `load_session` returns null;
250-
if a legacy blob is still in `localStorage` under `TauriAdapter.STATE_KEY`, the
251-
adapter adopts it, persists it to the Rust store (through the store's normal
252-
write path, so it shares the coalescing), and removes the key — so WebKit stops
253-
rewriting it and its bloated WAL collapses on the next quit. The legacy blob is
254-
per-origin (window-shared), so adoption is gated to the `main` window — a
255-
second window never races the adopt-and-clear (removal criteria: `## Future`).
256-
Because the
257-
identical-value short-circuit above would swallow a `setItem` whose value equals
258-
the freshly hydrated cache, the migration hydrates with the pre-migration seed
259-
(null) and lets the `setItem` be a genuine change — localStorage is already
260-
cleared, so the Rust store is the blob's only remaining home.
261-
262249
**Durability on quit.** A clean quit durably writes the latest state before the
263250
process exits. `saveState` still returns after updating the cache and *firing*
264251
`save_session`, but the quit orchestrator (§Quit flow) now awaits the pipeline to
@@ -460,21 +447,3 @@ root `package.json` for the `dev:standalone*` orchestration.
460447
| `standalone/sidecar/clipboard-ops.js` | OS clipboard tiers (owned by `docs/specs/mouse-and-clipboard.md`) |
461448
| `standalone/scripts/build-sidecar-proxy.mjs` | Bundles `lib/src/host/` into the sidecar `.cjs` copies |
462449
| `standalone/scripts/dev-agent-browser.mjs` | `dev:standalone:ab` entry (owned by `docs/specs/transport.md`) |
463-
464-
## Future
465-
466-
Requirements for the next round of session-persistence work — what must be true,
467-
not how. (Detailed working notes live outside the specs while the work is in
468-
flight.)
469-
470-
**Retire the `localStorage` → Rust migration.** The one-time migration branch in
471-
`standalone/src/tauri-adapter.ts` (marked `SUNSET`, gated to the `main` window)
472-
cannot be removed on a version fence: the Tauri updater jumps directly from any
473-
old version to the latest, so there is no release after which "all shipped
474-
builds have migrated" — a pre-migration straggler updating much later would
475-
adopt nothing and lose exactly one session restore. Removal is therefore an
476-
acceptance decision, recorded here (2026-07-07): remove once a one-time lost
477-
restore for pre-0.12 stragglers is acceptable — recommended at least six months
478-
or two minor releases after the migration ships. Multi-window (the
479-
workspaces-rollout scope, `docs/specs/layout.md` `## Future`) does not block
480-
removal: the branch already gates adoption to the `main` window.

0 commit comments

Comments
 (0)