Files
agent-radar 2ed3415fb7
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
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

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?