Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
206 lines
9.9 KiB
Markdown
206 lines
9.9 KiB
Markdown
# 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/snapshot` owns the canonical TypeScript snapshot contract,
|
|
migrations, normalization, validation, and JSON Schema.
|
|
- `packages/workbench/service` owns shared Go snapshot validation,
|
|
canonicalization, and the storage-facing service contract.
|
|
- `packages/workbench/electron` owns product-neutral Electron main-process Dock
|
|
preview capture and bounded filesystem cache mechanics.
|
|
- `packages/workbench/surface` owns 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-workbench` owns 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/workspace` owns 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-electron` accept a host-selected cache directory and
|
|
Electron `webContents`; IPC authorization, BrowserWindow ownership, product
|
|
logging, and `app.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-popover` node 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 `WorkbenchHostHandle` narrow; 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:
|
|
|
|
- `snapshotNodeState` is the only persisted node extension field for host-owned
|
|
node state
|
|
- `externalNodeState` is 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 `layoutBasis` from the same Workbench
|
|
state as the frames. On initial restore, map `frame`, `restoreFrame`, space
|
|
frames, and reusable closed-window frames from that basis into the current
|
|
safe layout before exposing a restored floating frame
|
|
- keep `layoutBasis` additive 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 `lockedLayout` with 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 `float32` transport 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 `false` values 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/**` from
|
|
`workspace-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:
|
|
|
|
```sh
|
|
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 `WorkbenchService` focused on load, save, validation,
|
|
canonicalization, and calling the configured reconciler
|
|
- put node-kind-specific cleanup behind a `WorkbenchSnapshotReconciler`
|
|
implementation
|
|
- 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:
|
|
|
|
1. Is this generic Workbench mechanics, or product workflow policy?
|
|
2. Is durable state going through `snapshotNodeState`, or is runtime
|
|
`externalNodeState` leaking into persistence?
|
|
3. Does UI depend on a public service/controller seam instead of
|
|
`services/internal/**`?
|
|
4. Does a product consumer need a narrow host command, or is it trying to grab
|
|
the raw controller?
|
|
5. Is daemon reconciliation behind an explicit reconciler, or is node-specific
|
|
logic being added to the core Workbench service?
|