@canvas-harness/core 0.0.0 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -71,6 +71,13 @@ type Style = {
71
71
  * per-keystroke).
72
72
  */
73
73
  autoFit?: boolean;
74
+ /**
75
+ * Color applied to SVG icons (`node.type === 'icon'`) — substitutes
76
+ * every `currentColor` occurrence in the SVG markup before
77
+ * rasterizing. The rasterizer caches by `(markup, iconColor, size)`
78
+ * so changing this re-keys without invalidating other variants.
79
+ */
80
+ iconColor?: string;
74
81
  };
75
82
  type EdgeStyle = Style & {
76
83
  sourceArrowhead?: Arrowhead;
@@ -126,6 +133,28 @@ type Node = {
126
133
  style?: Style;
127
134
  data?: unknown;
128
135
  };
136
+ /**
137
+ * `node.data` shape for `node.type === 'image'`. `src` is a self-
138
+ * contained data URI (we don't accept external URLs to keep scenes
139
+ * portable + CORS-free). `naturalW` / `naturalH` are the post-downscale
140
+ * dimensions used for aspect-ratio preservation on resize.
141
+ */
142
+ type ImageNodeData = {
143
+ src: string;
144
+ naturalW: number;
145
+ naturalH: number;
146
+ alt?: string;
147
+ };
148
+ /**
149
+ * `node.data` shape for `node.type === 'icon'`. `src` is sanitized
150
+ * SVG markup (scripts + event handlers stripped at add time). The
151
+ * recolor knob lives on `style.iconColor`, not here, so theming
152
+ * flows through the same channel as other style tokens.
153
+ */
154
+ type IconNodeData = {
155
+ src: string;
156
+ alt?: string;
157
+ };
129
158
 
130
159
  type PathStyle = 'straight' | 'bezier' | 'polyline';
131
160
  /**
@@ -1475,6 +1504,55 @@ interface CanvasStore {
1475
1504
  * batch (so one undo restores the node + every edge that pointed to it).
1476
1505
  */
1477
1506
  removeNode(id: NodeId): void;
1507
+ /**
1508
+ * Adds a raster image node. Async because the input may need decoding
1509
+ * + downscaling before storage. Accepts `File` / `Blob` / data URI;
1510
+ * external URLs are rejected to keep scenes self-contained.
1511
+ *
1512
+ * PNG and JPEG only; up to 2 MB. Larger sources throw immediately
1513
+ * (caller should surface the error in UI).
1514
+ *
1515
+ * `w` / `h` default to the natural dimensions clamped to 400 px on
1516
+ * the longer side. Aspect ratio is preserved by default; the resize
1517
+ * gesture locks aspect for image nodes (shift to override).
1518
+ *
1519
+ * @example
1520
+ * const id = await store.addImage({ src: file, x: 100, y: 100 })
1521
+ */
1522
+ addImage(opts: {
1523
+ src: File | Blob | string;
1524
+ x: number;
1525
+ y: number;
1526
+ w?: number;
1527
+ h?: number;
1528
+ /** Longest-side cap after downscale. Default 2048. 0 disables. */
1529
+ maxDimension?: number;
1530
+ alt?: string;
1531
+ style?: Style;
1532
+ }): Promise<NodeId>;
1533
+ /**
1534
+ * Adds an SVG icon node. Markup is sanitized at add time (scripts +
1535
+ * event handlers stripped) and stored on `node.data.src`. The
1536
+ * `iconColor` style token substitutes `currentColor` at rasterize
1537
+ * time so the same SVG renders in multiple tints without re-storage.
1538
+ *
1539
+ * `w` / `h` default to the SVG's intrinsic size (width/height attrs
1540
+ * or viewBox); falls back to 24×24 if neither is present. Aspect
1541
+ * ratio is locked on resize by default.
1542
+ *
1543
+ * @example
1544
+ * const id = await store.addSvg({ src: '<svg ...>', x: 0, y: 0, color: '#3b82f6' })
1545
+ */
1546
+ addSvg(opts: {
1547
+ src: string;
1548
+ x: number;
1549
+ y: number;
1550
+ w?: number;
1551
+ h?: number;
1552
+ color?: string;
1553
+ alt?: string;
1554
+ style?: Style;
1555
+ }): Promise<NodeId>;
1478
1556
  /** Adds an edge. Returns its id. */
1479
1557
  addEdge(edge: Edge): EdgeId;
1480
1558
  /** Patches fields on an existing edge. */
@@ -1944,6 +2022,16 @@ type RendererOptions = {
1944
2022
  * the synced scene). Update at runtime via `Renderer.setBackground`.
1945
2023
  */
1946
2024
  background?: CanvasBackground;
2025
+ /**
2026
+ * Color for all selection chrome: selection outlines, resize +
2027
+ * rotate handles, edge endpoint + midpoint handles, marquee rect,
2028
+ * and the drag-create preview. Defaults to `#3b82f6` (the standard
2029
+ * canvas-app blue). Update at runtime via `Renderer.setSelectionColor`.
2030
+ *
2031
+ * Accepts any CSS color literal (hex, rgb(), named). The marquee
2032
+ * fill tints via globalAlpha — no parsing needed.
2033
+ */
2034
+ selectionColor?: string;
1947
2035
  /**
1948
2036
  * Fires when the set of custom nodes that should be rendered in the DOM
1949
2037
  * overlay changes. Consumers use this to mount/unmount React subtrees
@@ -1965,6 +2053,8 @@ type Renderer = {
1965
2053
  setSize(cssW: number, cssH: number): void;
1966
2054
  /** Update the page background / pattern. Triggers a static repaint. */
1967
2055
  setBackground(bg: CanvasBackground | undefined): void;
2056
+ /** Update the selection chrome color. Triggers an interactive repaint. */
2057
+ setSelectionColor(color: string): void;
1968
2058
  /** Per-frame timing (FPS, lastMs, avgMs, frames). */
1969
2059
  stats(): FrameStats;
1970
2060
  /** Number of items the most recent paint actually drew. */
@@ -2946,6 +3036,102 @@ type AnthropicToolDef = {
2946
3036
  */
2947
3037
  declare const opSchemasAsAnthropicTools: () => AnthropicToolDef[];
2948
3038
 
3039
+ /**
3040
+ * Raster image utilities used by `store.addImage` and the renderer's
3041
+ * asset cache.
3042
+ *
3043
+ * Inputs accepted: `File`, `Blob`, or a `data:image/(png|jpeg)` URI.
3044
+ * External URLs are rejected — scenes must be self-contained (no
3045
+ * out-of-document references, no CORS surprises).
3046
+ *
3047
+ * Anything larger than `MAX_IMAGE_BYTES` or not PNG/JPEG is rejected
3048
+ * up front with a clear error so consumers can show a useful message.
3049
+ */
3050
+ declare const MAX_IMAGE_BYTES: number;
3051
+ /**
3052
+ * Validates that the input is a PNG/JPEG within the size cap. Throws
3053
+ * on rejection — meant for the synchronous prologue of an async
3054
+ * `addImage` call so consumers see the failure immediately, not after
3055
+ * an in-flight load.
3056
+ */
3057
+ declare const validateImageInput: (input: File | Blob | string) => void;
3058
+ /** Normalize any accepted `addImage` input to a `Blob`. */
3059
+ declare const toImageBlob: (input: File | Blob | string) => Promise<Blob>;
3060
+ /**
3061
+ * Downscales a blob's image if its longer side exceeds `maxDim`. Returns
3062
+ * the original blob unchanged when no downscale is needed. The output
3063
+ * MIME mirrors the input (PNG stays PNG to preserve alpha; JPEG stays
3064
+ * JPEG with q=0.9).
3065
+ *
3066
+ * `maxDim <= 0` disables downscaling entirely — useful when the caller
3067
+ * wants the original bytes (e.g. they're going to do their own
3068
+ * processing or they need full fidelity).
3069
+ */
3070
+ declare const downscaleImageBlob: (blob: Blob, maxDim: number) => Promise<{
3071
+ blob: Blob;
3072
+ naturalW: number;
3073
+ naturalH: number;
3074
+ }>;
3075
+ /**
3076
+ * Encodes a blob as a `data:` URI. The store persists this string on
3077
+ * `node.data.src` so the node round-trips through serialize/restore
3078
+ * without needing an external asset store.
3079
+ */
3080
+ declare const blobToDataUri: (blob: Blob) => Promise<string>;
3081
+
3082
+ /**
3083
+ * SVG utilities used by `store.addSvg` and the renderer's asset cache.
3084
+ *
3085
+ * SVG is XML — it can carry `<script>` tags, `on*` event handlers, and
3086
+ * `javascript:` hrefs that execute when the markup is inlined into the
3087
+ * DOM. We rasterize SVGs (so the DOM is never asked to live-render
3088
+ * them as elements), but a defense-in-depth sanitize still runs since
3089
+ * the rasterization itself goes through `<img src=blob:>` and a stray
3090
+ * embedded `<foreignObject>` could host arbitrary HTML.
3091
+ */
3092
+ declare const MAX_SVG_BYTES: number;
3093
+ /**
3094
+ * Cheap "is this plausibly SVG markup?" check + size cap. Throws on
3095
+ * rejection so consumers see the error immediately.
3096
+ */
3097
+ declare const validateSvgMarkup: (markup: string) => void;
3098
+ /**
3099
+ * Removes attack surfaces from SVG markup:
3100
+ * - `<script>` and `<foreignObject>` elements entirely
3101
+ * - `on*` event-handler attributes
3102
+ * - `href` / `xlink:href` / `src` attributes whose value starts with
3103
+ * `javascript:` (case-insensitive)
3104
+ * - External entity references (`<!DOCTYPE` / `<!ENTITY`) by parsing
3105
+ * in SVG mode (DOMParser ignores DTDs in SVG context)
3106
+ *
3107
+ * Returns the cleaned markup. Throws if the parser can't make sense
3108
+ * of the input.
3109
+ */
3110
+ declare const sanitizeSvg: (markup: string) => string;
3111
+ /**
3112
+ * Resolves intended display dimensions for an SVG. Order of preference:
3113
+ * 1. explicit `width` + `height` attributes (numeric, units stripped)
3114
+ * 2. `viewBox` width/height
3115
+ * 3. fallback 24×24
3116
+ *
3117
+ * The result is the SVG's "natural size" — what `addSvg` uses as the
3118
+ * default node dimensions when caller omits `w`/`h`.
3119
+ */
3120
+ declare const extractSvgDimensions: (markup: string) => {
3121
+ w: number;
3122
+ h: number;
3123
+ };
3124
+ /**
3125
+ * Substitutes every `currentColor` occurrence in the markup with the
3126
+ * given color literal. Case-insensitive. Used by the rasterizer cache
3127
+ * to bake the icon's tint into the rendered bitmap.
3128
+ *
3129
+ * Single-color recoloring covers ~95% of real icon libraries (Lucide,
3130
+ * Heroicons, Phosphor, Tabler, etc.) which are designed monochromatic.
3131
+ * Two-tone icons can pre-color their markup and skip this step.
3132
+ */
3133
+ declare const applySvgColor: (markup: string, color: string) => string;
3134
+
2949
3135
  /**
2950
3136
  * Extension system — see ARCHITECTURE.md §13.9.
2951
3137
  *
@@ -2974,8 +3160,14 @@ type Extension = {
2974
3160
  * Called when the extension is installed. May return a cleanup
2975
3161
  * function that runs on uninstall (in addition to auto-unsubscribed
2976
3162
  * listeners registered via `api.on`).
3163
+ *
3164
+ * `void` in the union is deliberate — extensions whose installer
3165
+ * does its work via `api.on` (auto-cleaned-up) don't need to return
3166
+ * anything. `undefined | (() => void)` would require an explicit
3167
+ * `return undefined`, which is noise. Biome's noConfusingVoidType
3168
+ * fires on this pattern; the suppression below is intentional.
2977
3169
  */
2978
- onInstall(api: ExtensionApi): undefined | (() => void);
3170
+ onInstall(api: ExtensionApi): void | (() => void);
2979
3171
  };
2980
3172
  /**
2981
3173
  * Defines an extension. Pure identity — exists for symmetry with
@@ -3021,4 +3213,4 @@ declare const installedExtensions: (store: CanvasStore) => string[];
3021
3213
  */
3022
3214
  declare const VERSION = "0.0.0";
3023
3215
 
3024
- export { type AnthropicToolDef, type Arrowhead, BEZIER_SEGMENTS, type BatchId, type BitmapCacheEntry, type BitmapCacheRequest, type BuiltInNodeType, CODE_BG_COLOR, CODE_BLOCK_MARGIN_Y, CODE_BLOCK_PADDING_X, CONTENT_HEIGHT_BUFFER, CONTENT_PADDING, type CameraState, type CanvasBackground, type CanvasBackgroundPattern, type CanvasStore, type CanvasSurface, type ClientId, type ClipResult, type ConflictRecord, type ContextEdge, type ContextNode, DEFAULT_BACKGROUND, DEFAULT_CAMERA, DEFAULT_HIGHLIGHT_COLOR, DEFAULT_HIGHLIGHT_COLOR_DARK, DEFAULT_MINIMAP_MAX_NODES, DEFAULT_STYLE, DEFAULT_TEXT_COLOR, type DeserializeOptions, type DragOriginal, type DrawTextOptions, EDGE_HANDLE_SLOP_PX, EDGE_HIT_SLOP_PX, type Edge, type EdgeEnd, type EdgeGeometry, EdgeGeometryCache, type EdgeHit, type EdgeId, type EdgeStyle, type EditorAdapter, type EditorAdapterFactory, type EditorAdapterMountOptions, type EstimateOptions, type ExportOptions, type Extension, type ExtensionApi, FONT_FAMILY_MAP, FONT_SIZE_MAP, type FontFamily, type FontSize, type FrameLoop, type FrameStats, type GetContextOptions, type Group, type GroupId, type Hit, type IdGenerator, type InlineType, type InteractionMode, type InteractionState, LINE_HEIGHT_MAP, LINK_COLOR, type LayoutLine, type LayoutOptions, MAX_ZOOM, MIN_ZOOM, type Migrator, type MinimapContentOptions, type Node, type NodeHit, type NodeId, type NodeType, type NodeTypeDef, type NodeTypeDefOptions, type Op, type OpBatch, type OpOrigin, PALM_REJECTION_GRACE_MS, type PalmRejectionState, type PathStyle, type PointerInfo, type PresenceEvent, type PresencePatch, type PresenceSlice, type PresenceState, type PrimitiveType, RESIZE_HANDLES, RESIZE_HANDLE_SIZE_PX, ROTATE_HANDLE_OFFSET_PX, ROTATE_HANDLE_RADIUS_PX, type RenderEnv, type Renderer, type RendererOptions, type ResizeHandle, SCHEMA_VERSION, type Scene, type SceneContextJson, type SchemaVersion, type SerializedClipboard, type SerializedScene, type Side, type SnapshotEnv, type SpatialId, type SpatialQuery, type SpatialResult, type StoreEventHandler, type StoreEventName, type StoreEvents, type StoreOptions, type StrokeStyle, type Style, type StyledRun, type SvgExportOptions, type SyncAdapter, type SyncAdapterCapabilities, type TextAlign, type TextStyle, type ThemeResolver, type Token, type Transform, UniformGrid, type Unsubscribe, VERSION, type Vec2, type WorldRect, applyCameraTransform, arrowheadLength, asBatchId, asClientId, asEdgeId, asGroupId, asNodeId, attachSync, autoRouteControls, clampEffectiveScale, clampZoom, clearMeasureCache, clearSurface, clearTextBitmapCache, clipSamples, computeAutoFitHeight, computeEdgeGeometry, copy, createCanvasStore, createDefaultTextareaEditor, createFrameLoop, createPalmRejectionState, createRenderer, cubicBezier, cubicBezierTangent, cut, defineExtension, defineNode, deserializeClipboard, detectConflicts, drawArrowhead, drawEdge, drawMinimapViewport, drawShape, drawTextToCanvas, drawWithNodeTransform, edgeAABBFromSamples, edgeLabelBoundsWorld, emptyPresenceState, estimateMarkdownContentHeight, exportSelection, exportSelectionSvg, exportViewport, fromSerialized, fullVisibleClipResult, getCanvasFont, getContentHeight, getContext, getDpr, getFontEpoch, getMarkdownLineHeightPx, getOrRenderTextBitmap, getPointAndTangentAtArcLength, getTextBitmapCacheSize, handleEnter, handleWorldPositions, hitTestAny, hitTestEdge, hitTestHandles, hitTestPoint, hitTestRotateHandle, idleInteractionState, inflateRect, insertLink, installExtension, installedExtensions, inverseBatch, inverseOp, isAttached, isCanvasHarnessClipboard, isDrawablePrimitive, isMoving, isNodeRemoteEditing, layoutTokens, makeIdGenerator, marqueeNodes, measureText, midpointToCubicControls, minimapScreenToWorld, nodeAABB, nodeIntersectsRect, nodeLocalToWorld, notePenActive, notePenInactive, opSchemas, opSchemasAsAnthropicTools, paintBackground, panByScreen, paste, pointInNode, projectEndToWorld, projectToNodeBoundary, quantizeDpr, quantizeZoom, randomClientId, rectContainsPoint, rectFromPoints, rectsIntersect, registerMigrator, renderMinimapContent, resolveColor, resolveOpacity, resolveRenderScale, resolveStrokeWidth, rotateHandleWorldPosition, rotateVecByAngle, sampleBezier, sampleSelfLoop, samplesFor, sceneBounds, screenToWorld, selfLoopGeometry, serializeSelection, setupSurface, shouldAutoFit, shouldRejectTouch, sideNormalLocal, sideOf, sizeSurface, storeToJSON, subscribeFontEpoch, tangentAtArcLength, toSerialized, toggleBold, toggleCode, toggleItalic, toggleStrike, toggleUnderline, tokenize, unionRects, viewportWorldRect, withAutoFitHeight, worldToNodeLocal, worldToScreen, worldViewport, worldViewportFromCamera, zoomAtScreenPoint };
3216
+ export { type AnthropicToolDef, type Arrowhead, BEZIER_SEGMENTS, type BatchId, type BitmapCacheEntry, type BitmapCacheRequest, type BuiltInNodeType, CODE_BG_COLOR, CODE_BLOCK_MARGIN_Y, CODE_BLOCK_PADDING_X, CONTENT_HEIGHT_BUFFER, CONTENT_PADDING, type CameraState, type CanvasBackground, type CanvasBackgroundPattern, type CanvasStore, type CanvasSurface, type ClientId, type ClipResult, type ConflictRecord, type ContextEdge, type ContextNode, DEFAULT_BACKGROUND, DEFAULT_CAMERA, DEFAULT_HIGHLIGHT_COLOR, DEFAULT_HIGHLIGHT_COLOR_DARK, DEFAULT_MINIMAP_MAX_NODES, DEFAULT_STYLE, DEFAULT_TEXT_COLOR, type DeserializeOptions, type DragOriginal, type DrawTextOptions, EDGE_HANDLE_SLOP_PX, EDGE_HIT_SLOP_PX, type Edge, type EdgeEnd, type EdgeGeometry, EdgeGeometryCache, type EdgeHit, type EdgeId, type EdgeStyle, type EditorAdapter, type EditorAdapterFactory, type EditorAdapterMountOptions, type EstimateOptions, type ExportOptions, type Extension, type ExtensionApi, FONT_FAMILY_MAP, FONT_SIZE_MAP, type FontFamily, type FontSize, type FrameLoop, type FrameStats, type GetContextOptions, type Group, type GroupId, type Hit, type IconNodeData, type IdGenerator, type ImageNodeData, type InlineType, type InteractionMode, type InteractionState, LINE_HEIGHT_MAP, LINK_COLOR, type LayoutLine, type LayoutOptions, MAX_IMAGE_BYTES, MAX_SVG_BYTES, MAX_ZOOM, MIN_ZOOM, type Migrator, type MinimapContentOptions, type Node, type NodeHit, type NodeId, type NodeType, type NodeTypeDef, type NodeTypeDefOptions, type Op, type OpBatch, type OpOrigin, PALM_REJECTION_GRACE_MS, type PalmRejectionState, type PathStyle, type PointerInfo, type PresenceEvent, type PresencePatch, type PresenceSlice, type PresenceState, type PrimitiveType, RESIZE_HANDLES, RESIZE_HANDLE_SIZE_PX, ROTATE_HANDLE_OFFSET_PX, ROTATE_HANDLE_RADIUS_PX, type RenderEnv, type Renderer, type RendererOptions, type ResizeHandle, SCHEMA_VERSION, type Scene, type SceneContextJson, type SchemaVersion, type SerializedClipboard, type SerializedScene, type Side, type SnapshotEnv, type SpatialId, type SpatialQuery, type SpatialResult, type StoreEventHandler, type StoreEventName, type StoreEvents, type StoreOptions, type StrokeStyle, type Style, type StyledRun, type SvgExportOptions, type SyncAdapter, type SyncAdapterCapabilities, type TextAlign, type TextStyle, type ThemeResolver, type Token, type Transform, UniformGrid, type Unsubscribe, VERSION, type Vec2, type WorldRect, applyCameraTransform, applySvgColor, arrowheadLength, asBatchId, asClientId, asEdgeId, asGroupId, asNodeId, attachSync, autoRouteControls, blobToDataUri, clampEffectiveScale, clampZoom, clearMeasureCache, clearSurface, clearTextBitmapCache, clipSamples, computeAutoFitHeight, computeEdgeGeometry, copy, createCanvasStore, createDefaultTextareaEditor, createFrameLoop, createPalmRejectionState, createRenderer, cubicBezier, cubicBezierTangent, cut, defineExtension, defineNode, deserializeClipboard, detectConflicts, downscaleImageBlob, drawArrowhead, drawEdge, drawMinimapViewport, drawShape, drawTextToCanvas, drawWithNodeTransform, edgeAABBFromSamples, edgeLabelBoundsWorld, emptyPresenceState, estimateMarkdownContentHeight, exportSelection, exportSelectionSvg, exportViewport, extractSvgDimensions, fromSerialized, fullVisibleClipResult, getCanvasFont, getContentHeight, getContext, getDpr, getFontEpoch, getMarkdownLineHeightPx, getOrRenderTextBitmap, getPointAndTangentAtArcLength, getTextBitmapCacheSize, handleEnter, handleWorldPositions, hitTestAny, hitTestEdge, hitTestHandles, hitTestPoint, hitTestRotateHandle, idleInteractionState, inflateRect, insertLink, installExtension, installedExtensions, inverseBatch, inverseOp, isAttached, isCanvasHarnessClipboard, isDrawablePrimitive, isMoving, isNodeRemoteEditing, layoutTokens, makeIdGenerator, marqueeNodes, measureText, midpointToCubicControls, minimapScreenToWorld, nodeAABB, nodeIntersectsRect, nodeLocalToWorld, notePenActive, notePenInactive, opSchemas, opSchemasAsAnthropicTools, paintBackground, panByScreen, paste, pointInNode, projectEndToWorld, projectToNodeBoundary, quantizeDpr, quantizeZoom, randomClientId, rectContainsPoint, rectFromPoints, rectsIntersect, registerMigrator, renderMinimapContent, resolveColor, resolveOpacity, resolveRenderScale, resolveStrokeWidth, rotateHandleWorldPosition, rotateVecByAngle, sampleBezier, sampleSelfLoop, samplesFor, sanitizeSvg, sceneBounds, screenToWorld, selfLoopGeometry, serializeSelection, setupSurface, shouldAutoFit, shouldRejectTouch, sideNormalLocal, sideOf, sizeSurface, storeToJSON, subscribeFontEpoch, tangentAtArcLength, toImageBlob, toSerialized, toggleBold, toggleCode, toggleItalic, toggleStrike, toggleUnderline, tokenize, unionRects, validateImageInput, validateSvgMarkup, viewportWorldRect, withAutoFitHeight, worldToNodeLocal, worldToScreen, worldViewport, worldViewportFromCamera, zoomAtScreenPoint };
package/dist/index.d.ts CHANGED
@@ -71,6 +71,13 @@ type Style = {
71
71
  * per-keystroke).
72
72
  */
73
73
  autoFit?: boolean;
74
+ /**
75
+ * Color applied to SVG icons (`node.type === 'icon'`) — substitutes
76
+ * every `currentColor` occurrence in the SVG markup before
77
+ * rasterizing. The rasterizer caches by `(markup, iconColor, size)`
78
+ * so changing this re-keys without invalidating other variants.
79
+ */
80
+ iconColor?: string;
74
81
  };
75
82
  type EdgeStyle = Style & {
76
83
  sourceArrowhead?: Arrowhead;
@@ -126,6 +133,28 @@ type Node = {
126
133
  style?: Style;
127
134
  data?: unknown;
128
135
  };
136
+ /**
137
+ * `node.data` shape for `node.type === 'image'`. `src` is a self-
138
+ * contained data URI (we don't accept external URLs to keep scenes
139
+ * portable + CORS-free). `naturalW` / `naturalH` are the post-downscale
140
+ * dimensions used for aspect-ratio preservation on resize.
141
+ */
142
+ type ImageNodeData = {
143
+ src: string;
144
+ naturalW: number;
145
+ naturalH: number;
146
+ alt?: string;
147
+ };
148
+ /**
149
+ * `node.data` shape for `node.type === 'icon'`. `src` is sanitized
150
+ * SVG markup (scripts + event handlers stripped at add time). The
151
+ * recolor knob lives on `style.iconColor`, not here, so theming
152
+ * flows through the same channel as other style tokens.
153
+ */
154
+ type IconNodeData = {
155
+ src: string;
156
+ alt?: string;
157
+ };
129
158
 
130
159
  type PathStyle = 'straight' | 'bezier' | 'polyline';
131
160
  /**
@@ -1475,6 +1504,55 @@ interface CanvasStore {
1475
1504
  * batch (so one undo restores the node + every edge that pointed to it).
1476
1505
  */
1477
1506
  removeNode(id: NodeId): void;
1507
+ /**
1508
+ * Adds a raster image node. Async because the input may need decoding
1509
+ * + downscaling before storage. Accepts `File` / `Blob` / data URI;
1510
+ * external URLs are rejected to keep scenes self-contained.
1511
+ *
1512
+ * PNG and JPEG only; up to 2 MB. Larger sources throw immediately
1513
+ * (caller should surface the error in UI).
1514
+ *
1515
+ * `w` / `h` default to the natural dimensions clamped to 400 px on
1516
+ * the longer side. Aspect ratio is preserved by default; the resize
1517
+ * gesture locks aspect for image nodes (shift to override).
1518
+ *
1519
+ * @example
1520
+ * const id = await store.addImage({ src: file, x: 100, y: 100 })
1521
+ */
1522
+ addImage(opts: {
1523
+ src: File | Blob | string;
1524
+ x: number;
1525
+ y: number;
1526
+ w?: number;
1527
+ h?: number;
1528
+ /** Longest-side cap after downscale. Default 2048. 0 disables. */
1529
+ maxDimension?: number;
1530
+ alt?: string;
1531
+ style?: Style;
1532
+ }): Promise<NodeId>;
1533
+ /**
1534
+ * Adds an SVG icon node. Markup is sanitized at add time (scripts +
1535
+ * event handlers stripped) and stored on `node.data.src`. The
1536
+ * `iconColor` style token substitutes `currentColor` at rasterize
1537
+ * time so the same SVG renders in multiple tints without re-storage.
1538
+ *
1539
+ * `w` / `h` default to the SVG's intrinsic size (width/height attrs
1540
+ * or viewBox); falls back to 24×24 if neither is present. Aspect
1541
+ * ratio is locked on resize by default.
1542
+ *
1543
+ * @example
1544
+ * const id = await store.addSvg({ src: '<svg ...>', x: 0, y: 0, color: '#3b82f6' })
1545
+ */
1546
+ addSvg(opts: {
1547
+ src: string;
1548
+ x: number;
1549
+ y: number;
1550
+ w?: number;
1551
+ h?: number;
1552
+ color?: string;
1553
+ alt?: string;
1554
+ style?: Style;
1555
+ }): Promise<NodeId>;
1478
1556
  /** Adds an edge. Returns its id. */
1479
1557
  addEdge(edge: Edge): EdgeId;
1480
1558
  /** Patches fields on an existing edge. */
@@ -1944,6 +2022,16 @@ type RendererOptions = {
1944
2022
  * the synced scene). Update at runtime via `Renderer.setBackground`.
1945
2023
  */
1946
2024
  background?: CanvasBackground;
2025
+ /**
2026
+ * Color for all selection chrome: selection outlines, resize +
2027
+ * rotate handles, edge endpoint + midpoint handles, marquee rect,
2028
+ * and the drag-create preview. Defaults to `#3b82f6` (the standard
2029
+ * canvas-app blue). Update at runtime via `Renderer.setSelectionColor`.
2030
+ *
2031
+ * Accepts any CSS color literal (hex, rgb(), named). The marquee
2032
+ * fill tints via globalAlpha — no parsing needed.
2033
+ */
2034
+ selectionColor?: string;
1947
2035
  /**
1948
2036
  * Fires when the set of custom nodes that should be rendered in the DOM
1949
2037
  * overlay changes. Consumers use this to mount/unmount React subtrees
@@ -1965,6 +2053,8 @@ type Renderer = {
1965
2053
  setSize(cssW: number, cssH: number): void;
1966
2054
  /** Update the page background / pattern. Triggers a static repaint. */
1967
2055
  setBackground(bg: CanvasBackground | undefined): void;
2056
+ /** Update the selection chrome color. Triggers an interactive repaint. */
2057
+ setSelectionColor(color: string): void;
1968
2058
  /** Per-frame timing (FPS, lastMs, avgMs, frames). */
1969
2059
  stats(): FrameStats;
1970
2060
  /** Number of items the most recent paint actually drew. */
@@ -2946,6 +3036,102 @@ type AnthropicToolDef = {
2946
3036
  */
2947
3037
  declare const opSchemasAsAnthropicTools: () => AnthropicToolDef[];
2948
3038
 
3039
+ /**
3040
+ * Raster image utilities used by `store.addImage` and the renderer's
3041
+ * asset cache.
3042
+ *
3043
+ * Inputs accepted: `File`, `Blob`, or a `data:image/(png|jpeg)` URI.
3044
+ * External URLs are rejected — scenes must be self-contained (no
3045
+ * out-of-document references, no CORS surprises).
3046
+ *
3047
+ * Anything larger than `MAX_IMAGE_BYTES` or not PNG/JPEG is rejected
3048
+ * up front with a clear error so consumers can show a useful message.
3049
+ */
3050
+ declare const MAX_IMAGE_BYTES: number;
3051
+ /**
3052
+ * Validates that the input is a PNG/JPEG within the size cap. Throws
3053
+ * on rejection — meant for the synchronous prologue of an async
3054
+ * `addImage` call so consumers see the failure immediately, not after
3055
+ * an in-flight load.
3056
+ */
3057
+ declare const validateImageInput: (input: File | Blob | string) => void;
3058
+ /** Normalize any accepted `addImage` input to a `Blob`. */
3059
+ declare const toImageBlob: (input: File | Blob | string) => Promise<Blob>;
3060
+ /**
3061
+ * Downscales a blob's image if its longer side exceeds `maxDim`. Returns
3062
+ * the original blob unchanged when no downscale is needed. The output
3063
+ * MIME mirrors the input (PNG stays PNG to preserve alpha; JPEG stays
3064
+ * JPEG with q=0.9).
3065
+ *
3066
+ * `maxDim <= 0` disables downscaling entirely — useful when the caller
3067
+ * wants the original bytes (e.g. they're going to do their own
3068
+ * processing or they need full fidelity).
3069
+ */
3070
+ declare const downscaleImageBlob: (blob: Blob, maxDim: number) => Promise<{
3071
+ blob: Blob;
3072
+ naturalW: number;
3073
+ naturalH: number;
3074
+ }>;
3075
+ /**
3076
+ * Encodes a blob as a `data:` URI. The store persists this string on
3077
+ * `node.data.src` so the node round-trips through serialize/restore
3078
+ * without needing an external asset store.
3079
+ */
3080
+ declare const blobToDataUri: (blob: Blob) => Promise<string>;
3081
+
3082
+ /**
3083
+ * SVG utilities used by `store.addSvg` and the renderer's asset cache.
3084
+ *
3085
+ * SVG is XML — it can carry `<script>` tags, `on*` event handlers, and
3086
+ * `javascript:` hrefs that execute when the markup is inlined into the
3087
+ * DOM. We rasterize SVGs (so the DOM is never asked to live-render
3088
+ * them as elements), but a defense-in-depth sanitize still runs since
3089
+ * the rasterization itself goes through `<img src=blob:>` and a stray
3090
+ * embedded `<foreignObject>` could host arbitrary HTML.
3091
+ */
3092
+ declare const MAX_SVG_BYTES: number;
3093
+ /**
3094
+ * Cheap "is this plausibly SVG markup?" check + size cap. Throws on
3095
+ * rejection so consumers see the error immediately.
3096
+ */
3097
+ declare const validateSvgMarkup: (markup: string) => void;
3098
+ /**
3099
+ * Removes attack surfaces from SVG markup:
3100
+ * - `<script>` and `<foreignObject>` elements entirely
3101
+ * - `on*` event-handler attributes
3102
+ * - `href` / `xlink:href` / `src` attributes whose value starts with
3103
+ * `javascript:` (case-insensitive)
3104
+ * - External entity references (`<!DOCTYPE` / `<!ENTITY`) by parsing
3105
+ * in SVG mode (DOMParser ignores DTDs in SVG context)
3106
+ *
3107
+ * Returns the cleaned markup. Throws if the parser can't make sense
3108
+ * of the input.
3109
+ */
3110
+ declare const sanitizeSvg: (markup: string) => string;
3111
+ /**
3112
+ * Resolves intended display dimensions for an SVG. Order of preference:
3113
+ * 1. explicit `width` + `height` attributes (numeric, units stripped)
3114
+ * 2. `viewBox` width/height
3115
+ * 3. fallback 24×24
3116
+ *
3117
+ * The result is the SVG's "natural size" — what `addSvg` uses as the
3118
+ * default node dimensions when caller omits `w`/`h`.
3119
+ */
3120
+ declare const extractSvgDimensions: (markup: string) => {
3121
+ w: number;
3122
+ h: number;
3123
+ };
3124
+ /**
3125
+ * Substitutes every `currentColor` occurrence in the markup with the
3126
+ * given color literal. Case-insensitive. Used by the rasterizer cache
3127
+ * to bake the icon's tint into the rendered bitmap.
3128
+ *
3129
+ * Single-color recoloring covers ~95% of real icon libraries (Lucide,
3130
+ * Heroicons, Phosphor, Tabler, etc.) which are designed monochromatic.
3131
+ * Two-tone icons can pre-color their markup and skip this step.
3132
+ */
3133
+ declare const applySvgColor: (markup: string, color: string) => string;
3134
+
2949
3135
  /**
2950
3136
  * Extension system — see ARCHITECTURE.md §13.9.
2951
3137
  *
@@ -2974,8 +3160,14 @@ type Extension = {
2974
3160
  * Called when the extension is installed. May return a cleanup
2975
3161
  * function that runs on uninstall (in addition to auto-unsubscribed
2976
3162
  * listeners registered via `api.on`).
3163
+ *
3164
+ * `void` in the union is deliberate — extensions whose installer
3165
+ * does its work via `api.on` (auto-cleaned-up) don't need to return
3166
+ * anything. `undefined | (() => void)` would require an explicit
3167
+ * `return undefined`, which is noise. Biome's noConfusingVoidType
3168
+ * fires on this pattern; the suppression below is intentional.
2977
3169
  */
2978
- onInstall(api: ExtensionApi): undefined | (() => void);
3170
+ onInstall(api: ExtensionApi): void | (() => void);
2979
3171
  };
2980
3172
  /**
2981
3173
  * Defines an extension. Pure identity — exists for symmetry with
@@ -3021,4 +3213,4 @@ declare const installedExtensions: (store: CanvasStore) => string[];
3021
3213
  */
3022
3214
  declare const VERSION = "0.0.0";
3023
3215
 
3024
- export { type AnthropicToolDef, type Arrowhead, BEZIER_SEGMENTS, type BatchId, type BitmapCacheEntry, type BitmapCacheRequest, type BuiltInNodeType, CODE_BG_COLOR, CODE_BLOCK_MARGIN_Y, CODE_BLOCK_PADDING_X, CONTENT_HEIGHT_BUFFER, CONTENT_PADDING, type CameraState, type CanvasBackground, type CanvasBackgroundPattern, type CanvasStore, type CanvasSurface, type ClientId, type ClipResult, type ConflictRecord, type ContextEdge, type ContextNode, DEFAULT_BACKGROUND, DEFAULT_CAMERA, DEFAULT_HIGHLIGHT_COLOR, DEFAULT_HIGHLIGHT_COLOR_DARK, DEFAULT_MINIMAP_MAX_NODES, DEFAULT_STYLE, DEFAULT_TEXT_COLOR, type DeserializeOptions, type DragOriginal, type DrawTextOptions, EDGE_HANDLE_SLOP_PX, EDGE_HIT_SLOP_PX, type Edge, type EdgeEnd, type EdgeGeometry, EdgeGeometryCache, type EdgeHit, type EdgeId, type EdgeStyle, type EditorAdapter, type EditorAdapterFactory, type EditorAdapterMountOptions, type EstimateOptions, type ExportOptions, type Extension, type ExtensionApi, FONT_FAMILY_MAP, FONT_SIZE_MAP, type FontFamily, type FontSize, type FrameLoop, type FrameStats, type GetContextOptions, type Group, type GroupId, type Hit, type IdGenerator, type InlineType, type InteractionMode, type InteractionState, LINE_HEIGHT_MAP, LINK_COLOR, type LayoutLine, type LayoutOptions, MAX_ZOOM, MIN_ZOOM, type Migrator, type MinimapContentOptions, type Node, type NodeHit, type NodeId, type NodeType, type NodeTypeDef, type NodeTypeDefOptions, type Op, type OpBatch, type OpOrigin, PALM_REJECTION_GRACE_MS, type PalmRejectionState, type PathStyle, type PointerInfo, type PresenceEvent, type PresencePatch, type PresenceSlice, type PresenceState, type PrimitiveType, RESIZE_HANDLES, RESIZE_HANDLE_SIZE_PX, ROTATE_HANDLE_OFFSET_PX, ROTATE_HANDLE_RADIUS_PX, type RenderEnv, type Renderer, type RendererOptions, type ResizeHandle, SCHEMA_VERSION, type Scene, type SceneContextJson, type SchemaVersion, type SerializedClipboard, type SerializedScene, type Side, type SnapshotEnv, type SpatialId, type SpatialQuery, type SpatialResult, type StoreEventHandler, type StoreEventName, type StoreEvents, type StoreOptions, type StrokeStyle, type Style, type StyledRun, type SvgExportOptions, type SyncAdapter, type SyncAdapterCapabilities, type TextAlign, type TextStyle, type ThemeResolver, type Token, type Transform, UniformGrid, type Unsubscribe, VERSION, type Vec2, type WorldRect, applyCameraTransform, arrowheadLength, asBatchId, asClientId, asEdgeId, asGroupId, asNodeId, attachSync, autoRouteControls, clampEffectiveScale, clampZoom, clearMeasureCache, clearSurface, clearTextBitmapCache, clipSamples, computeAutoFitHeight, computeEdgeGeometry, copy, createCanvasStore, createDefaultTextareaEditor, createFrameLoop, createPalmRejectionState, createRenderer, cubicBezier, cubicBezierTangent, cut, defineExtension, defineNode, deserializeClipboard, detectConflicts, drawArrowhead, drawEdge, drawMinimapViewport, drawShape, drawTextToCanvas, drawWithNodeTransform, edgeAABBFromSamples, edgeLabelBoundsWorld, emptyPresenceState, estimateMarkdownContentHeight, exportSelection, exportSelectionSvg, exportViewport, fromSerialized, fullVisibleClipResult, getCanvasFont, getContentHeight, getContext, getDpr, getFontEpoch, getMarkdownLineHeightPx, getOrRenderTextBitmap, getPointAndTangentAtArcLength, getTextBitmapCacheSize, handleEnter, handleWorldPositions, hitTestAny, hitTestEdge, hitTestHandles, hitTestPoint, hitTestRotateHandle, idleInteractionState, inflateRect, insertLink, installExtension, installedExtensions, inverseBatch, inverseOp, isAttached, isCanvasHarnessClipboard, isDrawablePrimitive, isMoving, isNodeRemoteEditing, layoutTokens, makeIdGenerator, marqueeNodes, measureText, midpointToCubicControls, minimapScreenToWorld, nodeAABB, nodeIntersectsRect, nodeLocalToWorld, notePenActive, notePenInactive, opSchemas, opSchemasAsAnthropicTools, paintBackground, panByScreen, paste, pointInNode, projectEndToWorld, projectToNodeBoundary, quantizeDpr, quantizeZoom, randomClientId, rectContainsPoint, rectFromPoints, rectsIntersect, registerMigrator, renderMinimapContent, resolveColor, resolveOpacity, resolveRenderScale, resolveStrokeWidth, rotateHandleWorldPosition, rotateVecByAngle, sampleBezier, sampleSelfLoop, samplesFor, sceneBounds, screenToWorld, selfLoopGeometry, serializeSelection, setupSurface, shouldAutoFit, shouldRejectTouch, sideNormalLocal, sideOf, sizeSurface, storeToJSON, subscribeFontEpoch, tangentAtArcLength, toSerialized, toggleBold, toggleCode, toggleItalic, toggleStrike, toggleUnderline, tokenize, unionRects, viewportWorldRect, withAutoFitHeight, worldToNodeLocal, worldToScreen, worldViewport, worldViewportFromCamera, zoomAtScreenPoint };
3216
+ export { type AnthropicToolDef, type Arrowhead, BEZIER_SEGMENTS, type BatchId, type BitmapCacheEntry, type BitmapCacheRequest, type BuiltInNodeType, CODE_BG_COLOR, CODE_BLOCK_MARGIN_Y, CODE_BLOCK_PADDING_X, CONTENT_HEIGHT_BUFFER, CONTENT_PADDING, type CameraState, type CanvasBackground, type CanvasBackgroundPattern, type CanvasStore, type CanvasSurface, type ClientId, type ClipResult, type ConflictRecord, type ContextEdge, type ContextNode, DEFAULT_BACKGROUND, DEFAULT_CAMERA, DEFAULT_HIGHLIGHT_COLOR, DEFAULT_HIGHLIGHT_COLOR_DARK, DEFAULT_MINIMAP_MAX_NODES, DEFAULT_STYLE, DEFAULT_TEXT_COLOR, type DeserializeOptions, type DragOriginal, type DrawTextOptions, EDGE_HANDLE_SLOP_PX, EDGE_HIT_SLOP_PX, type Edge, type EdgeEnd, type EdgeGeometry, EdgeGeometryCache, type EdgeHit, type EdgeId, type EdgeStyle, type EditorAdapter, type EditorAdapterFactory, type EditorAdapterMountOptions, type EstimateOptions, type ExportOptions, type Extension, type ExtensionApi, FONT_FAMILY_MAP, FONT_SIZE_MAP, type FontFamily, type FontSize, type FrameLoop, type FrameStats, type GetContextOptions, type Group, type GroupId, type Hit, type IconNodeData, type IdGenerator, type ImageNodeData, type InlineType, type InteractionMode, type InteractionState, LINE_HEIGHT_MAP, LINK_COLOR, type LayoutLine, type LayoutOptions, MAX_IMAGE_BYTES, MAX_SVG_BYTES, MAX_ZOOM, MIN_ZOOM, type Migrator, type MinimapContentOptions, type Node, type NodeHit, type NodeId, type NodeType, type NodeTypeDef, type NodeTypeDefOptions, type Op, type OpBatch, type OpOrigin, PALM_REJECTION_GRACE_MS, type PalmRejectionState, type PathStyle, type PointerInfo, type PresenceEvent, type PresencePatch, type PresenceSlice, type PresenceState, type PrimitiveType, RESIZE_HANDLES, RESIZE_HANDLE_SIZE_PX, ROTATE_HANDLE_OFFSET_PX, ROTATE_HANDLE_RADIUS_PX, type RenderEnv, type Renderer, type RendererOptions, type ResizeHandle, SCHEMA_VERSION, type Scene, type SceneContextJson, type SchemaVersion, type SerializedClipboard, type SerializedScene, type Side, type SnapshotEnv, type SpatialId, type SpatialQuery, type SpatialResult, type StoreEventHandler, type StoreEventName, type StoreEvents, type StoreOptions, type StrokeStyle, type Style, type StyledRun, type SvgExportOptions, type SyncAdapter, type SyncAdapterCapabilities, type TextAlign, type TextStyle, type ThemeResolver, type Token, type Transform, UniformGrid, type Unsubscribe, VERSION, type Vec2, type WorldRect, applyCameraTransform, applySvgColor, arrowheadLength, asBatchId, asClientId, asEdgeId, asGroupId, asNodeId, attachSync, autoRouteControls, blobToDataUri, clampEffectiveScale, clampZoom, clearMeasureCache, clearSurface, clearTextBitmapCache, clipSamples, computeAutoFitHeight, computeEdgeGeometry, copy, createCanvasStore, createDefaultTextareaEditor, createFrameLoop, createPalmRejectionState, createRenderer, cubicBezier, cubicBezierTangent, cut, defineExtension, defineNode, deserializeClipboard, detectConflicts, downscaleImageBlob, drawArrowhead, drawEdge, drawMinimapViewport, drawShape, drawTextToCanvas, drawWithNodeTransform, edgeAABBFromSamples, edgeLabelBoundsWorld, emptyPresenceState, estimateMarkdownContentHeight, exportSelection, exportSelectionSvg, exportViewport, extractSvgDimensions, fromSerialized, fullVisibleClipResult, getCanvasFont, getContentHeight, getContext, getDpr, getFontEpoch, getMarkdownLineHeightPx, getOrRenderTextBitmap, getPointAndTangentAtArcLength, getTextBitmapCacheSize, handleEnter, handleWorldPositions, hitTestAny, hitTestEdge, hitTestHandles, hitTestPoint, hitTestRotateHandle, idleInteractionState, inflateRect, insertLink, installExtension, installedExtensions, inverseBatch, inverseOp, isAttached, isCanvasHarnessClipboard, isDrawablePrimitive, isMoving, isNodeRemoteEditing, layoutTokens, makeIdGenerator, marqueeNodes, measureText, midpointToCubicControls, minimapScreenToWorld, nodeAABB, nodeIntersectsRect, nodeLocalToWorld, notePenActive, notePenInactive, opSchemas, opSchemasAsAnthropicTools, paintBackground, panByScreen, paste, pointInNode, projectEndToWorld, projectToNodeBoundary, quantizeDpr, quantizeZoom, randomClientId, rectContainsPoint, rectFromPoints, rectsIntersect, registerMigrator, renderMinimapContent, resolveColor, resolveOpacity, resolveRenderScale, resolveStrokeWidth, rotateHandleWorldPosition, rotateVecByAngle, sampleBezier, sampleSelfLoop, samplesFor, sanitizeSvg, sceneBounds, screenToWorld, selfLoopGeometry, serializeSelection, setupSurface, shouldAutoFit, shouldRejectTouch, sideNormalLocal, sideOf, sizeSurface, storeToJSON, subscribeFontEpoch, tangentAtArcLength, toImageBlob, toSerialized, toggleBold, toggleCode, toggleItalic, toggleStrike, toggleUnderline, tokenize, unionRects, validateImageInput, validateSvgMarkup, viewportWorldRect, withAutoFitHeight, worldToNodeLocal, worldToScreen, worldViewport, worldViewportFromCamera, zoomAtScreenPoint };