weasel API - v0.8.0
    Preparing search index...

    Type Alias SceneCanvasProps<TData, TLayer, TPose>

    SceneCanvasProps: Omit<
        CanvasProps<SceneNode<TData, TLayer, TPose>, TPose>,

            | "adapter"
            | "moveOptions"
            | "resizeOptions"
            | "rotateOptions"
            | "snap"
            | "pickEvery"
            | "boundsOf"
            | "handleHitRadius"
            | "selection"
            | "selectionOptions"
            | "tools"
            | "geometry"
            | "layers"
            | "onBackgroundClick"
            | "getIsVisible",
    > & {
        actionDefaults?: {
            cloneNode?: (
                id: NodeId,
                offset: { dx: number; dy: number },
            ) => { id: NodeId };
            duplicateOffset?: { dx: number; dy: number };
            nudgeShiftStep?: number;
            nudgeStep?: number;
        };
        actions?: ActionsProp;
        alphaFor?: (id: string) => number;
        ambient?: AnyTool[];
        animator?: Animator;
        backgroundFill?: FillStyle;
        children?: ReactNode;
        chromeVisibility?: VisibilityRules;
        cursorCoordsHud?: boolean;
        debug?: { slops?: boolean };
        defaultTools?: readonly BuiltinToolId[];
        describeKind?: (
            node: SceneNode<TData, TLayer, TPose>,
        ) => string | undefined;
        device?: Partial<DeviceProfile>;
        enableGestureDispatcher?: boolean;
        enableKeybindings?: boolean;
        geometry?: {
            boundsOf?: (id: string) => Bounds | null;
            pickEvery?: (worldX: number, worldY: number) => string | string[] | null;
            picking?: "pose" | "shape";
            pickTolerancePx?: number;
        };
        geometryProjection?: GeometryProjection;
        getActiveMode?: () => {
            allowedCapabilities: ReadonlySet<string>;
            id: string;
        };
        getFocused?: () => boolean;
        ingestion?: {
            clipboard?: {
                enabled?: boolean;
                reviver?: (key: string, value: unknown) => unknown;
            };
            handlers?: ContentHandlerEntry[];
            resolveSrc?: (file: File) => Promise<string>;
            svg?: SvgIngestOptions;
        };
        initialActiveTool?: string;
        insertNodeFactories?: Record<string, InsertNodeFactory>;
        insertTool?: {
            create: SceneToAdapterOptions<TData, TLayer, TPose>["commitInsert"];
            layer?: TLayer;
        };
        isPointerInteractive?: (id: string) => boolean;
        layers?: LayersMap<SceneNode<TData, TLayer, TPose>, TPose>;
        layouts?: SceneToAdapterOptions<TData, TLayer, TPose>["layouts"];
        modalityHud?: boolean | { modeId?: string };
        onDoubleClick?: (hit: SceneCanvasHit | null) => void;
        onToolsCreated?: (tools: ToolsApi) => void;
        pickHud?: boolean;
        routing?: readonly NodeRoutingEntry[];
        scene:
            | Scene<TData, TLayer, TPose>
            | SerializedScene<TData, TLayer, TPose>
            | {
                nodes: ReadonlyArray<unknown>;
                systemLayers?: ReadonlyArray<{ id: string }>;
                version: number;
            };
        selection?: SelectionApi;
        selectionMode?: CanvasSelectionMode;
        selectionOptions?: UseSelectionOptions;
        selectTool?: {
            handleHitRadius?: number;
            move?: UseMoveOptions<TPose>;
            pickBest?: (
                worldX: number,
                worldY: number,
                alt: boolean,
                sel: readonly string[],
            ) => string | null;
            resize?: UseResizeOptions<TPose>;
            rotate?: UseRotateOptions<TPose> | false;
            snap?: SnapStrategy<TPose>;
        };
        shaders?: ShaderProgramHandle[];
        toolBundle?: ToolBundle;
        toolOptions?: BuiltinToolOptions;
        tools?: ToolsApi
        | Record<string, AnyTool | true | false>;
        viewport?: {
            animatedZoom?:
                | boolean
                | {
                    duration?: number;
                    easing?: (t: number) => number;
                    resetDuration?: number;
                };
            inertia?: | boolean
            | {
                boundary?: "stop"
                | "bounce"
                | "spring";
                bounds?: PanBounds;
                friction?: number;
                minSpeed?: number;
            };
            pan?: boolean;
            pinchZoom?: boolean
            | { max?: number; min?: number };
            recenter?: () => void;
            zoom?: boolean | ViewportZoomOptions;
        };
    }

    Type Parameters

    • TData
    • TLayer extends string
    • TPose

    Type Declaration

    • OptionalactionDefaults?: {
          cloneNode?: (
              id: NodeId,
              offset: { dx: number; dy: number },
          ) => { id: NodeId };
          duplicateOffset?: { dx: number; dy: number };
          nudgeShiftStep?: number;
          nudgeStep?: number;
      }

      unused after legacy-bridge removal; will be deleted

      • OptionalcloneNode?: (id: NodeId, offset: { dx: number; dy: number }) => { id: NodeId }
      • OptionalduplicateOffset?: { dx: number; dy: number }

        Per-clone offset for the duplicate default. Default {dx:8,dy:8}.

      • OptionalnudgeShiftStep?: number

        Shifted nudge step. Default 10.

      • OptionalnudgeStep?: number

        Base nudge step. Default 1.

    • Optional Experimentalactions?: ActionsProp

      Override / disable / extend the default action set. Resolution rules: see docs/superpowers/specs/2026-05-09-actions-registry-design.md §D. Pass null to disable all defaults.

    • OptionalalphaFor?: (id: string) => number

      Optional per-id alpha multiplier for the scene-render slot. When supplied, each node's draw output is wrapped in a GroupDrawCommand with the returned alpha so the renderer applies the multiplier. Values equal to 1 are a no-op (no wrapper emitted). Typical use: scoping-dim integration dims non-active nodes during a mode transition.

      Defaults to () => 1 (no effect).

    • Optionalambient?: AnyTool[]

      Always-on tools to register alongside the internal default select. Use this for wheel/keyboard zoom + pan tools that should run alongside the default select. If you supply your own tools prop, this is ignored — wire ambient through your own useTools call instead.

    • Optionalanimator?: Animator

      Optional animator to bind for per-frame redraws. When supplied, SceneCanvas subscribes to animator.onTick and requests a redraw on every active animation frame. This is the supported way to drive repaints when an animation's effect is read from a non-scene channel (e.g. a custom drawOne consults animator.colorOverrides) — scene mutations trigger repaints automatically, but colorOverrides writes do not.

      Omit when no animation channel touches the render pipeline; idle frames don't cost anything if no animations are active (the animator's subscriber list stays quiet).

    • OptionalbackgroundFill?: FillStyle

      FillStyle applied to the full canvas behind the scene. Accepts the kit's FillStyle union (solid / pattern / linear-gradient / radial-gradient / conic-gradient) so consumers don't have to author a background node just to colorize the canvas. Rendered as a screen-space layer slotted before 'scene' — independent of pan / zoom.

    • Optionalchildren?: ReactNode

      Children rendered alongside the canvas. Useful for siblings that need the same <ActionsProvider> scope (e.g. shortcuts overlays, probes).

    • OptionalchromeVisibility?: VisibilityRules

      Chrome-caps visibility overrides, keyed by chrome id (selection.outline, selection.rotation-handle, gesture.marquee, …). Each entry is a composable Condition built from the cond() builder. Merged on top of the kit's defaultVisibilityRules; unspecified ids fall through to the defaults.

      Set an id to never to suppress a chrome element entirely (also unhittable). Set to always to force-show. Mix cond(...) chains (e.g. selectionIs(1).and(focused).andNot(gesturing)) for the in- between cases.

    • OptionalcursorCoordsHud?: boolean

      Dev HUD: when true, mounts a fixed-position widget in the top-left of the viewport showing live cursor coords in both viewport (client) and canvas (world) frames. Useful for diagnosing pointer- coord drift / pan-zoom misalignment without instrumenting events.

    • Optionaldebug?: { slops?: boolean }

      Dev overlay flags. slops: true renders translucent halos at every affordance hit zone.

    • OptionaldefaultTools?: readonly BuiltinToolId[]

      Which built-in tools SceneCanvas registers in its internal useTools. Default: ['select', 'rotate'] (plus 'hand' when the viewport feature is on). Pass a smaller array to slim — e.g. ['select'] for move-only. Wins over toolBundle when both are passed. Ignored when the consumer supplies their own tools prop.

    • Optional ExperimentaldescribeKind?: (node: SceneNode<TData, TLayer, TPose>) => string | undefined

      Optional resolver: given a scene node, return a short human-readable "kind" label (e.g. 'rectangle', 'path', 'sticky note'). When supplied, the kit publishes per-id kinds into any surrounding <SelectionContextProvider> so non-canvas UI (palette, status bar) can render type-aware copy. Return undefined to skip an entry.

      Default behavior when omitted: containers report 'group', paths (poses with a kind property) report 'path', everything else is left unlabelled.

    • Optionaldevice?: Partial<DeviceProfile>

      Override detected device facts. Merged over what matchMedia reports; targetScale is re-derived from the merged coarsePointer unless you override it explicitly.

      Reach for this in three cases: tests that need a coarse profile without stubbing matchMedia, demos that want to show touch-sized chrome on a desktop, and hybrid devices where the media query guesses wrong.

    • OptionalenableGestureDispatcher?: boolean

      Auto-mount the gesture dispatcher (useGestureDispatcher) inside <SceneCanvas>. Default true. When false, the dispatcher is not wired — useful in tests or demos that drive actions through alternative mechanisms, or that want to call useGestureDispatcher themselves.

      The dispatcher reads registered actions' defaultBinding fields and routes matching window keydown / canvas pointer / wheel events to the corresponding invoker.run (or invoker.start for ongoing gestures).

    • OptionalenableKeybindings?: boolean

      Auto-wire keyboard shortcuts. Default true. When false, SceneCanvas still mounts its tools but routes no keyboard input to them: it neither subscribes the legacy useKeybindings hook (tool hotkeys) nor lets the gesture dispatcher attach its keydown/keyup listeners (modern keyboard-bound actions like delete / escape / nudge). Pointer, wheel, and contextmenu interactions are unaffected. Leaves the consumer free to call useKeybindings(tools, { ... }) themselves (e.g. with disable, overrides, or defaultTool).

    • Optionalgeometry?: {
          boundsOf?: (id: string) => Bounds | null;
          pickEvery?: (worldX: number, worldY: number) => string | string[] | null;
          picking?: "pose" | "shape";
          pickTolerancePx?: number;
      }
      • OptionalboundsOf?: (id: string) => Bounds | null
      • OptionalpickEvery?: (worldX: number, worldY: number) => string | string[] | null

        Hit-test override. Return the topmost id, the full back-to-front hit stack (string[]), or null for empty space. The stack form lets a consumer with domain overlap ordering (e.g. children over their container) feed the kit's pickTopMostHit the true order instead of pre-collapsing to one id. Matches Canvas's pickEvery shape.

      • Optionalpicking?: "pose" | "shape"

        What "the pointer is on this node" means for the default body-pick.

        • 'pose' — the node's pose rect, rotation honored. What every consumer got before 'shape' existed, and now the opt-out.
        • 'shape' (default) — the pose rect as a pre-filter, then the ink the painter actually lays down: its silhouette (findShapeSilhouette) filled or not per the painter's ink, plus its outline widened by the stroke half-width and pickTolerancePx. A click in the concave notch of a star, in the corner outside an ellipse, or in the blank half of a text box falls through to whatever is beneath; a click on the thin outline of an unfilled shape hits it.

        Painters with no silhouette are unaffected — they keep the pose-rect answer either way, so this can never make a node unreachable.

        Ignored when pickEvery is supplied: that override owns the test.

      • OptionalpickTolerancePx?: number

        Grab slop around a shape's outline, in screen pixels. Default 4.

        Screen pixels rather than world units so the target keeps its apparent size at any zoom. It widens the outline test under picking: 'shape' (a 1px hairline is otherwise a half-world-unit target, which is unhittable), and it grows the pose-rect pre-filter so those outline hits survive it. Set 0 for exact geometry.

    • OptionalgeometryProjection?: GeometryProjection

      Optional consumer seam for eager geometry sync: lets pose-transform actions (move, resize, nudge, flip — NOT rotate) also rewrite a node's data-held geometry. Given a node and the affine m applied to its pose, transform(node, m) returns updated data (geometry mapped by m) or null for nodes with no data-held geometry.

      Strictly opt-in: when absent, the kit emits only the pose op and leaves data untouched. Consumers wire this via geometryProjection={myProjection}.

      GeometryProjection

    • OptionalgetActiveMode?: () => { allowedCapabilities: ReadonlySet<string>; id: string }

      Returns the active mode id + the capability tags the mode allows. Defaults to { id: 'normal', allowedCapabilities: new Set() } when omitted. Apps using the modality machine should derive this from modality.machine.registry.current() (mode.id + mode.allows union with implicit capability tags).

      Threading this through enables mode-aware chrome (selection outline, resize handles, rotation handle are off in path-edit mode) and the dispatcher's eligibility filter in later phases.

    • OptionalgetFocused?: () => boolean

      Optional live focus getter for chrome-caps' focused ctx field. SceneCanvas does not own focus state by default — wire this when your visibility rules read the focused atom (e.g. the kit's default selection.rotation-handle rule requires focus). Omit to default focused to true (rule fires regardless of focus).

    • Optionalingestion?: {
          clipboard?: {
              enabled?: boolean;
              reviver?: (key: string, value: unknown) => unknown;
          };
          handlers?: ContentHandlerEntry[];
          resolveSrc?: (file: File) => Promise<string>;
          svg?: SvgIngestOptions;
      }

      External-content ingestion (OS drop / clipboard paste / picker). handlers are consumer content handlers registered for this canvas's lifetime (priority 0 by default — they beat the kit's image/* / image/svg+xml handlers at -100/-90). resolveSrc overrides the image handler's data:-URI embed (e.g. upload to an asset store, return the URL). svg.unpack makes the kit SVG handler parse dropped SVG files into native scene nodes instead of keeping each one a single embedded-image node. clipboard configures the kit weasel-JSON paste handler: enabled by default (pastes of weasel clipboard payloads re-materialize through this canvas's adapter); reviver restores JSON-unfriendly values the copying side encoded via jsonReplacer (typed arrays etc.); enabled: false opts the canvas out — but note the kit handler still consumes weasel-matching items at match time; on a disabled canvas it declines inert (a dwarn, nothing ingested) rather than falling through. Only items that never match (non-weasel text) flow on to other handlers. Memoize handlers (useState/useMemo/module const) — an inline array literal re-registers the handlers on every render. The same applies to clipboard.reviver: an inline function identity-churns the memoized clipboard ctx each render (harmless but wasteful).

    • OptionalinitialActiveTool?: string

      Initial active-slot tool id. Default: 'select'. Must be one of the registered tools (via defaultTools / toolBundle). Useful for demos / consumers that want to land on a non-select tool — e.g. the lasso demo starts with initialActiveTool="lasso". Ignored when the consumer supplies their own tools prop.

    • OptionalinsertNodeFactories?: Record<string, InsertNodeFactory>

      Consumer node factories for the insert action, keyed by tool kind. Each factory receives the drag AABB + tool extras and returns the node's data (in this canvas's own data shape) plus an optional pose. A factory for a kit kind (rect, line, …) replaces the kit's default { path, fill } node for that kind; a factory for a novel kind (e.g. text) adds insert support the kit doesn't ship. The dep supplies id, layer, and the undoable op. Return null to reject an insert.

    • OptionalinsertTool?: {
          create: SceneToAdapterOptions<TData, TLayer, TPose>["commitInsert"];
          layer?: TLayer;
      }
    • OptionalisPointerInteractive?: (id: string) => boolean

      Optional per-id pointer-interactivity predicate. When supplied, ids for which the predicate returns false are excluded from hit-test results — getNodeAtPoint returns null for those positions. Typical use: scoping-dim integration suppresses pointer events for non-active nodes during a mode transition.

      Defaults to () => true (all nodes are interactive).

    • Optionallayers?: LayersMap<SceneNode<TData, TLayer, TPose>, TPose>

      Layer configuration. When omitted, SceneCanvas applies kit defaults (a scene slot that paints node.data.color rects + a default selection overlay). Partial slot configs deep-merge with the defaults; pass slot: null to suppress a default explicitly.

    • Optionallayouts?: SceneToAdapterOptions<TData, TLayer, TPose>["layouts"]

      Layout strategies keyed by container node id (or a resolver). Forwarded to sceneToAdapter so useMove's layout pass runs on configured containers (reflow on enter, reparent + reflow on commit).

    • OptionalmodalityHud?: boolean | { modeId?: string }

      Dev HUD: when true (or object), mounts a fixed-position widget below the pick HUD showing the active modality mode, active-slot tool, and hotkey stack. Pass { modeId } to populate the mode line.

    • OptionalonDoubleClick?: (hit: SceneCanvasHit | null) => void

      Called when the user double-clicks the canvas. Receives the hit node (id + kind) at the double-click position, or null when the click lands on empty canvas. Wired internally via a dblclick listener on the canvas element so it doesn't interfere with the pointer-gesture pipeline.

      Typical use: modality dispatch — enter path-edit on a path node, isolation on a group, text-edit on a text node.

    • OptionalonToolsCreated?: (tools: ToolsApi) => void

      Called once after SceneCanvas constructs (or receives) its ToolsApi. Useful for introspection — e.g. the toolkit-builder dev surface walks tools.registry to render the live route table. Fires with the consumer-supplied tools prop when present, or with the internally-synthesized one otherwise.

    • OptionalpickHud?: boolean

      Dev HUD: when true, mounts a fixed-position widget just below the cursor-coords HUD listing the ids returned by pickEvery(world) under the cursor. Useful for diagnosing hit-test order and container/leaf overlap during select-tool work.

    • Optionalrouting?: readonly NodeRoutingEntry[]

      Routing-trait classifiers — list of NodeRoutingEntry entries. The kit constructs a NodeRouting registry per-<SceneCanvas> from this prop, then uses the resulting classifier to derive each hit's kind when building getNodeAtPoint. Tool routing tables (e.g. { target: 'rect', actionId: 'move' }) match against the produced kind strings.

      Pass defaultNodeRouting to pick up the kit's built-in shape kinds (rect, ellipse, polygon, …) for data: { kind: '<shape>' } nodes. Spread additional entries for consumer-defined kinds.

      See docs/superpowers/specs/2026-05-24-node-traits-reframe-design.md.

      Memoize the routing value. The kit memoizes the registry on the prop's reference identity. Passing a fresh array each render (e.g. routing={[...defaultNodeRouting, custom]} inline) rebuilds the registry and cascades into a new adapter, churning gesture state. Define the list as a module-level constant, or wrap it in useMemo. (defaultNodeRouting alone is a stable module-level constant; spreading it with extras is what needs the memo.)

    • scene:
          | Scene<TData, TLayer, TPose>
          | SerializedScene<TData, TLayer, TPose>
          | {
              nodes: ReadonlyArray<unknown>;
              systemLayers?: ReadonlyArray<{ id: string }>;
              version: number;
          }

      A Scene (typically from useScene) — or a SerializedScene JSON object, which SceneCanvas bakes into a Scene internally on first render. The serialized form is read once; subsequent changes to the prop are ignored. Pass a key prop on <SceneCanvas> to force a fresh canvas from updated JSON.

      The accepted JSON shape is intentionally relaxed (version: number rather than 1 literal) so a import json from './x.json' result satisfies the type without an as cast — Vite infers number-literal types as number from JSON, which the strict SerializedScene<…> would reject.

    • Optionalselection?: SelectionApi
    • OptionalselectionMode?: CanvasSelectionMode

      High-level selection semantics. Controls whether canvas interactions mutate selection and whether multi-select chrome (union AABB) activates.

      • 'single' (default) — click selects one id.
      • 'multi' — shift-click extends/toggles.
      • 'none' — canvas interactions never update selection. See CanvasSelectionMode.
    • OptionalselectionOptions?: UseSelectionOptions
    • OptionalselectTool?: {
          handleHitRadius?: number;
          move?: UseMoveOptions<TPose>;
          pickBest?: (
              worldX: number,
              worldY: number,
              alt: boolean,
              sel: readonly string[],
          ) => string | null;
          resize?: UseResizeOptions<TPose>;
          rotate?: UseRotateOptions<TPose> | false;
          snap?: SnapStrategy<TPose>;
      }
      • OptionalhandleHitRadius?: number
      • Optionalmove?: UseMoveOptions<TPose>
      • OptionalpickBest?: (
            worldX: number,
            worldY: number,
            alt: boolean,
            sel: readonly string[],
        ) => string | null

        Override the body-pick used on click/pointerdown. Alt-aware: receives the live alt state + current selection so consumers can implement alt-cycling through an overlapping stack. Default: top-most hit (alt ignored).

      • Optionalresize?: UseResizeOptions<TPose>
      • Optionalrotate?: UseRotateOptions<TPose> | false

        Rotation options, or false to disable rotation entirely — drops the rotate action AND hides the selection rotation-handle chrome, so a consumer whose objects don't rotate (e.g. a floor-plan / garden editor) opts out with a single switch instead of pairing actions={{ rotate: null }} with a chromeVisibility override.

      • Optionalsnap?: SnapStrategy<TPose>
    • Optionalshaders?: ShaderProgramHandle[]

      Custom shader programs to compile on the renderer. Forwarded directly to <Canvas shaders={...} />. See CanvasProps.shaders for details.

    • OptionaltoolBundle?: ToolBundle

      Named preset for the built-in tool set: 'minimal' (select + hand), 'standard' (select + rotate + hand + rect + ellipse + line + pencil), or 'exhaustive' (every built-in including polygon, star, lasso, text, clone). When set, defines the starting set; defaultTools (if also passed) overrides it. Ignored when the consumer supplies their own tools prop.

    • OptionaltoolOptions?: BuiltinToolOptions

      Per-tool option overrides for the built-in shape/lasso/clone tools. Each entry is a narrow subset of the underlying hook's options surface — lasso.mode, clone.cloneSelection, etc.

    • Optionaltools?: ToolsApi | Record<string, AnyTool | true | false>

      Extra tools or overrides keyed by id, or a full ToolsApi takeover.

      Patch form (Record<string, AnyTool | true | false>): merged into the built-in registry on top of whatever defaultTools / toolBundle already selected.

      • true pulls in the built-in for this id ('pen', 'lasso', 'rotate', …) even when it's outside the active tier — useful for toolBundle: 'minimal' + tools={{ pen: true }}. Unknown built-in ids warn in dev and are ignored.
      • AnyTool adds a new id or replaces an existing one (dev-only warning on replace).
      • false omits a bundled tool entirely. Auto-wiring (keybindings, dispatcher, action registry) still runs.

      Takeover form (ToolsApi): the internal default useSelectTool is bypassed and this tools value is forwarded to Canvas as-is — the consumer owns active-slot management. Keybindings are still auto-wired against the supplied registry; pass enableKeybindings={false} to opt out.

    • Optionalviewport?: {
          animatedZoom?:
              | boolean
              | {
                  duration?: number;
                  easing?: (t: number) => number;
                  resetDuration?: number;
              };
          inertia?: | boolean
          | {
              boundary?: "stop"
              | "bounce"
              | "spring";
              bounds?: PanBounds;
              friction?: number;
              minSpeed?: number;
          };
          pan?: boolean;
          pinchZoom?: boolean
          | { max?: number; min?: number };
          recenter?: () => void;
          zoom?: boolean | ViewportZoomOptions;
      }

      Viewport feature wiring.

      • inertia, pinchZoom, animatedZoom are opt-in: pass true for defaults or an object to tune. Omitted means off.
      • pan (wheel pan) and zoom (Cmd+wheel + Cmd+=/-/0) are opt-OUT: on by default; pass false to disable. They are wired by registering the kit's viewport.pan / viewport.zoom action descriptors with the actions registry — disabling via the actions prop (actions: { 'viewport.wheelPan': null }) also works and runs after this.

      When omitted entirely, no hand/pinch tools are registered but the default wheel pan + Cmd+wheel/key zoom remain wired (canvas-first default). Pass { pan: false, zoom: false } to opt out entirely.

      • OptionalanimatedZoom?:
            | boolean
            | {
                duration?: number;
                easing?: (t: number) => number;
                resetDuration?: number;
            }
      • Optionalinertia?:
            | boolean
            | {
                boundary?: "stop"
                | "bounce"
                | "spring";
                bounds?: PanBounds;
                friction?: number;
                minSpeed?: number;
            }
      • Optionalpan?: boolean
      • OptionalpinchZoom?: boolean | { max?: number; min?: number }
      • Optionalrecenter?: () => void

        Callback invoked by Cmd-0 (viewport.zoom action's reset branch). When supplied, replaces the default reset-to-identity behavior — consumers typically refit the document page into the workspace via fitViewToBounds. The callback owns its own bounds + host dims and dispatches the resulting view via onViewChange.

      • Optionalzoom?: boolean | ViewportZoomOptions

        Wheel/keyboard zoom. true/omitted = default Cmd+wheel zoom with the kit's 0.1–8 clamp; false disables. Pass a ViewportZoomOptions object to bind zoom to plain wheel (wheel: 'plain', pair with pan: false) and/or set min/max scale clamps.