feat(web): the malleable workbench — layout tree, movable slots, workspaces as nodes (0280) - #409
Merged
crs48 merged 11 commits intoJul 7, 2026
Merged
Conversation
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>
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 }) { |
| } | ||
|
|
||
| /** 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>
Contributor
🖼️ UI changes in this PRScreensAuto-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
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



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, shareablexnet:Workspacenodes; and the companion edits the workspace through the same undoable commands the UI runs.What landed
LayoutTreeprimitives (now in@xnetjs/pluginsworkspace/), pure move/tier ops, payload codec,useWorkbenchintegration with v1→v3 persist migration, and the flag-gatedShellFramerenderer (xnet:experiment:layout-tree; legacy shells remain the default). A source-grep tripwire forbids components branching on preset identity.SlotContributiongeneralizes 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, andView: Move/Open/Hide/Showpalette commands.xnet:Workspaceschema (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./pinand/workspaceslash verbs run the shared commands.workspace_*agent tools (describe/apply preset/move/open/save/scaffold/undo) that emit registered commands with an undo stack + agent-change toast;slot-viewscaffold template (network-closed by default,ai-generatedconsent preview); marketplaceworkspacelisting kind; Electron shares the primitives behind a parity guard (full ShellFrame port explicitly deferred per risk 6).Three-roads audit (review gate)
View: Move <label> to <dock>View: Open <label>, ⌘B/⌘\/⌘JView: Hide/Show <label>Workspace: Switch…,Workspace: Preset: …Workspace: Save as…,/workspaceslashWorkspace: Undo layout changeValidation
when-filtered targets, Esc closing docks rung by rung.routeForTabcrash 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