Skip to content

feat(web): the malleable workbench — layout tree, movable slots, workspaces as nodes (0280) - #409

Merged
crs48 merged 11 commits into
mainfrom
claude/0280-malleable-workbench-composable-workspace
Jul 7, 2026
Merged

feat(web): the malleable workbench — layout tree, movable slots, workspaces as nodes (0280)#409
crs48 merged 11 commits into
mainfrom
claude/0280-malleable-workbench-composable-workspace

Conversation

@crs48

@crs48 crs48 commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Implements exploration 0280_[x]_MALLEABLE_WORKBENCH_COMPOSABLE_WORKSPACE.md — the shell as data. One layout tree (regions → slots → views) replaces the calm/workbench component fork; the former shells become data-only presets; layouts are saveable, syncable, shareable xnet:Workspace nodes; and the companion edits the workspace through the same undoable commands the UI runs.

What landed

  • Phase 1 — layout tree engine: LayoutTree primitives (now in @xnetjs/plugins workspace/), pure move/tier ops, payload codec, useWorkbench integration with v1→v3 persist migration, and the flag-gated ShellFrame renderer (xnet:experiment:layout-tree; legacy shells remain the default). A source-grep tripwire forbids components branching on preset identity.
  • Phase 2 — movable views: SlotContribution generalizes the 0273 SurfaceDock contract (defaultRegion/allowedRegions; deprecated aliases kept — no breaking change). One app-side slot registry replaces the PanelViewHost/dock maps; every panel gains Move menu, drag handle, and View: Move/Open/Hide/Show palette commands.
  • Phase 3 — workspaces as nodes: xnet:Workspace schema (portable tree only; pixel sizes stay device-local keyed by workspace id), Tier-1 preset seeder (round-trip guarded), quick switcher with Save as… / Switch / Share / Reset.
  • Phase 4 — customization slope: Esc walks the pinned frame down one dock per press; tabless-bench starter chips; ModeSwitch workspace button + first-run coachmark; /pin and /workspace slash verbs run the shared commands.
  • Phase 5 — agent + ecosystem: workspace_* agent tools (describe/apply preset/move/open/save/scaffold/undo) that emit registered commands with an undo stack + agent-change toast; slot-view scaffold template (network-closed by default, ai-generated consent preview); marketplace workspace listing kind; Electron shares the primitives behind a parity guard (full ShellFrame port explicitly deferred per risk 6).

Three-roads audit (review gate)

Verb Pointer Touch twin Keyboard / ⌘K
Move a view between docks Header Move menu; drag header → dock drop zone Same Move menu (tap) View: Move <label> to <dock>
Open a view Dock strips / rail buttons / corner launcher FAB + bottom sheet (0273) View: Open <label>, ⌘B/⌘\/⌘J
Hide / show a view Panel close button Same View: Hide/Show <label>
Switch workspace / preset ModeSwitch Layers button Same (tap) Workspace: Switch…, Workspace: Preset: …
Save a layout Switcher "Save current layout as…" Same Workspace: Save as…, /workspace slash
Undo a layout change Agent-change toast Undo Same Workspace: Undo layout change
Walk disclosure down Scrim/close buttons Swipe-away (0273 sheets) Esc (one rung per press)

Validation

  • 33/33 checklist items checked in the doc; new unit/interaction tests: layout-tree ops + payload round-trip, store integration + migrations (incl. foreign-tab hardening), preset tripwire, seeder round-trip, agent tools (undo/announce/capability bounds), scaffold template, marketplace kind filter, Esc ladder walk, slot-move re-render bound, Electron parity guard.
  • Live click-through on the worktree preview (flag on): fresh-identity boot into calm, preset switcher → bench (Rail/tabs/status), Move menu left→corner with corner launcher appearing, reload persistence, palette move navigator→right with when-filtered targets, Esc closing docks rung by rung.
  • The live pass caught and fixed three real defects: foreign-nodeType tabs crashing TabBar, routeForTab crash on unknown types, and the Esc handler dying on window-target events.

Implements and checks off docs/explorations/0280_[x]_MALLEABLE_WORKBENCH_COMPOSABLE_WORKSPACE.md.

🤖 Generated with Claude Code

xNet Test and others added 9 commits July 7, 2026 12:27
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Regions → slots → views as data: createPresetTree(quiet|calm|bench),
pure move/tier ops, workspace-payload round trip, store integration with
v1→v2 persist migration, flag-gated ShellFrame renderer, preset tripwire.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Generalizes the 0273 SurfaceDock contract: SlotRegion plus defaultRegion/
allowedRegions in @xnetjs/plugins (surfaceDock kept as deprecated alias),
one app-side slot registry replacing the PanelViewHost/dock maps, move/
hide/show palette commands per view, Move menu and drag between docks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Esc walks the pinned frame's ladder down one dock at a time; slot views
gain View: Open palette entries (replacing dock-only commands); tabless
bench shows dimmed starter chips; ModeSwitch gains the workspace switcher
button with a first-run save-as coachmark; /pin and /workspace slash
verbs run the same registered commands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ngs (0280)

Companion tools (workspace_describe/apply_preset/move_view/open_view/
save/scaffold/undo) emit the same registered commands the UI runs, with
an undo stack + agent-change toast; slot-view scaffold template defaults
to network:[] with ai-generated consent preview; marketplace gains a
workspace listing kind (shared benches are data, not code); Electron
shares the workspace primitives behind a parity guard (ShellFrame port
deferred per 0280 risk 6).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live click-through caught three defects: persisted tabs from another
branch's build crashed TabBar (migration v3 drops unknown nodeTypes +
icon/route fallbacks), and the Esc ladder died on window-target events
(instanceof guard). Adds the Esc-walk and slot-move re-render-bound
validation tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@crs48
crs48 temporarily deployed to pr-409 July 7, 2026 22:38 — with GitHub Actions Inactive
github-actions Bot added a commit that referenced this pull request Jul 7, 2026
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Preview removed for PR #409.

* device-local and keyed by the workspace id (0280 phase 3) — a saved bench
* carries its placements everywhere, its pixel widths nowhere.
*/
function PinnedFrame({ tree, children }: { tree: LayoutTree; children: ReactNode }) {
Comment thread apps/web/src/workbench/PanelViewHost.tsx Fixed
Comment thread apps/web/src/workbench/PanelViewHost.tsx Fixed
}

/** The dock body: the active placement's view, bare or hosted. */
function DockBody({ tree, region, side }: { tree: LayoutTree; region: RegionId; side: PanelSide }) {
)
}

export function WorkspaceSwitcher(): JSX.Element | null {
value={query}
onValueChange={setQuery}
placeholder={saving ? 'Name this workspace…' : 'Switch workspace…'}
onKeyDown={(event) => {
* layout tree + slot registry (0280) — the same resolution the pinned
* frame's DockBody uses, so a moved view follows the user into quiet.
*/
function OverlayBody({ side }: { side: 'left' | 'right' }) {
…shim

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@crs48
crs48 temporarily deployed to pr-409 July 7, 2026 22:55 — with GitHub Actions Inactive
github-actions Bot added a commit that referenced this pull request Jul 7, 2026
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🖼️ UI changes in this PR

Screens

✏️ Home _(SSIM 0.998)_
before after diff
before after diff

Auto-captured by CI · run. Informational — not a blocking check.

Fallow flagged PanelViewHost → slot-registry → Rail/StatusBar →
contributions cycles; first-party residents move to
builtin-slot-views.tsx so the registry module imports no components.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@crs48
crs48 temporarily deployed to pr-409 July 7, 2026 23:17 — with GitHub Actions Inactive
github-actions Bot added a commit that referenced this pull request Jul 7, 2026
github-actions Bot added a commit that referenced this pull request Jul 7, 2026
@crs48
crs48 merged commit 1c03e5b into main Jul 7, 2026
19 of 22 checks passed
@crs48
crs48 deleted the claude/0280-malleable-workbench-composable-workspace branch July 7, 2026 23:36
github-actions Bot added a commit that referenced this pull request Jul 7, 2026
crs48 added a commit that referenced this pull request Jul 7, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @xnetjs/data@0.6.0

### Minor Changes

- [#409](#409)
[`bd50f40`](bd50f40)
Thanks [@crs48](https://github.com/crs48)! - Workspaces as nodes
(exploration 0280): new `xnet:Workspace` schema in `@xnetjs/data`
(name/preset/system/tree — the portable half of a saved shell layout),
and workspace layout primitives in `@xnetjs/plugins` (`LayoutTree`,
`createPresetTree`, `moveSlot`/`setSlotTier`,
`parseWorkspacePayload`/`serializeWorkspacePayload`) shared by the web
shell, the seed, and future desktop adoption.

### Patch Changes

- Updated dependencies []:
  - @xnetjs/storage@0.6.0
  - @xnetjs/sqlite@0.6.0
  - @xnetjs/sync@0.6.0
  - @xnetjs/identity@0.6.0
  - @xnetjs/crypto@0.6.0
  - @xnetjs/core@0.6.0
## @xnetjs/plugins@0.6.0

### Minor Changes

- [#409](#409)
[`6795f6b`](6795f6b)
Thanks [@crs48](https://github.com/crs48)! - Generalize the SurfaceDock
contract into shell-wide slot contributions (exploration 0280). New
`SlotContribution` type (with `defaultRegion` / `allowedRegions`),
`SlotRegion` union, a `slots` registry on `ContributionRegistry`, a
`slots` key on `PluginContributions`, and
`ExtensionContext.registerSlotView()`. `SurfaceDockContribution` and the
`surfaceDock` registry remain as deprecated aliases — no breaking
changes.

- [#409](#409)
[`bd50f40`](bd50f40)
Thanks [@crs48](https://github.com/crs48)! - Workspaces as nodes
(exploration 0280): new `xnet:Workspace` schema in `@xnetjs/data`
(name/preset/system/tree — the portable half of a saved shell layout),
and workspace layout primitives in `@xnetjs/plugins` (`LayoutTree`,
`createPresetTree`, `moveSlot`/`setSlotTier`,
`parseWorkspacePayload`/`serializeWorkspacePayload`) shared by the web
shell, the seed, and future desktop adoption.

### Patch Changes

- Updated dependencies
[[`bd50f40`](bd50f40)]:
  - @xnetjs/data@0.6.0
  - @xnetjs/abuse@0.6.0
  - @xnetjs/core@0.6.0
## @xnetjs/abuse@0.6.0

### Patch Changes

- Updated dependencies []:
  - @xnetjs/identity@0.6.0
  - @xnetjs/crypto@0.6.0
## @xnetjs/cli@0.0.11

### Patch Changes

- Updated dependencies
[[`6795f6b`](6795f6b),
[`bd50f40`](bd50f40)]:
  - @xnetjs/plugins@0.6.0
  - @xnetjs/data@0.6.0
  - @xnetjs/runtime@0.1.7
  - @xnetjs/sqlite@0.6.0
  - @xnetjs/sync@0.6.0
  - @xnetjs/identity@0.6.0
  - @xnetjs/crypto@0.6.0
  - @xnetjs/core@0.6.0
## @xnetjs/crypto@0.6.0

### Patch Changes

- Updated dependencies []:
  - @xnetjs/core@0.6.0
## @xnetjs/data-bridge@0.6.0

### Patch Changes

- Updated dependencies
[[`bd50f40`](bd50f40)]:
  - @xnetjs/data@0.6.0
  - @xnetjs/sqlite@0.6.0
  - @xnetjs/sync@0.6.0
  - @xnetjs/core@0.6.0
## @xnetjs/history@0.6.0

### Patch Changes

- Updated dependencies
[[`bd50f40`](bd50f40)]:
  - @xnetjs/data@0.6.0
  - @xnetjs/sync@0.6.0
  - @xnetjs/core@0.6.0
## @xnetjs/identity@0.6.0

### Patch Changes

- Updated dependencies []:
  - @xnetjs/crypto@0.6.0
  - @xnetjs/core@0.6.0
## @xnetjs/react@0.6.0

### Patch Changes

- Updated dependencies
[[`6795f6b`](6795f6b),
[`bd50f40`](bd50f40)]:
  - @xnetjs/plugins@0.6.0
  - @xnetjs/data@0.6.0
  - @xnetjs/runtime@0.1.7
  - @xnetjs/data-bridge@0.6.0
  - @xnetjs/history@0.6.0
  - @xnetjs/sync@0.6.0
  - @xnetjs/identity@0.6.0
  - @xnetjs/crypto@0.6.0
  - @xnetjs/core@0.6.0
## @xnetjs/runtime@0.1.7

### Patch Changes

- Updated dependencies
[[`6795f6b`](6795f6b),
[`bd50f40`](bd50f40)]:
  - @xnetjs/plugins@0.6.0
  - @xnetjs/data@0.6.0
  - @xnetjs/data-bridge@0.6.0
  - @xnetjs/history@0.6.0
  - @xnetjs/storage@0.6.0
  - @xnetjs/sync@0.6.0
  - @xnetjs/identity@0.6.0
  - @xnetjs/crypto@0.6.0
  - @xnetjs/core@0.6.0
## @xnetjs/storage@0.6.0

### Patch Changes

- Updated dependencies []:
  - @xnetjs/sqlite@0.6.0
  - @xnetjs/crypto@0.6.0
  - @xnetjs/core@0.6.0
## @xnetjs/sync@0.6.0

### Patch Changes

- Updated dependencies []:
  - @xnetjs/identity@0.6.0
  - @xnetjs/crypto@0.6.0
  - @xnetjs/core@0.6.0
## @xnetjs/core@0.6.0


## @xnetjs/sqlite@0.6.0


## xnet-cloud@0.0.9

### Patch Changes

- Updated dependencies []:
  - @xnetjs/crypto@0.6.0
  - @xnetjs/cloud@0.0.1
## @xnetjs/brain@0.0.10

### Patch Changes

- Updated dependencies
[[`bd50f40`](bd50f40)]:
  - @xnetjs/data@0.6.0
  - @xnetjs/vectors@0.0.1
## @xnetjs/comms@0.0.10

### Patch Changes

- Updated dependencies
[[`bd50f40`](bd50f40)]:
  - @xnetjs/data@0.6.0
  - @xnetjs/crypto@0.6.0
## @xnetjs/dashboard@0.0.10

### Patch Changes

- Updated dependencies
[[`6795f6b`](6795f6b),
[`bd50f40`](bd50f40)]:
  - @xnetjs/plugins@0.6.0
  - @xnetjs/data@0.6.0
  - @xnetjs/react@0.6.0
  - @xnetjs/social@0.0.10
## @xnetjs/labs@0.0.10

### Patch Changes

- Updated dependencies
[[`6795f6b`](6795f6b),
[`bd50f40`](bd50f40)]:
  - @xnetjs/plugins@0.6.0
  - @xnetjs/data@0.6.0
## @xnetjs/licenses@0.0.10

### Patch Changes

- Updated dependencies []:
  - @xnetjs/crypto@0.6.0
## @xnetjs/maps@0.0.10

### Patch Changes

- Updated dependencies
[[`bd50f40`](bd50f40)]:
  - @xnetjs/data@0.6.0
## @xnetjs/meetings@0.0.3

### Patch Changes

- Updated dependencies
[[`6795f6b`](6795f6b),
[`bd50f40`](bd50f40)]:
  - @xnetjs/plugins@0.6.0
  - @xnetjs/data@0.6.0
## @xnetjs/server@0.0.9

### Patch Changes

- Updated dependencies
[[`bd50f40`](bd50f40)]:
  - @xnetjs/data@0.6.0
  - @xnetjs/data-bridge@0.6.0
  - @xnetjs/identity@0.6.0
  - @xnetjs/crypto@0.6.0
## @xnetjs/social@0.0.10

### Patch Changes

- Updated dependencies
[[`bd50f40`](bd50f40)]:
  - @xnetjs/data@0.6.0
  - @xnetjs/crypto@0.6.0
## @xnetjs/unreal@0.0.10

### Patch Changes

- Updated dependencies
[[`bd50f40`](bd50f40)]:
  - @xnetjs/data@0.6.0
## xnet-desktop@0.6.0

Desktop shell release riding the @xnetjs/core 0.6.0 train.
Desktop-specific changes are not tracked here; see the core packages'
changelogs for what shipped.
crs48 added a commit that referenced this pull request Jul 8, 2026
)

Follow-up to #409: the workspace switcher verb (`workspace.switch`) was
only reachable from the calm shell's ModeSwitch. Now every shell posture
has the pointer road:

- **Workbench (bench)** — a Workspaces button on the Rail, above the
identity/settings footer.
- **Quiet (minimal)** — a Workspaces glyph in the top-left corner
cluster, between Search and Settings.
- **Calm** — unchanged (already had it).

And **Settings → Appearance** reflects the 0280 defaults: the Layout row
now picks between the three presets (Quiet / Calm / Bench via
`applyPreset`, active state derived from the tree's provenance), plus a
Workspaces row with a shortcut into the switcher.

Verified live in the worktree preview (flag on): the button opens the
switcher in all three postures, and the settings preset row applies and
highlights correctly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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