@tutti-os/workbench-surface
v0.0.254
Published
Shared React workbench surface primitives for rendering neutral workbench nodes.
Keywords
Readme
@tutti-os/workbench-surface
Shared React workbench surface primitives for rendering neutral workbench nodes.
Consumers can import the workbench surface stylesheet once from the application shell. It pulls in the shared UI-system defaults first, then layers the workbench structural styles on top:
import "@tutti-os/workbench-surface/styles.css";Hosts that already import @tutti-os/ui-system/styles.css elsewhere may keep
doing so, then import @tutti-os/workbench-surface/styles.css for the
structural layer.
The package owns workbench surface behavior, frame-level rendering contracts, and shell snapshot persistence mechanics. Applications still own product state, node bodies, business persistence, and persistence adapters, while window-chrome i18n now flows through the shared app-level i18n runtime instead of injected label objects.
Host State And Lifecycle Boundaries
WorkbenchHost owns shell mechanics:
- projected node reconciliation
- launch request dispatch
- opening, restoring, and focusing windows
- dock rendering and default window actions
- snapshot persistence for shell state
The consuming host owns product behavior:
- business instance creation and reuse policy
- external node and workspace business state
- business-state commits and reducers
- close policy for host-owned instances
- external store subscriptions
When a node body or header needs host-owned business state, pass an
externalStateSource. The source is read into render context as
externalNodeState and externalWorkspaceState. If the source exposes
subscribe(...), the host re-renders when that subscription notifies.
Node body and header contexts expose isDragging and isResizing. The Workbench
shell continues applying live frame geometry during direct manipulation. Window
position is shell-owned: changing only frame.x or frame.y does not render a
body during drag or resize. Width and height changes remain live, as do changes
to node data, external state, focus, visibility, or interaction state. The body
always renders after the interaction settles so the final committed frame
reaches responsive layout. Body actions that need execution-time window
position should read the current node from context.host.getSnapshot() instead
of closing over a rendered position.
Translated window boundaries keep using their window rect for menu clamping,
but viewport-positioned menus portal to document.body so fixed coordinates
are not offset a second time by the window translation.
Headers keep live frame renders by default. A definition may provide
getHeaderFrameRenderKey(context) when several intermediate frames produce the
same visible header layout. During drag or resize, equal primitive keys skip
renderHeader; changes to node data, external state, focus, or interaction
state still render. Call context.windowActions.getFrame() inside actions that
need execution-time geometry instead of closing over a frame omitted from the
key.
Host-owned business instances are represented with projectedNodes. A
projected node tells the workbench that a shell should currently exist for a
host-owned instance; the workbench reconciles that presence with snapshot
layout and live shell state.
Dock entries are configured at the host level through dockEntries, separate
from node definitions. Dock entry identity drives slot ordering, grouping,
anchor lookup, popup aggregation, badge state, and launch payload dispatch.
Entries may also provide host-owned popup metadata through
resolvePopupItem(...) and capturePopupItemPreview(...), plus custom badge
content when the default count or status shapes are not enough.
Component-based dock previews should render their host-owned body through
WorkbenchDockComponentPreviewFrame. The frame owns source-to-viewport
scaling, clipping, centering, and the non-interactive preview boundary. Preview
children are decorative and never participate in pointer hit testing; the Dock
button remains the only interaction owner even if frozen preview markup is
replaced between pointer events. Popup preview providers receive the canonical
target size through item.previewViewport; use that value instead of duplicating
Dock card dimensions in a consuming host.
Presentation-only changes to merged Dock entries belong in
dockEntryPresentationOverrides. WorkbenchHost applies these overrides by
entry id after contributions and explicit entries are merged, preserving Dock
order without changing contribution nodes or rebuilding the host session. Use
this seam for host-owned visibility and retention presentation; keep product
preference persistence in the consuming host.
Dock, shortcut, and command opens flow through launchNode(...) and the
optional onLaunchRequest(...) callback. The host may create a business
instance asynchronously, then return the shell identity, title, default frame,
required framePolicy ("cascade" to offset from the active node or
"absolute" to keep host coordinates), stable dockEntryId, and optional
one-shot activation payload. Activations and launch payloads are transient
render signals and are not persisted.
Runtime external business state is not written to the Workbench snapshot. Hosts
that need durable node-specific state should expose it through
getSnapshotNodeState(...); Workbench stores that value as snapshotNodeState.
Node bodies that need live-only coordination can write runtimeNodeState through
setNodeRuntimeState(...); refresh-safe state uses setSnapshotNodeState(...).
The snapshot also stores shell state such as nodes, frames, stack order, and
host node metadata. Snapshots are sanitized before save so transient render data
such as activation payloads, runtime status, or live subscriptions do not become
layout truth.
Hosts that need to address shells from outside a render context can use
createWorkbenchHostProjectedNodeId(...) for projected presence and
createWorkbenchHostLaunchedNodeId(...) for launch-created shells. These
helpers only derive stable shell ids; layout restore and stack reconciliation
remain owned by WorkbenchHost.
Contributions
Hosts may keep using the explicit WorkbenchHost props directly. For larger
hosts, WorkbenchHost also accepts optional contributions that can provide
node definitions, dock entries, external state, launch handlers, and node-close
handlers. Contributions may also provide prepareHostClose(...) when a module
needs to finish or cancel host-wide close before the consuming app closes its
workbench shell.
Contributions are additive. Explicit top-level props remain supported and keep override authority for compatibility:
- explicit
nodesoverride contribution nodes with the sametypeId - explicit
dockEntriesoverride contribution dock entries with the sameid - explicit
externalStateSourcefully replaces contribution external-state sources - explicit launch and node-close handlers run before contribution handlers
Top-level shell inputs such as snapshotRepository, workspaceId,
missionControl, layout constraints, wallpaper, and chrome renderers remain
owned by the consuming host rather than contributions. The consuming host also
decides when to invoke host-close preparation; workbench-surface only owns the
contribution contract and deterministic composition helper.
For hosts that already use contributions, new workbench modules should prefer adding a contribution instead of adding more feature-specific node, dock, external-state, or launch wiring to a centralized host assembly service.
Layout Semantics
packages/workbench/surface owns frame geometry for floating, snapped, and
fullscreen nodes.
Layout rules:
- floating nodes respect the full configured safe area
- top-snapped nodes use the normal safe layout and preserve bottom safe area
- fullscreen nodes respect top, left, and right safe area, but ignore bottom safe area so immersive content can reach the bottom edge of the workbench surface
- persisted snapshots record the surface and layout constraints that produced their frames; initial restore maps node frames, fullscreen restore frames, space frames, and reusable closed-window frames into the current safe layout
- legacy snapshots without a layout basis remain readable; fullscreen geometry is recomputed and hidden restore frames are clamped before they become floating windows
The package ships a baseline host layout default so new integrations do not start with windows tucked under shared chrome. Today that default uses:
minWidth: 280minHeight: 160safeArea.top: 52safeArea.bottom: 88
Hosts should override layoutConstraints when their shell chrome differs. They
should not duplicate fullscreen bottom-safe-area correction outside this
package.
Host Reuse Pattern
When another host wants to reuse this package:
- Keep workbench controller state, shared window mechanics, and default workbench host plus chrome i18n resources in this package.
- Keep host-owned node bodies, business state, reducers, commits, persistence, routing, and product-specific workbench wording in the consuming host.
- Create one host-level i18n runtime that merges:
- host-owned workbench i18n resources
workbenchHostI18nResourcesworkbenchWindowChromeI18nResources- any other shared package i18n resources used by the same shell
- Let the host surface and window chrome scope that runtime into the
workbench namespaces with
createWorkbenchHostI18nRuntime(...)andcreateWorkbenchWindowChromeI18nRuntime(...). - Use
externalStateSourcefor read-only render context injection andprojectedNodesfor host-owned presence such as terminal sessions, agent sessions, or browser records. - Provide explicit
dockEntriesfor host chrome navigation instead of relying on node type order or node-definition dock metadata. - Use
onLaunchRequestfor dock, shortcut, and command opens that require host-owned instance creation before a shell appears. - Use
onNodeCloseRequestwhen a close button needs host policy instead of a direct shell removal.
This keeps generic workbench mechanics reusable while leaving product behavior and host integration in the owning app or service.
