@lace-cloud/canvas
v0.17.0
Published
Lace Cloud's visual canvas — the React + ReactFlow webview UI for visually composing Terraform modules. The core renders against `CanvasPorts` (`source`, `context`, `viewState`, and optional `editor`, `commands`, `navigator`, `presenter`); a host wires on
Readme
@lace-cloud/canvas
Lace Cloud's visual canvas — the React + ReactFlow webview UI for
visually composing Terraform modules. The core renders against
CanvasPorts (source, context, viewState, and optional editor,
commands, navigator, presenter); a host wires only the ports it
can support, and read-only is the absence of editor (or a live
session whose source tf is refused), never a flag.
Install
npm install @lace-cloud/canvas @lace-cloud/design-tokens react react-dom @xyflow/reactPorts
packages/canvas/src/ports.ts is the one contract every host — VS
Code, JetBrains, Storybook, the portal — renders against:
source(required) — subscribes to the liveCanvasSource: the IR Bundle, undo/redo flags,unsaved/ungenerated/conflict/sourceRefused, and the engine's own diagnostics. The canvas runs no validator of its own.editor—apply(every IR edit, registry placement included),save(working IR →state.lace),generate(state.lace→ source tf),resolveConflict(Keep or Reload), and an optionalautosavedelay for a host that offers the preference. Its absence is read-only.commands— runs one CLI command in-process (invoke(path, args, signal)); every canvas feature that isn't an IR edit is a contribution bound to a command path incommand-tree.golden.navigator/presenter— open a run/stack/change/module, and notify/confirm/showCode, respectively.context/viewState— session identity and consumer-owned arrangement (node positions, group collapse/label) — never transported over the wire.
The engine adapter
src/adapters/engine/ turns one engine connection into
source/editor/commands plus context. Single barrel entry — no
deep imports:
import { connectCaller, openEngineCanvas } from '@lace-cloud/canvas';
const caller = connectCaller({ baseUrl: 'http://localhost:50051', token });
const { ports, close } = await openEngineCanvas(caller, { root: '/path/to/authoring/root' });connectCaller({baseUrl, token})reaches a runninglace engineprocess directly over Connect-JSON (HTTP POST, fetch) — Storybook flow stories and the canvas-flow Playwright suite use this.bridgeCaller(bridge)forwards every call over aMessageBridge(@lace-cloud/proto) to a host process holding the engine connection instead — VS Code and JetBrains webviews. The other end is@lace-cloud/host'sserveEngineBridge.openEngineCanvas(caller, target)opens the session ({root}|{ephemeral:{name}}|{fixture}) and subscribes exactly once, fanning that stream out tosourceand to whichevereditor.generate()call is in flight.commands.invokeruns a CLI command overInvoke;Invocation.cancel()sendsCancelInvokeonly and leaveseventsrunning until the command's own exit — asignalabort orclose()detaches the stream instead, without cancelling the command's remote effects.
snapshotSource (src/adapters/snapshot-source.ts) is the read-only
source a host without a live engine mounts (portal's control-plane
dashboards): it fetches one Bundle snapshot and never changes —
unsaved/ungenerated/sourceRefused are always false, conflict is
always null, diagnostics is always empty, since a RunSnapshot has no live
validator and the run's own certification already vouched for it.
import { snapshotSource } from '@lace-cloud/canvas';
const source = snapshotSource({ bundleUrl, headers });Save and generate
A root session's canvas renders from the root's state.lace
(.lace/.canvas/state.lace), the persistent bridge between its source
tf and the canvas. An edit changes the working IR only ("● Unsaved");
Save writes it to state.lace ("Not generated"); Generate writes
state.lace's IR to the tf in the repo. A hand edit to the tf updates
the canvas live, or — with edits the tf lacks — opens the conflict
banner (Keep / Reload). lace stack apply and preview run the tf on
disk, never the canvas.
Read-only mode
A canvas is read-only when its host supplies no editor port, or
while its session's source tf is refused — there is no readOnly
flag. See lace's
CLAUDE.md
for the full contract.
License
BUSL-1.1 — see the LICENSE file packed in this package.
