Skip to content

Workspaces step 2b: Window container, dormant behind a flag#190

Merged
nedtwigg merged 5 commits into
mainfrom
workspaces-2b
Jun 30, 2026
Merged

Workspaces step 2b: Window container, dormant behind a flag#190
nedtwigg merged 5 commits into
mainfrom
workspaces-2b

Conversation

@nedtwigg

Copy link
Copy Markdown
Member

Stage 2b of the Workspace effort: introduce the Workspace / Window container and its supporting model, dormant behind a feature flag. Builds on the spec + stage-2a work already on main (#189).

Dormant by default

Everything here is gated on dormouse.flags.workspaces (localStorage, off by default). With the flag off, the standalone persists a bare PersistedSession and the app runs exactly one implicit Workspace — byte-identical to today (a test locks the flag-off save path doing no extra read). The flag gates the container so stages 3 (switch UI) and 4 (real multi-Workspace) can build on a structure that's already in place and exercised by daily use at N=1.

What's included

  • Persistence types + migration (session-types.ts): PersistedWorkspace / PersistedWindow and readPersistedWindow(), which migrates a pre-workspace bare PersistedSession (any version) to a single Workspace 1, drops unreadable inner sessions, and repairs a dangling activeWorkspaceId.
  • Feature flag (feature-flags.ts): isWorkspacesEnabled() / setWorkspacesEnabled().
  • Union projection (workspace-union.ts): computeWorkspaceUnion(){ ringing, todo, count } (only terminals ring; any surface may carry TODO; each attention-owing surface counts once).
  • In-memory model (workspace-store.ts): the list + active id with the container verbs (create / close / rename / setActive), subscribable for the stage-3 strip. Default single Workspace.
  • Standalone wiring (window-persistence.ts + the Tauri / browser-dev adapters): getState / saveState route through flag-gated helpers. Standalone-only — VS Code keeps one bare PersistedSession per webview, untouched.

Deliberately deferred (not in this PR)

  • VS Code union-on-chrome reflection — the union primitive exists, but feeding it per webview and reflecting onto the tab icon / view badge needs icon assets and runtime verification in VS Code; cleaner as a focused follow-up.
  • Stage 3 — the standalone workspace strip + union surfacing.
  • Stage 4 — the actual Wall mount/unmount on switchWorkspace (today the model switches the active id but does not re-render the Wall) and lifting the one-Workspace cap.

Spec status markers (glossary Implementation status, transport, layout, alert, vscode) are updated to reflect implemented-but-dormant vs. stages 3/4.

Verification

  • pnpm test — all packages green (dor, lib 729, standalone, website).
  • pnpm build — vscode-ext + website build clean.
  • standalone tsc --noEmit clean. No Rust changes (cargo check unaffected).
  • Not done: a live flag-on runtime smoke test — the path is unit-tested (load/save round-trip, migration, preserve-other-Workspaces, flag-off no-read) and dormant by default, but it has not been clicked through in a running app.

🤖 Generated with Claude Code

nedtwigg and others added 5 commits June 29, 2026 16:25
…, store, flag

Pure additive lib core for the Workspace container. No app wiring yet (that is
2b part 2, in the standalone adapter), so this is fully dormant and changes no
behavior.

- session-types: PersistedWorkspace, PersistedWindow, WorkspaceId, and
  readPersistedWindow() — reads a canonical/JSON window or migrates a bare
  PersistedSession (any version) to a single "Workspace 1" window; drops
  unreadable inner sessions; repairs a dangling activeWorkspaceId. Plus
  wrapSessionInWindow() and activeWorkspaceSession() helpers.
- feature-flags: isWorkspacesEnabled() / setWorkspacesEnabled(), a localStorage
  flag (dormouse.flags.workspaces) off by default — gates the whole container
  (stage 2b) and the UI built on it (3/4).
- workspace-union: computeWorkspaceUnion(surfaceIds, activitySnapshot) →
  { ringing, todo, count }, the display-only projection. Only terminals ring;
  any surface may carry TODO; each attention-owing surface counts once.
- workspace-store: in-memory model (list + activeId) with the container verbs
  (create/close/rename/switch), default single Workspace, subscribable for the
  stage-3 strip. closeWorkspace refuses the last; reactivates a neighbor.

Tests: +35 across the four units. Full lib suite green (710), tsc -b clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s (flag-gated)

The standalone now persists a PersistedWindow when the workspaces flag is on,
and a bare PersistedSession when off — so with the flag off (the default) the
stored blob and all behavior are byte-identical to today. Dormant.

- window-persistence (lib): activeSessionFromStored / storedValueForSession —
  flag-gated, pure translators between the host's stored top-level blob and the
  bare PersistedSession the shared restore/save code (reconnect, session-save)
  operates on. Flag off = identity passthrough. Flag on: load returns the active
  Workspace's session; save merges back into the active slot, preserving the
  other Workspaces. Migrates a pre-workspace bare session transparently on load.
- session-types: replaceActiveSession(window, session) helper.
- tauri-adapter + browser-sidecar-adapter: getState/saveState route through the
  helpers. No change to shared lib persistence (no session-save.ts touch).

The Window container is standalone-only; VS Code keeps one bare PersistedSession
per webview, untouched.

Tests: +8 (window-persistence, both flag states + multi-Workspace merge/load).
Full lib suite green (718); standalone tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g/VS Code = 3/4

Now that the Workspace/Window container, migration, model, union projection, and
standalone persistence wiring are in place behind `dormouse.flags.workspaces`,
update the spec status markers to match reality:

- glossary Implementation status: three buckets — live (2a), implemented-but-
  dormant-behind-flag (2b container/migration/model/union/persistence), and not
  yet built (strip + union surfacing = 3, Wall mount/unmount on switch + >1
  Workspace = 4)
- transport: container/migration paragraphs flip to implemented; document that
  the wrapping lives at the standalone adapter boundary (window-persistence.ts),
  flag off = byte-identical bare PersistedSession
- layout: Workspaces section is partially implemented (model behind flag; strip
  + switch-rerender still 3/4)
- alert: union projection implemented (computeWorkspaceUnion); surfacing still 3
- vscode: union primitive exists but VS Code does not yet feed/reflect it; the
  Window container is standalone-only and doesn't touch VS Code

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…guards/id

Cleanup pass over the stage-2b diff (4 review angles). Quality only, no
behavior change.

- window-persistence: add storage-level loadSessionState/saveSessionState that
  own the JSON parse/stringify + localStorage access, so both standalone
  adapters collapse to a one-liner instead of re-implementing the identical
  read-merge-write dance. saveSessionState skips reading the existing blob when
  the flag is off (the default) — previously every debounced save needlessly
  re-parsed the scrollback-bearing snapshot on the hot path.
- session-types: isPersistedWindowShape is now a structural gate only; per-
  Workspace validation (id/name/readable session) moved into readPersistedWindow
  so malformed elements are dropped uniformly instead of a single bad element
  rejecting the whole Window. Deletes the now-redundant isPersistedWorkspaceShape.
- workspace-store: generateWorkspaceId uses the random suffix only (drops the
  belt-and-suspenders monotonic counter); the suffix never equals "workspace-1".

Skipped (intentional, noted): the hand-rolled subscribe/snapshot store and
localStorage-guard patterns (codebase convention, no shared helper exists), the
flags-module reader and dormant createWorkspace option, and the multi-validation
cost at N>1 (proportionate for dormant N=1).

Tests: +5 (storage round trip incl. flag-off no-read). Full lib suite green
(729); lib + standalone tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying mouseterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: 9d28cc8
Status: ✅  Deploy successful!
Preview URL: https://21bc69f0.mouseterm.pages.dev
Branch Preview URL: https://workspaces-2b.mouseterm.pages.dev

View logs

@nedtwigg nedtwigg merged commit 50bbbce into main Jun 30, 2026
9 checks passed
@nedtwigg nedtwigg deleted the workspaces-2b branch June 30, 2026 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants