9.9 KiB
Workbench
This document defines durable Workbench ownership rules across shared packages,
the desktop renderer host, and tuttid snapshot persistence.
Purpose
Workbench is a reusable shell for workspace tools. It owns window-like interaction mechanics, layout, dock identity, node shell lifecycle, and snapshot sanitation.
It must not become a product workflow core.
Ownership
Current ownership:
packages/workbench/snapshotowns the canonical TypeScript snapshot contract, migrations, normalization, validation, and JSON Schema.packages/workbench/serviceowns shared Go snapshot validation, canonicalization, and the storage-facing service contract.packages/workbench/electronowns product-neutral Electron main-process Dock preview capture and bounded filesystem cache mechanics.packages/workbench/surfaceowns reusable Workbench mechanics: controller commands, host/session reconciliation, placement, stacking, dock rendering, window chrome, render context plumbing, and shell snapshot sanitation.apps/desktop/src/renderer/src/features/workspace-workbenchowns the Tutti desktop host adapter: product contributions, tuttid clients, preload adapters, workspace context, close policy, launch policy, and desktop-owned Workbench copy.services/tuttid/service/workspaceowns daemon-side Workbench snapshot load, save, and host-specific snapshot reconciliation before a snapshot is returned to desktop.
Shared Package Rules
packages/workbench/* should stay product-neutral.
Rules:
- keep product-specific node bodies, daemon clients, preload calls, host path selection, unrestricted filesystem access, and workflow policy out of shared Workbench packages
- let
@tutti-os/workbench-electronaccept a host-selected cache directory and ElectronwebContents; IPC authorization, BrowserWindow ownership, product logging, andapp.getPath("userData")stay in the consuming desktop app - keep product-specific CSS selectors and host globals out of
@tutti-os/workbench-surface - restore a minimized node from its in-memory Genie texture before launching the mounted node when the cached texture and Dock anchor are usable; allow that first texture frame to paint before host launch work begins
- attempt the host-provided native Dock preview capture before cloning live DOM; clone DOM only after native capture fails or exceeds the bounded wait, and persist a late result's Dock image immediately but defer its full-size decode until the active Genie animation settles and the browser is idle
- for Electron hosts, derive the full-size Genie image and bounded Dock image from one region capture; never upscale the Dock image into a Genie texture
- bound retained Genie Canvas textures by both entry count and estimated RGBA bytes; retain restored-node textures for later minimizes, and remove them when their nodes no longer exist
- publish Genie-hidden state through per-node subscriptions so changing one window does not invalidate every mounted Workbench window
- use per-node operation tokens for Genie hiding; global animation generation may control the shared Canvas but must not block another node from becoming visible
- project normal-window visibility through the mounted body context; it is false while minimized, Genie-hidden, or in Mission Control, so heavy child presentation can wait without importing Workbench animation state
- project Mission Control preview visibility separately from normal-window visibility. A preview that Workbench paints must keep its body rendering even when normal-window visibility is false; a preview filtered out of the presentation must remain paused
- separate a node's own exposure from whether it may occlude lower nodes. Scale restore, shell frame transitions, and onboarding entry keep their own body visible but do not cover lower bodies until the matching DOM animation or transition settles; overlapping transitions release independently
- order geometric occlusion by rendered surface layer before normal window
stack order. A
dialog-popovernode remains above default-layer nodes even when the normal focus stack changes - keep window chrome hit zones unambiguous; floating-window resize handles
should render outside the clipped window surface so corner handles remain
reachable and take precedence over header drag regions; interactive controls
inside or near draggable chrome must opt out with the product no-drag classes
and
-webkit-app-region: no-drag, but noninteractive wrappers should not shrink the draggable hot zone - inject debug diagnostics, product callbacks, and host-specific behavior through explicit props or adapters
- keep
WorkbenchHostHandlenarrow; product consumers should call public host commands instead of reaching into the raw controller - expose raw controller access only through an internal runtime handle used by Workbench surface internals
- keep package root exports intentionally small; exporting a symbol makes it a stable interface, not just a convenient file shortcut
Shared packages may own narrow default copy for generic Workbench mechanics, such as chrome and dock labels. Product-owned copy stays in the consuming host i18n layer.
Snapshot State Rules
Workbench snapshots persist shell-owned presentation state plus explicit host-owned node state.
Rules:
snapshotNodeStateis the only persisted node extension field for host-owned node stateexternalNodeStateis runtime render input, not a persisted compatibility field- node state sources should expose snapshot state through
getSnapshotNodeState(...) - snapshot sanitizers and session serialization must strip transient runtime render data before persistence
- snapshot serialization must persist
layoutBasisfrom the same Workbench state as the frames. On initial restore, mapframe,restoreFrame, space frames, and reusable closed-window frames from that basis into the current safe layout before exposing a restored floating frame - keep
layoutBasisadditive within schema version 1. Snapshots that predate it remain valid and use conservative bounds normalization; do not invent a historical surface size because that would turn unknown geometry into a misleading proportional migration - persist
lockedLayoutwith the same snapshot as its node frames. On restore, retain it only when at least two locked nodes are restored, so Mission Control keeps its proportional grid and any user-adjusted divider geometry after a Workspace reopen - canonicalize locked frame coordinates to three decimal places, then clamp
width and height to the normalized right and bottom edges. The daemon may
accept only bounded
float32transport noise before storing this canonical form - adapter-specific durable state should remain behind generic contract fields unless the adapter detail is part of the shared snapshot contract
- desktop-owned workspace Dock retention is product metadata in the workspace
Workbench snapshot. Preserve explicit
falsevalues as user choices and merge them through the repository's product-owner path so ordinary host saves cannot overwrite them - treat retention as a Dock presentation override. Product hosts must pass it
through
dockEntryPresentationOverrides; they must not rewrite contribution Dock entries or let presentation state flow into contribution nodes/runtime configuration. WorkbenchHost applies overrides in place after Dock entries are merged so presentation changes preserve entry order and host sessions
Breaking migration rule:
- when the snapshot contract intentionally removes a compatibility field, do not keep silent fallback code in the renderer or daemon; make the break explicit and update tests around the new durable contract
Desktop Host Rules
The desktop workspace Workbench is a host adapter, not a React page that owns Workbench workflows.
Rules:
- keep product contribution factories and host adapter assembly in
workspace-workbench/services/internal/** - expose public service/controller seams from
workspace-workbench/services/*when UI needs model data or commands - keep
workspace-workbench/ui/**shallow: render snapshots, subscribe through hooks, and forward DOM events to services or controllers - do not import
workspace-workbench/services/internal/**fromworkspace-workbench/ui/** - keep daemon reads, writes, save requests, object URL lifecycle, and similar side effects in feature services or controllers, not node body React files
- keep launchpad option projection, provider catalog shaping, and stable Workbench node id helpers behind public desktop service seams
The mechanical guard for the Workbench UI-to-internal rule is:
pnpm check:renderer-boundaries
Daemon Reconciliation Rules
services/tuttid/service/workspace may reconcile Workbench snapshots with
daemon-owned runtime state before returning them to desktop.
Rules:
- keep the core
WorkbenchServicefocused on load, save, validation, canonicalization, and calling the configured reconciler - put node-kind-specific cleanup behind a
WorkbenchSnapshotReconcilerimplementation - wire host-specific reconcilers explicitly in
services/tuttid/wiring.go - do not hide a fallback to a concrete node-kind service inside
WorkbenchService
This keeps the service interface small while preserving locality for daemon runtime cleanup such as terminal session reconciliation.
Review Questions
When reviewing Workbench changes, ask:
- Is this generic Workbench mechanics, or product workflow policy?
- Is durable state going through
snapshotNodeState, or is runtimeexternalNodeStateleaking into persistence? - Does UI depend on a public service/controller seam instead of
services/internal/**? - Does a product consumer need a narrow host command, or is it trying to grab the raw controller?
- Is daemon reconciliation behind an explicit reconciler, or is node-specific logic being added to the core Workbench service?