@tangle-network/agent-app 0.24.0 → 0.26.0

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.
@@ -12,12 +12,21 @@ import {
12
12
  collectGridTargets,
13
13
  createSnapEngine,
14
14
  createZoomPanMath,
15
+ documentCropToStageCoords,
16
+ downloadDataUrl,
17
+ exportDocumentJson,
18
+ exportPageDataUrl,
19
+ identifyTaintedSrc,
20
+ isCrossOriginSrc,
21
+ isExportHiddenNodeName,
15
22
  marqueeSelect,
16
- nudgeDelta
17
- } from "../chunk-GCIQVSNW.js";
23
+ nudgeDelta,
24
+ resolveExportParams
25
+ } from "../chunk-FSUR2752.js";
18
26
  import {
19
27
  DesignCanvas,
20
28
  EllipseGlyph,
29
+ ExportControl,
21
30
  EyeGlyph,
22
31
  EyeOffGlyph,
23
32
  GroupGlyph,
@@ -28,6 +37,7 @@ import {
28
37
  RectGlyph,
29
38
  Rulers,
30
39
  SCENE_COMMAND_HISTORY_LIMIT,
40
+ ShapesGlyph,
31
41
  SlotGlyph,
32
42
  TextGlyph,
33
43
  Toolbar,
@@ -53,129 +63,13 @@ import {
53
63
  setPageGuidesCommand,
54
64
  setPagePropsCommand,
55
65
  ungroupElementCommand
56
- } from "../chunk-2W4YCAFH.js";
66
+ } from "../chunk-LPAVBGVY.js";
57
67
  import {
58
- SCENE_SCHEMA_VERSION,
59
68
  assertSceneMediaSrc,
60
69
  bleedAwareExportBounds,
61
70
  scaleForPreset
62
71
  } from "../chunk-JZAJE3JL.js";
63
72
 
64
- // src/design-canvas-react/export-math.ts
65
- function isExportHiddenNodeName(name) {
66
- return name.startsWith("overlay:") || name === "Transformer";
67
- }
68
- function resolveExportParams(page, opts) {
69
- const includeBleed = opts.preset !== void 0 ? opts.preset.includeBleed : opts.includeBleed ?? false;
70
- const cropRect = bleedAwareExportBounds(page, includeBleed);
71
- const pixelRatio = opts.preset !== void 0 ? scaleForPreset(opts.preset, cropRect) : opts.pixelRatio ?? 1;
72
- const format = opts.preset !== void 0 ? opts.preset.format : opts.format;
73
- const mimeType = format === "jpeg" ? "image/jpeg" : "image/png";
74
- return {
75
- cropRect,
76
- pixelRatio,
77
- mimeType,
78
- quality: format === "jpeg" ? 0.92 : void 0
79
- };
80
- }
81
- function identifyTaintedSrc(imageSrcs) {
82
- for (const { src } of imageSrcs) {
83
- if (isCrossOriginSrc(src)) return src;
84
- }
85
- return null;
86
- }
87
- function isCrossOriginSrc(src) {
88
- return /^https?:\/\//i.test(src);
89
- }
90
- function documentCropToStageCoords(cropRect, stageScale, stageX, stageY) {
91
- return {
92
- x: stageX + cropRect.x * stageScale,
93
- y: stageY + cropRect.y * stageScale,
94
- width: cropRect.width * stageScale,
95
- height: cropRect.height * stageScale
96
- };
97
- }
98
-
99
- // src/design-canvas-react/export.ts
100
- async function exportPageDataUrl(stage, page, opts) {
101
- const { cropRect, pixelRatio, mimeType, quality } = resolveExportParams(page, opts);
102
- const imageSrcs = collectImageSrcs(stage);
103
- const hiddenNodes = [];
104
- for (const layer of stage.getLayers()) {
105
- for (const node of layer.getChildren()) {
106
- const name = node.name();
107
- if (isExportHiddenNodeName(name) && node.visible()) {
108
- node.visible(false);
109
- hiddenNodes.push(node);
110
- }
111
- }
112
- }
113
- const stageScale = stage.scaleX();
114
- const stageCrop = documentCropToStageCoords(cropRect, stageScale, stage.x(), stage.y());
115
- let dataUrl;
116
- try {
117
- dataUrl = stage.toDataURL({
118
- mimeType,
119
- ...quality !== void 0 ? { quality } : {},
120
- pixelRatio,
121
- x: stageCrop.x,
122
- y: stageCrop.y,
123
- width: stageCrop.width,
124
- height: stageCrop.height
125
- });
126
- } catch (err) {
127
- for (const node of hiddenNodes) {
128
- node.visible(true);
129
- }
130
- if (err instanceof Error && err.name === "SecurityError") {
131
- const taintedSrc = identifyTaintedSrc(imageSrcs);
132
- if (taintedSrc !== null) {
133
- throw new Error(
134
- `Export failed: image source is CORS-tainted and cannot be read by the canvas. Offending src: "${taintedSrc}". Ensure the image is served with Access-Control-Allow-Origin or use a proxied /api/ path.`
135
- );
136
- }
137
- throw new Error(
138
- `Export failed: a canvas SecurityError occurred but no cross-origin image src could be identified. The stage may contain a tainted video or image loaded without CORS headers.`
139
- );
140
- }
141
- throw err;
142
- }
143
- for (const node of hiddenNodes) {
144
- node.visible(true);
145
- }
146
- return dataUrl;
147
- }
148
- function collectImageSrcs(stage) {
149
- const result = [];
150
- for (const layer of stage.getLayers()) {
151
- for (const node of layer.getChildren()) {
152
- const src = node.getAttr("src");
153
- if (typeof src === "string" && src.length > 0) {
154
- result.push({ name: node.name(), src });
155
- }
156
- }
157
- }
158
- return result;
159
- }
160
- function exportDocumentJson(document) {
161
- if (document.schemaVersion !== SCENE_SCHEMA_VERSION) {
162
- throw new Error(
163
- `exportDocumentJson: document schemaVersion is ${document.schemaVersion}, expected ${SCENE_SCHEMA_VERSION} \u2014 upgrade the document before exporting`
164
- );
165
- }
166
- return JSON.stringify(document, null, 2);
167
- }
168
- function downloadDataUrl(dataUrl, filename) {
169
- if (typeof globalThis.document === "undefined") return;
170
- const a = globalThis.document.createElement("a");
171
- a.href = dataUrl;
172
- a.download = filename;
173
- a.style.display = "none";
174
- globalThis.document.body.appendChild(a);
175
- a.click();
176
- globalThis.document.body.removeChild(a);
177
- }
178
-
179
73
  // src/design-canvas-react/insert-builders.ts
180
74
  var MAX_INSERT_DIMENSION = 600;
181
75
  function mintElementId() {
@@ -329,6 +223,44 @@ function SparkleGlyph({ className }) {
329
223
  function SpinnerGlyph({ className }) {
330
224
  return /* @__PURE__ */ jsx("svg", { className: `${className ?? ""} animate-spin`, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.2-8.6", strokeLinecap: "round" }) });
331
225
  }
226
+ var PREVIEW_PROBE_PAGE = { pageId: "preview", width: 1e3, height: 1e3 };
227
+ var HEADING_FONT_SIZE = 32;
228
+ function templateShape(tpl) {
229
+ try {
230
+ const ops = tpl.build(PREVIEW_PROBE_PAGE);
231
+ const added = ops.find((op) => op.type === "add_element");
232
+ if (!added || added.type !== "add_element") return "other";
233
+ const el = added.element;
234
+ if (el.kind === "rect") return "rect";
235
+ if (el.kind === "ellipse") return "ellipse";
236
+ if (el.kind === "text") {
237
+ const isHeading = el.fontStyle.includes("bold") || el.fontSize >= HEADING_FONT_SIZE;
238
+ return isHeading ? "heading" : "body";
239
+ }
240
+ return "other";
241
+ } catch {
242
+ return "other";
243
+ }
244
+ }
245
+ function TemplatePreview({ shape }) {
246
+ if (shape === "rect") {
247
+ return /* @__PURE__ */ jsx("span", { className: "block h-6 w-9 rounded-sm bg-[var(--brand-primary)]", "aria-hidden": true });
248
+ }
249
+ if (shape === "ellipse") {
250
+ return /* @__PURE__ */ jsx("span", { className: "block h-7 w-7 rounded-full bg-[var(--brand-primary)]", "aria-hidden": true });
251
+ }
252
+ if (shape === "heading" || shape === "body") {
253
+ return /* @__PURE__ */ jsx(
254
+ "span",
255
+ {
256
+ className: `block leading-none text-[var(--text-primary)] ${shape === "heading" ? "text-2xl font-bold" : "text-lg"}`,
257
+ "aria-hidden": true,
258
+ children: shape === "heading" ? "T" : "\xB6"
259
+ }
260
+ );
261
+ }
262
+ return /* @__PURE__ */ jsx(ShapesGlyph, { className: "h-6 w-6 text-[var(--text-muted)]" });
263
+ }
332
264
  function CanvasInsertPanel({
333
265
  canWrite,
334
266
  page,
@@ -402,7 +334,7 @@ function CanvasInsertPanel({
402
334
  }
403
335
  const tabs = [
404
336
  { id: "uploads", label: "Uploads", icon: ImageGlyph, show: true },
405
- { id: "templates", label: "Templates", icon: TextGlyph, show: templates.length > 0 },
337
+ { id: "templates", label: "Templates", icon: ShapesGlyph, show: templates.length > 0 },
406
338
  { id: "generations", label: "Generations", icon: SparkleGlyph, show: !!loadGenerations }
407
339
  ];
408
340
  const visibleTabs = tabs.filter((t) => t.show);
@@ -461,14 +393,17 @@ function CanvasInsertPanel({
461
393
  }
462
394
  )
463
395
  ] }),
464
- tab === "templates" && /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-2", children: templates.map((tpl) => /* @__PURE__ */ jsx(
396
+ tab === "templates" && /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-2", children: templates.map((tpl) => /* @__PURE__ */ jsxs(
465
397
  "button",
466
398
  {
467
399
  type: "button",
468
400
  disabled: busy,
469
401
  onClick: () => void runInsert(() => tpl.build(page)),
470
402
  className: "flex h-20 flex-col items-center justify-center gap-1.5 rounded-md border border-[var(--border-default)] bg-[var(--bg-input)] text-xs font-medium text-[var(--text-primary)] transition-colors hover:border-[var(--brand-primary)]/40 disabled:opacity-50",
471
- children: /* @__PURE__ */ jsx("span", { children: tpl.label })
403
+ children: [
404
+ /* @__PURE__ */ jsx("span", { className: "flex h-7 items-center justify-center", children: /* @__PURE__ */ jsx(TemplatePreview, { shape: templateShape(tpl) }) }),
405
+ /* @__PURE__ */ jsx("span", { children: tpl.label })
406
+ ]
472
407
  },
473
408
  tpl.id
474
409
  )) }),
@@ -677,10 +612,10 @@ function LayersPanel({ page, selectedElementIds, canWrite, onSetAttrs, onReorder
677
612
  // src/design-canvas-react/lazy.tsx
678
613
  import { lazy } from "react";
679
614
  var DesignCanvasLazy = lazy(
680
- () => import("../DesignCanvasEditor-C5SOZHF5.js").then((m) => ({ default: m.DesignCanvasEditor }))
615
+ () => import("../DesignCanvasEditor-YLCO7I5I.js").then((m) => ({ default: m.DesignCanvasEditor }))
681
616
  );
682
617
  var DesignCanvasChromeLazy = lazy(
683
- () => import("../DesignCanvas-JTSAL6KX.js").then((m) => ({ default: m.DesignCanvas }))
618
+ () => import("../DesignCanvas-MA5EH324.js").then((m) => ({ default: m.DesignCanvas }))
684
619
  );
685
620
  export {
686
621
  CanvasInsertPanel,
@@ -691,6 +626,7 @@ export {
691
626
  DesignCanvasEditor,
692
627
  DesignCanvasLazy,
693
628
  ElementNode,
629
+ ExportControl,
694
630
  GridLayer,
695
631
  LAYERS_PANEL_ROW_LIMIT,
696
632
  LayersPanel,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/design-canvas-react/export-math.ts","../../src/design-canvas-react/export.ts","../../src/design-canvas-react/insert-builders.ts","../../src/design-canvas-react/components/CanvasInsertPanel.tsx","../../src/design-canvas-react/components/LayersPanel.tsx","../../src/design-canvas-react/components/layer-tree.ts","../../src/design-canvas-react/lazy.tsx"],"sourcesContent":["/**\n * Pure export math for the design-canvas surface — no Konva, no DOM, no React.\n *\n * All stage-interaction code (save/restore, node visibility, toDataURL) lives\n * in export.ts and delegates the decision math here so it can be tested\n * without a canvas environment.\n */\n\nimport type { ScenePage } from '../design-canvas/model'\nimport {\n bleedAwareExportBounds,\n scaleForPreset,\n type ExportCropRect,\n type ExportPreset,\n} from '../design-canvas/export-presets'\n\n// ---------------------------------------------------------------------------\n// Re-exports so callers only import from one place\n// ---------------------------------------------------------------------------\n\nexport type { ExportCropRect, ExportPreset }\nexport { bleedAwareExportBounds, scaleForPreset }\n\n// ---------------------------------------------------------------------------\n// Overlay / transformer node name predicates\n// ---------------------------------------------------------------------------\n\n/** Returns true for any Konva node that must be hidden during export. Nodes\n * whose names start with 'overlay:' are editor-only chrome (snap lines,\n * selection indicators, rulers, bleed guides). The transformer node is\n * identified by its canonical name 'Transformer'. */\nexport function isExportHiddenNodeName(name: string): boolean {\n return name.startsWith('overlay:') || name === 'Transformer'\n}\n\n// ---------------------------------------------------------------------------\n// Crop rect + pixel ratio resolution\n// ---------------------------------------------------------------------------\n\nexport interface ResolvedExportParams {\n cropRect: ExportCropRect\n pixelRatio: number\n mimeType: 'image/png' | 'image/jpeg'\n quality: number | undefined\n}\n\n/**\n * Resolve the full set of toDataURL params from a page, format, pixel ratio,\n * bleed flag, and optional preset. When a preset is supplied it overrides\n * `pixelRatio` and `includeBleed` — the explicit args are ignored for those\n * two values so the preset is the single source of truth.\n *\n * `quality` is set to 0.92 for jpeg, undefined for png (Konva ignores it).\n */\nexport function resolveExportParams(\n page: ScenePage,\n opts: {\n format: 'png' | 'jpeg'\n pixelRatio?: number\n includeBleed?: boolean\n preset?: ExportPreset\n },\n): ResolvedExportParams {\n const includeBleed = opts.preset !== undefined\n ? opts.preset.includeBleed\n : (opts.includeBleed ?? false)\n\n const cropRect = bleedAwareExportBounds(page, includeBleed)\n\n const pixelRatio = opts.preset !== undefined\n ? scaleForPreset(opts.preset, cropRect)\n : (opts.pixelRatio ?? 1)\n\n const format = opts.preset !== undefined ? opts.preset.format : opts.format\n const mimeType: 'image/png' | 'image/jpeg' = format === 'jpeg' ? 'image/jpeg' : 'image/png'\n\n return {\n cropRect,\n pixelRatio,\n mimeType,\n quality: format === 'jpeg' ? 0.92 : undefined,\n }\n}\n\n// ---------------------------------------------------------------------------\n// CORS taint detection\n// ---------------------------------------------------------------------------\n\n/**\n * Walk image nodes to find the src of any that may have caused a SecurityError\n * when the stage called toDataURL. Returns the first offending src found, or\n * null if none can be identified.\n *\n * `imageSrcs` is a flat list of { name, src } records collected from all image\n * nodes on the stage before calling toDataURL — export.ts builds it before\n * attempting the export so that if the SecurityError fires we have the data.\n */\nexport function identifyTaintedSrc(imageSrcs: ReadonlyArray<{ name: string; src: string }>): string | null {\n // We cannot re-probe CORS from a caught SecurityError — the browser does not\n // tell us which src triggered it. Return the first src that looks like a\n // cross-origin resource the browser would taint: anything that is not a\n // same-origin relative path or a data: URL.\n for (const { src } of imageSrcs) {\n if (isCrossOriginSrc(src)) return src\n }\n return null\n}\n\n/**\n * Heuristic: a src is potentially cross-origin when it is an absolute http(s)\n * URL. Same-origin relative paths (/api/...) and data: blobs are safe.\n * This is intentionally conservative — the taint check runs only after a\n * SecurityError fires, so a false positive is \"blamed\" without being silently\n * ignored. NOTE: same-origin https:// assets will also match; if a\n * SecurityError fires, the cross-origin culprit may be a different image than\n * the one this function flags first.\n */\nexport function isCrossOriginSrc(src: string): boolean {\n return /^https?:\\/\\//i.test(src)\n}\n\n// ---------------------------------------------------------------------------\n// View state snapshot (used by save/restore helpers)\n// ---------------------------------------------------------------------------\n\n/** Minimal snapshot of stage view state that export.ts saves before mutating\n * zoom/pan/visibility and restores afterward. */\nexport interface ExportViewSnapshot {\n /** Stage scale factor before export. */\n scaleX: number\n scaleY: number\n /** Stage translation before export. */\n x: number\n y: number\n /** node name → prior visibility, for the nodes that were hidden. */\n hiddenNodeNames: string[]\n}\n\n/**\n * Build the toDataURL crop rect translated to stage output coordinates.\n * Konva's `stage.toDataURL({ x, y, width, height })` takes STAGE OUTPUT\n * coordinates, not document coordinates. When the stage has been scaled to fit\n * (zoom × stageScale), the crop rect in document px must be multiplied by the\n * combined scale to land on the right pixels.\n *\n * `stageScale` is the stage's current uniform scale (scaleX == scaleY);\n * `pixelRatio` is separate and handed directly to toDataURL — Konva multiplies\n * it internally when rendering to the backing canvas, so we must NOT include\n * it in the stage-coordinate rect.\n */\nexport function documentCropToStageCoords(\n cropRect: ExportCropRect,\n stageScale: number,\n stageX: number,\n stageY: number,\n): { x: number; y: number; width: number; height: number } {\n return {\n x: stageX + cropRect.x * stageScale,\n y: stageY + cropRect.y * stageScale,\n width: cropRect.width * stageScale,\n height: cropRect.height * stageScale,\n }\n}\n","/**\n * Client-side export for design-canvas pages: raster (PNG/JPEG via Konva\n * stage.toDataURL) and JSON (document serialisation).\n *\n * The heavy lifting — crop math, pixel ratio resolution, CORS taint detection\n * — lives in export-math.ts where it can be unit-tested without a canvas\n * context. This file is the thin Konva-wiring layer: hide overlays, call\n * toDataURL, restore, handle the SecurityError.\n *\n * Konva is an OPTIONAL peer. Import this file only from browser-context code\n * that has konva wired in. The types are written against a minimal structural\n * interface so the file compiles even when konva's types are absent.\n */\n\nimport type { SceneDocument, ScenePage } from '../design-canvas/model'\nimport { SCENE_SCHEMA_VERSION } from '../design-canvas/model'\nimport type { ExportPreset } from '../design-canvas/export-presets'\nimport {\n documentCropToStageCoords,\n identifyTaintedSrc,\n isExportHiddenNodeName,\n resolveExportParams,\n} from './export-math'\n\n// ---------------------------------------------------------------------------\n// Minimal structural interfaces for Konva stage / node\n// (avoids a hard konva import; the real Konva types satisfy these)\n// ---------------------------------------------------------------------------\n\ninterface KonvaNodeLike {\n name(): string\n visible(): boolean\n visible(v: boolean): void\n getAttr(key: string): unknown\n}\n\ninterface KonvaLayerLike {\n getChildren(): KonvaNodeLike[]\n}\n\ninterface KonvaStageLike {\n scaleX(): number\n scaleY(): number\n x(): number\n y(): number\n getLayers(): KonvaLayerLike[]\n toDataURL(params: {\n mimeType: string\n quality?: number\n pixelRatio: number\n x: number\n y: number\n width: number\n height: number\n }): string\n}\n\n// ---------------------------------------------------------------------------\n// Raster export\n// ---------------------------------------------------------------------------\n\nexport interface ExportPageDataUrlOptions {\n format: 'png' | 'jpeg'\n pixelRatio?: number\n includeBleed?: boolean\n preset?: ExportPreset\n}\n\n/**\n * Render a single page to a data URL.\n *\n * The function temporarily hides every node whose name starts with 'overlay:'\n * plus any Transformer node, computes the crop rect and pixel ratio from the\n * page model and options, calls stage.toDataURL, then restores all prior view\n * state exactly — zoom, pan, and node visibility.\n *\n * Rejects with a descriptive error when a CORS-tainted image source causes\n * the SecurityError, naming the offending src so the caller can surface it.\n *\n * The `stage` argument must be the Konva stage with the page content already\n * rendered. The caller is responsible for ensuring all async image loads have\n * settled before calling this function.\n */\nexport async function exportPageDataUrl(\n stage: KonvaStageLike,\n page: ScenePage,\n opts: ExportPageDataUrlOptions,\n): Promise<string> {\n const { cropRect, pixelRatio, mimeType, quality } = resolveExportParams(page, opts)\n\n // Collect all image node srcs BEFORE hiding anything, so the taint detector\n // has data even if the SecurityError fires mid-render.\n const imageSrcs = collectImageSrcs(stage)\n\n // Save state: which nodes we are hiding (visibility that was true → set false).\n const hiddenNodes: KonvaNodeLike[] = []\n\n for (const layer of stage.getLayers()) {\n for (const node of layer.getChildren()) {\n const name = node.name()\n if (isExportHiddenNodeName(name) && node.visible()) {\n node.visible(false)\n hiddenNodes.push(node)\n }\n }\n }\n\n // Compute the stage-coordinate crop rect for toDataURL.\n const stageScale = stage.scaleX()\n const stageCrop = documentCropToStageCoords(cropRect, stageScale, stage.x(), stage.y())\n\n let dataUrl: string\n try {\n dataUrl = stage.toDataURL({\n mimeType,\n ...(quality !== undefined ? { quality } : {}),\n pixelRatio,\n x: stageCrop.x,\n y: stageCrop.y,\n width: stageCrop.width,\n height: stageCrop.height,\n })\n } catch (err) {\n // Restore visibility before rethrowing so the editor is not left in a\n // broken state.\n for (const node of hiddenNodes) {\n node.visible(true)\n }\n\n if (err instanceof Error && err.name === 'SecurityError') {\n const taintedSrc = identifyTaintedSrc(imageSrcs)\n if (taintedSrc !== null) {\n throw new Error(\n `Export failed: image source is CORS-tainted and cannot be read by the canvas. ` +\n `Offending src: \"${taintedSrc}\". ` +\n `Ensure the image is served with Access-Control-Allow-Origin or use a proxied /api/ path.`,\n )\n }\n throw new Error(\n `Export failed: a canvas SecurityError occurred but no cross-origin image src could be identified. ` +\n `The stage may contain a tainted video or image loaded without CORS headers.`,\n )\n }\n throw err\n }\n\n // Restore visibility.\n for (const node of hiddenNodes) {\n node.visible(true)\n }\n\n return dataUrl\n}\n\n/** Collect all image-like nodes from the stage for taint identification. */\nfunction collectImageSrcs(stage: KonvaStageLike): Array<{ name: string; src: string }> {\n const result: Array<{ name: string; src: string }> = []\n for (const layer of stage.getLayers()) {\n for (const node of layer.getChildren()) {\n const src = node.getAttr('src')\n if (typeof src === 'string' && src.length > 0) {\n result.push({ name: node.name(), src })\n }\n }\n }\n return result\n}\n\n// ---------------------------------------------------------------------------\n// JSON export\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize a scene document to pretty-printed JSON with schemaVersion\n * asserted. Throws when the document's schemaVersion does not match\n * SCENE_SCHEMA_VERSION — the caller must not smuggle stale documents through.\n */\nexport function exportDocumentJson(document: SceneDocument): string {\n if (document.schemaVersion !== SCENE_SCHEMA_VERSION) {\n throw new Error(\n `exportDocumentJson: document schemaVersion is ${document.schemaVersion}, ` +\n `expected ${SCENE_SCHEMA_VERSION} — upgrade the document before exporting`,\n )\n }\n return JSON.stringify(document, null, 2)\n}\n\n// ---------------------------------------------------------------------------\n// Browser download helper\n// ---------------------------------------------------------------------------\n\n/**\n * Trigger a browser download for a data URL. Safe to import in SSR — the\n * function is a no-op when `document` is not defined (e.g. server-side render\n * or test environment without a DOM). The integrator must not rely on the\n * download executing in those contexts.\n */\nexport function downloadDataUrl(dataUrl: string, filename: string): void {\n if (typeof globalThis.document === 'undefined') return\n\n const a = globalThis.document.createElement('a')\n a.href = dataUrl\n a.download = filename\n a.style.display = 'none'\n globalThis.document.body.appendChild(a)\n a.click()\n globalThis.document.body.removeChild(a)\n}\n","/**\n * Pure builders for the human \"add element to the canvas\" path — the math and\n * `SceneOperation` construction behind {@link CanvasInsertPanel}, separated from\n * the React/DOM layer so every insert is unit-testable without Konva or a\n * browser. The panel calls these to produce `add_element` operations and hands\n * them to the host's `onApplyOperations` (server-validated, undoable) — the same\n * pipeline every other edit flows through.\n *\n * Media boundary: image inserts must pass {@link assertSceneMediaSrc} (remote\n * http(s) or a rooted `/api/` path — never `data:` blobs or sandbox-local\n * files). Builders that take a src assert it up front so a bad url fails here,\n * not deep in the apply layer.\n */\n\nimport { assertSceneMediaSrc } from '../design-canvas/model'\nimport type {\n EllipseElement,\n ImageElement,\n RectElement,\n SceneElement,\n TextElement,\n} from '../design-canvas/model'\nimport type { SceneOperation } from '../design-canvas/operations'\n\n/** Largest dimension (document px) a freshly inserted element is fitted to,\n * before the page-size cap applies. Keeps a huge upload from filling the page. */\nexport const MAX_INSERT_DIMENSION = 600\n\n/** Active page geometry an insert lands into — drives centering and fitting. */\nexport interface InsertPageGeometry {\n pageId: string\n width: number\n height: number\n}\n\n/** Mint a DOM-safe element id. Prefers `crypto.randomUUID`; falls back to a\n * time+random id where crypto is unavailable (older runtimes, some test envs). */\nexport function mintElementId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n return `el-${Date.now()}-${Math.random().toString(36).slice(2)}`\n}\n\n/** Fit (naturalW, naturalH) under a max-dimension cap (further bounded by the\n * page), preserving aspect ratio. Never upscales past natural size. */\nexport function fittedSize(\n naturalW: number,\n naturalH: number,\n pageWidth: number,\n pageHeight: number,\n): { width: number; height: number } {\n const cap = Math.min(MAX_INSERT_DIMENSION, pageWidth * 0.8, pageHeight * 0.8)\n const longest = Math.max(naturalW, naturalH)\n const scale = naturalW > 0 && naturalH > 0 ? Math.min(1, cap / longest) : 1\n const width = Math.max(1, Math.round((naturalW || cap) * scale))\n const height = Math.max(1, Math.round((naturalH || cap) * scale))\n return { width, height }\n}\n\n/** Top-left position that centers a (width, height) box on the page. */\nexport function centeredPosition(\n width: number,\n height: number,\n pageWidth: number,\n pageHeight: number,\n): { x: number; y: number } {\n return {\n x: Math.round((pageWidth - width) / 2),\n y: Math.round((pageHeight - height) / 2),\n }\n}\n\n/** Common element fields with a freshly minted id and unrotated/visible defaults. */\nfunction baseAttrs(name: string, x: number, y: number) {\n return { id: mintElementId(), name, x, y, rotation: 0, opacity: 1, locked: false, visible: true }\n}\n\n/** Wrap a single element in an `add_element` op for the active page. */\nfunction addElementOp(pageId: string, element: SceneElement): SceneOperation {\n return { type: 'add_element', pageId, element }\n}\n\n/**\n * Build an `add_element` op placing an image, fitted and centered on the page.\n * `naturalSize` is the probed image dimensions (the panel reads them in the\n * browser); pass `{ width: 0, height: 0 }` when unknown to fall back to the cap.\n *\n * Throws (via `assertSceneMediaSrc`) when `src` is not http(s) or a rooted\n * `/api/` path — callers should surface the error, never insert a `data:` src.\n */\nexport function buildInsertImageOp(\n src: string,\n naturalSize: { width: number; height: number },\n page: InsertPageGeometry,\n): SceneOperation {\n assertSceneMediaSrc(src, 'image src')\n const { width, height } = fittedSize(naturalSize.width, naturalSize.height, page.width, page.height)\n const { x, y } = centeredPosition(width, height, page.width, page.height)\n const element: ImageElement = {\n ...baseAttrs('Image', x, y),\n kind: 'image',\n width,\n height,\n src,\n fit: 'contain',\n }\n return addElementOp(page.pageId, element)\n}\n\n/** A template the insert panel can drop without the agent. `build` is pure and\n * produces the operations for the active page geometry. */\nexport interface InsertTemplate {\n id: string\n label: string\n build(page: InsertPageGeometry): SceneOperation[]\n}\n\n/** The built-in starter templates (heading, body text, rectangle, ellipse).\n * Consumers can pass their own list to {@link CanvasInsertPanel}; this is the\n * default so every canvas gets a usable set out of the box. */\nexport const DEFAULT_INSERT_TEMPLATES: readonly InsertTemplate[] = [\n {\n id: 'heading',\n label: 'Heading',\n build(page) {\n const width = Math.min(480, Math.round(page.width * 0.7))\n const { x, y } = centeredPosition(width, 60, page.width, page.height)\n const element: TextElement = {\n ...baseAttrs('Heading', x, y),\n kind: 'text',\n text: 'Add a headline',\n width,\n fontFamily: 'Inter',\n fontSize: 48,\n fontStyle: 'bold',\n fill: '#111827',\n align: 'left',\n lineHeight: 1.1,\n letterSpacing: 0,\n }\n return [addElementOp(page.pageId, element)]\n },\n },\n {\n id: 'body',\n label: 'Body text',\n build(page) {\n const width = Math.min(420, Math.round(page.width * 0.6))\n const { x, y } = centeredPosition(width, 80, page.width, page.height)\n const element: TextElement = {\n ...baseAttrs('Body', x, y),\n kind: 'text',\n text: 'Add a paragraph of supporting copy.',\n width,\n fontFamily: 'Inter',\n fontSize: 20,\n fontStyle: 'normal',\n fill: '#374151',\n align: 'left',\n lineHeight: 1.4,\n letterSpacing: 0,\n }\n return [addElementOp(page.pageId, element)]\n },\n },\n {\n id: 'rect',\n label: 'Rectangle',\n build(page) {\n const width = Math.min(320, Math.round(page.width * 0.4))\n const height = Math.min(200, Math.round(page.height * 0.3))\n const { x, y } = centeredPosition(width, height, page.width, page.height)\n const element: RectElement = {\n ...baseAttrs('Rectangle', x, y),\n kind: 'rect',\n width,\n height,\n fill: '#6366f1',\n cornerRadius: 12,\n }\n return [addElementOp(page.pageId, element)]\n },\n },\n {\n id: 'ellipse',\n label: 'Ellipse',\n build(page) {\n const size = Math.min(220, Math.round(Math.min(page.width, page.height) * 0.3))\n const { x, y } = centeredPosition(size, size, page.width, page.height)\n const element: EllipseElement = {\n ...baseAttrs('Ellipse', x, y),\n kind: 'ellipse',\n width: size,\n height: size,\n fill: '#10b981',\n }\n return [addElementOp(page.pageId, element)]\n },\n },\n]\n","/**\n * The human \"add element to the canvas\" side panel — a reusable left rail any\n * canvas consumer can mount so a person (not just the agent) can put images,\n * shapes, and text on the canvas. Designed for the `renderSidePanel` slot of\n * {@link DesignCanvasProps}; pass `renderSidePanel={(ctx) => <CanvasInsertPanel … />}`.\n *\n * Endpoints are per-app, so every I/O path is a callback — no product route is\n * hardcoded:\n * - `onUploadImage(file) => Promise<url>` — the host stores the file and returns\n * the src to insert. The url MUST be http(s) or a rooted `/api/` path\n * (enforced by `assertSceneMediaSrc` before insertion); a `data:` url is\n * rejected, matching the scene model's media boundary.\n * - `loadGenerations?()` — optional provider for \"already generated in this\n * workspace\" images; omit to hide the tab.\n * - `templates?` — optional template set; defaults to {@link DEFAULT_INSERT_TEMPLATES}.\n *\n * Insertion goes through `onInsert`, which the host wires to its\n * `onApplyOperations` pipeline (server-validated, undoable) — the same path\n * every other edit takes.\n *\n * Tokens/icons follow the canvas convention: CSS-var design tokens and inline\n * SVG glyphs, no icon-library or Tailwind-semantic-token dependency.\n */\n\nimport { useEffect, useRef, useState, type ReactElement } from 'react'\nimport type { SceneOperation } from '../../design-canvas/operations'\nimport { assertSceneMediaSrc } from '../../design-canvas/model'\nimport {\n DEFAULT_INSERT_TEMPLATES,\n buildInsertImageOp,\n type InsertPageGeometry,\n type InsertTemplate,\n} from '../insert-builders'\nimport { ImageGlyph, TextGlyph } from './glyphs'\n\n/** An already-generated image the panel can offer for one-click insertion. */\nexport interface InsertGeneration {\n id: string\n /** The image url to insert. Must satisfy the scene media boundary\n * (http(s) or rooted `/api/`); rejected otherwise at insert time. */\n url: string\n /** Optional prompt/label shown as the tile's title. */\n label?: string\n}\n\nexport interface CanvasInsertPanelProps {\n canWrite: boolean\n /** The active page new elements are added to. */\n page: InsertPageGeometry\n /** Submit operations through the host's apply pipeline. */\n onInsert(operations: SceneOperation[]): Promise<unknown>\n /** Store an uploaded file and return its src (http(s) or rooted `/api/`). */\n onUploadImage(file: File): Promise<string>\n /** Optional provider for the Generations tab; omit to hide it. */\n loadGenerations?(): Promise<InsertGeneration[]>\n /** Drop-in templates; defaults to the built-in starter set. */\n templates?: readonly InsertTemplate[]\n /** Accepted upload mime types. Default: PNG/JPEG/GIF/WebP. */\n accept?: string\n className?: string\n}\n\ntype Tab = 'uploads' | 'templates' | 'generations'\n\nconst DEFAULT_ACCEPT = 'image/png,image/jpeg,image/gif,image/webp'\n\n/** Read an image url's natural dimensions in the browser; resolves `{0,0}` when\n * Image is unavailable (SSR/tests), the load fails, or the probe times out —\n * builders fall back to the size cap, so a failed probe still inserts a sensibly\n * sized element rather than blocking insertion. */\nconst PROBE_TIMEOUT_MS = 4000\n\nfunction probeImageSize(url: string): Promise<{ width: number; height: number }> {\n return new Promise((resolve) => {\n if (typeof Image === 'undefined') return resolve({ width: 0, height: 0 })\n let settled = false\n const finish = (size: { width: number; height: number }) => {\n if (settled) return\n settled = true\n resolve(size)\n }\n const img = new Image()\n img.onload = () => finish({ width: img.naturalWidth, height: img.naturalHeight })\n img.onerror = () => finish({ width: 0, height: 0 })\n // A URL that never loads (or an environment that never fires load/error)\n // must not hang the insert forever.\n setTimeout(() => finish({ width: 0, height: 0 }), PROBE_TIMEOUT_MS)\n img.src = url\n })\n}\n\nfunction UploadGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 9l5-5 5 5M12 4v12\" />\n </svg>\n )\n}\n\nfunction SparkleGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1\" />\n </svg>\n )\n}\n\nfunction SpinnerGlyph({ className }: { className?: string }) {\n return (\n <svg className={`${className ?? ''} animate-spin`} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden>\n <path d=\"M21 12a9 9 0 1 1-6.2-8.6\" strokeLinecap=\"round\" />\n </svg>\n )\n}\n\nexport function CanvasInsertPanel({\n canWrite,\n page,\n onInsert,\n onUploadImage,\n loadGenerations,\n templates = DEFAULT_INSERT_TEMPLATES,\n accept = DEFAULT_ACCEPT,\n className,\n}: CanvasInsertPanelProps) {\n const [tab, setTab] = useState<Tab>('uploads')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState('')\n const [dragOver, setDragOver] = useState(false)\n const [generations, setGenerations] = useState<InsertGeneration[]>([])\n const [generationsLoaded, setGenerationsLoaded] = useState(false)\n const fileInputRef = useRef<HTMLInputElement | null>(null)\n\n useEffect(() => {\n if (tab !== 'generations' || generationsLoaded || !loadGenerations) return\n let cancelled = false\n void (async () => {\n try {\n const rows = await loadGenerations()\n if (!cancelled) setGenerations(rows.filter((g) => g.url))\n } catch (err) {\n if (!cancelled) setError(err instanceof Error ? err.message : 'Could not load generations')\n } finally {\n if (!cancelled) setGenerationsLoaded(true)\n }\n })()\n return () => {\n cancelled = true\n }\n }, [tab, generationsLoaded, loadGenerations])\n\n async function insertImageFromUrl(url: string) {\n // Validate the media boundary BEFORE probing — a data: or sandbox-local url\n // throws here (surfaced as an inline error) without a wasted image load.\n assertSceneMediaSrc(url, 'image src')\n const natural = await probeImageSize(url)\n await onInsert([buildInsertImageOp(url, natural, page)])\n }\n\n async function handleFiles(files: FileList | File[]) {\n if (!canWrite || busy) return\n const list = Array.from(files).filter((f) => f.type.startsWith('image/'))\n if (list.length === 0) {\n setError('Only image files can be added to the canvas')\n return\n }\n setBusy(true)\n setError('')\n try {\n for (const file of list) {\n const url = await onUploadImage(file)\n await insertImageFromUrl(url)\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Upload failed')\n } finally {\n setBusy(false)\n }\n }\n\n async function runInsert(build: () => SceneOperation[] | Promise<SceneOperation[]>) {\n if (!canWrite || busy) return\n setBusy(true)\n setError('')\n try {\n await onInsert(await build())\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Insert failed')\n } finally {\n setBusy(false)\n }\n }\n\n const tabs: Array<{ id: Tab; label: string; icon: (p: { className?: string }) => ReactElement; show: boolean }> = [\n { id: 'uploads', label: 'Uploads', icon: ImageGlyph, show: true },\n { id: 'templates', label: 'Templates', icon: TextGlyph, show: templates.length > 0 },\n { id: 'generations', label: 'Generations', icon: SparkleGlyph, show: !!loadGenerations },\n ]\n const visibleTabs = tabs.filter((t) => t.show)\n\n return (\n <div className={`flex h-full min-h-0 flex-col bg-[var(--bg-input)] text-[var(--text-primary)] ${className ?? ''}`}>\n <div className=\"flex shrink-0 border-b border-[var(--border-default)]\">\n {visibleTabs.map(({ id, label, icon: Icon }) => (\n <button\n key={id}\n type=\"button\"\n onClick={() => setTab(id)}\n className={`flex flex-1 items-center justify-center gap-1.5 px-2 py-2.5 text-xs font-medium transition-colors ${\n tab === id\n ? 'border-b-2 border-[var(--brand-primary)] text-[var(--text-primary)]'\n : 'text-[var(--text-muted)] hover:text-[var(--text-primary)]'\n }`}\n >\n <Icon className=\"h-3.5 w-3.5\" />\n {label}\n </button>\n ))}\n </div>\n\n {!canWrite ? (\n <div className=\"flex flex-1 items-center justify-center p-4 text-center text-sm text-[var(--text-muted)]\">\n You have view-only access to this design.\n </div>\n ) : (\n <div className=\"min-h-0 flex-1 overflow-y-auto p-3\">\n {tab === 'uploads' && (\n <div className=\"flex flex-col gap-3\">\n <button\n type=\"button\"\n disabled={busy}\n onClick={() => fileInputRef.current?.click()}\n onDragOver={(e) => {\n e.preventDefault()\n setDragOver(true)\n }}\n onDragLeave={() => setDragOver(false)}\n onDrop={(e) => {\n e.preventDefault()\n setDragOver(false)\n if (e.dataTransfer?.files?.length) void handleFiles(e.dataTransfer.files)\n }}\n className={`flex flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed px-4 py-8 text-center transition-colors disabled:opacity-50 ${\n dragOver\n ? 'border-[var(--brand-primary)] bg-[var(--brand-primary)]/5'\n : 'border-[var(--border-default)] hover:border-[var(--brand-primary)]/40'\n }`}\n >\n {busy ? (\n <SpinnerGlyph className=\"h-6 w-6 text-[var(--brand-primary)]\" />\n ) : (\n <UploadGlyph className=\"h-6 w-6 text-[var(--text-muted)]\" />\n )}\n <span className=\"text-sm font-medium text-[var(--text-primary)]\">\n {busy ? 'Uploading…' : 'Drop an image or click to upload'}\n </span>\n <span className=\"text-xs text-[var(--text-muted)]\">PNG, JPEG, GIF, or WebP</span>\n </button>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept={accept}\n multiple\n className=\"hidden\"\n onChange={(e) => {\n if (e.target.files?.length) void handleFiles(e.target.files)\n e.target.value = ''\n }}\n />\n </div>\n )}\n\n {tab === 'templates' && (\n <div className=\"grid grid-cols-2 gap-2\">\n {templates.map((tpl) => (\n <button\n key={tpl.id}\n type=\"button\"\n disabled={busy}\n onClick={() => void runInsert(() => tpl.build(page))}\n className=\"flex h-20 flex-col items-center justify-center gap-1.5 rounded-md border border-[var(--border-default)] bg-[var(--bg-input)] text-xs font-medium text-[var(--text-primary)] transition-colors hover:border-[var(--brand-primary)]/40 disabled:opacity-50\"\n >\n <span>{tpl.label}</span>\n </button>\n ))}\n </div>\n )}\n\n {tab === 'generations' && (\n <div className=\"flex flex-col gap-2\">\n {!generationsLoaded ? (\n <div className=\"flex items-center justify-center py-8 text-[var(--text-muted)]\">\n <SpinnerGlyph className=\"h-5 w-5\" />\n </div>\n ) : generations.length === 0 ? (\n <p className=\"px-1 py-6 text-center text-sm text-[var(--text-muted)]\">\n No generated images yet. Ask the agent to generate one.\n </p>\n ) : (\n <div className=\"grid grid-cols-2 gap-2\">\n {generations.map((gen) => (\n <button\n key={gen.id}\n type=\"button\"\n disabled={busy}\n onClick={() => {\n if (busy || !canWrite) return\n setBusy(true)\n setError('')\n insertImageFromUrl(gen.url)\n .catch((err) => setError(err instanceof Error ? err.message : 'Insert failed'))\n .finally(() => setBusy(false))\n }}\n title={gen.label}\n className=\"group relative aspect-square overflow-hidden rounded-md border border-[var(--border-default)] bg-[var(--bg-input)] transition-colors hover:border-[var(--brand-primary)] disabled:opacity-50\"\n >\n {/* eslint-disable-next-line jsx-a11y/img-redundant-alt */}\n <img src={gen.url} alt={gen.label || 'Generated image'} className=\"h-full w-full object-cover\" />\n </button>\n ))}\n </div>\n )}\n </div>\n )}\n\n {error ? <p className=\"mt-3 text-xs leading-5 text-[var(--text-danger,#dc2626)]\">{error}</p> : null}\n </div>\n )}\n </div>\n )\n}\n","/**\n * Layers panel — reverse-z list of the active page's elements. Highest z-index\n * is at the top. Groups show their children indented beneath them. Clicking\n * selects an element; meta-click adds to selection. Double-clicking the name\n * opens an inline rename input. Eye and lock icons toggle visibility/locked.\n * Rows can be drag-reordered within the list (siblings only; cross-group reorder\n * is a pending integrator concern). Capped at LAYERS_PANEL_ROW_LIMIT rows.\n */\n\nimport { useCallback, useMemo, useRef, useState } from 'react'\nimport type { SceneElement, ScenePage } from '../../design-canvas/model'\nimport { flattenLayerTree, LAYERS_PANEL_ROW_LIMIT } from './layer-tree'\nimport {\n EllipseGlyph,\n EyeGlyph,\n EyeOffGlyph,\n GroupGlyph,\n ImageGlyph,\n LineGlyph,\n LockGlyph,\n RectGlyph,\n SlotGlyph,\n TextGlyph,\n UnlockGlyph,\n VideoGlyph,\n} from './glyphs'\nimport type { SceneElementKind } from '../../design-canvas/model'\n\nfunction KindIcon({ kind, className }: { kind: SceneElementKind; className?: string }) {\n switch (kind) {\n case 'rect': return <RectGlyph className={className} />\n case 'ellipse': return <EllipseGlyph className={className} />\n case 'line': return <LineGlyph className={className} />\n case 'text': return <TextGlyph className={className} />\n case 'image': return <ImageGlyph className={className} />\n case 'video': return <VideoGlyph className={className} />\n case 'group': return <GroupGlyph className={className} />\n }\n}\n\nexport interface LayersPanelProps {\n page: ScenePage\n selectedElementIds: string[]\n canWrite: boolean\n /** Emit a set_attrs command for the given element. */\n onSetAttrs(elementId: string, attrs: Partial<Pick<SceneElement, 'name' | 'visible' | 'locked'>>): void\n /** Emit a reorder_element command. */\n onReorder(elementId: string, toIndex: number): void\n onSelect(elementId: string, additive: boolean): void\n}\n\nconst INDENT_PX = 16\n\nexport function LayersPanel({ page, selectedElementIds, canWrite, onSetAttrs, onReorder, onSelect }: LayersPanelProps) {\n const rows = useMemo(() => flattenLayerTree(page), [page])\n const [renamingId, setRenamingId] = useState<string | null>(null)\n const [renameValue, setRenameValue] = useState('')\n\n // Drag-reorder state: we track the dragged row's ownerIndex and the target\n // index within the SAME owner. Cross-owner reorder is not supported here.\n const dragRowRef = useRef<{ elementId: string; ownerIndex: number; ownerLength: number } | null>(null)\n const [dragOverIndex, setDragOverIndex] = useState<number | null>(null)\n\n function startRename(element: SceneElement) {\n setRenamingId(element.id)\n setRenameValue(element.name)\n }\n\n function commitRename(elementId: string) {\n const name = renameValue.trim()\n if (name.length > 0) onSetAttrs(elementId, { name })\n setRenamingId(null)\n }\n\n const visible = rows.length > LAYERS_PANEL_ROW_LIMIT ? rows.slice(0, LAYERS_PANEL_ROW_LIMIT) : rows\n\n return (\n <div className=\"flex h-full flex-col overflow-hidden text-[var(--text-primary)]\">\n <div className=\"shrink-0 border-b border-[var(--border-default)] px-3 py-1.5 text-[11px] font-medium uppercase tracking-wide text-[var(--text-muted)]\">\n Layers\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n {visible.map((row) => {\n const { element } = row\n const isSelected = selectedElementIds.includes(element.id)\n const isRenaming = renamingId === element.id\n\n return (\n <div\n key={element.id}\n data-layer-row={element.id}\n draggable={canWrite}\n onDragStart={() => {\n dragRowRef.current = {\n elementId: element.id,\n ownerIndex: row.ownerIndex,\n ownerLength: row.ownerLength,\n }\n }}\n onDragOver={(event) => {\n if (!dragRowRef.current) return\n // Only allow reorder within the same owner (same parentGroupId + depth)\n event.preventDefault()\n setDragOverIndex(row.ownerIndex)\n }}\n onDragLeave={() => setDragOverIndex(null)}\n onDrop={() => {\n const drag = dragRowRef.current\n if (!drag) return\n if (drag.elementId !== element.id) {\n onReorder(drag.elementId, row.ownerIndex)\n }\n dragRowRef.current = null\n setDragOverIndex(null)\n }}\n onDragEnd={() => {\n dragRowRef.current = null\n setDragOverIndex(null)\n }}\n onClick={(event) => onSelect(element.id, event.metaKey || event.ctrlKey)}\n className={[\n 'flex cursor-pointer items-center gap-1.5 py-1 pr-2 text-[13px] transition-colors',\n isSelected ? 'bg-[var(--brand-primary)]/15 text-[var(--text-primary)]' : 'hover:bg-[var(--border-default)]/40 text-[var(--text-secondary)]',\n dragOverIndex === row.ownerIndex ? 'border-t border-[var(--brand-primary)]' : '',\n ].join(' ')}\n style={{ paddingLeft: 8 + row.depth * INDENT_PX }}\n >\n <KindIcon kind={element.kind} className=\"h-3.5 w-3.5 shrink-0 opacity-60\" />\n\n {element.slot ? (\n <SlotGlyph className=\"h-3 w-3 shrink-0 text-[var(--brand-primary)]\" />\n ) : null}\n\n <span className=\"min-w-0 flex-1 truncate\">\n {isRenaming ? (\n <input\n autoFocus\n value={renameValue}\n onChange={(event) => setRenameValue(event.target.value)}\n onBlur={() => commitRename(element.id)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') commitRename(element.id)\n if (event.key === 'Escape') setRenamingId(null)\n event.stopPropagation()\n }}\n onClick={(event) => event.stopPropagation()}\n className=\"w-full rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-1 py-0 text-[13px] text-[var(--text-primary)] outline-none focus:border-[var(--brand-primary)]\"\n />\n ) : (\n <span\n onDoubleClick={(event) => {\n event.stopPropagation()\n if (canWrite) startRename(element)\n }}\n title={element.name}\n >\n {element.name}\n </span>\n )}\n </span>\n\n {/* Visibility toggle */}\n <button\n type=\"button\"\n aria-label={element.visible ? 'Hide element' : 'Show element'}\n onClick={(event) => {\n event.stopPropagation()\n if (canWrite) onSetAttrs(element.id, { visible: !element.visible })\n }}\n disabled={!canWrite}\n className=\"shrink-0 rounded p-0.5 opacity-0 transition-opacity hover:opacity-100 focus:opacity-100 group-hover:opacity-100 [.flex:hover_&]:opacity-60\"\n >\n {element.visible\n ? <EyeGlyph className=\"h-3.5 w-3.5\" />\n : <EyeOffGlyph className=\"h-3.5 w-3.5 text-[var(--text-muted)]\" />}\n </button>\n\n {/* Lock toggle */}\n <button\n type=\"button\"\n aria-label={element.locked ? 'Unlock element' : 'Lock element'}\n onClick={(event) => {\n event.stopPropagation()\n if (canWrite) onSetAttrs(element.id, { locked: !element.locked })\n }}\n disabled={!canWrite}\n className=\"shrink-0 rounded p-0.5 opacity-0 transition-opacity hover:opacity-100 focus:opacity-100 [.flex:hover_&]:opacity-60\"\n >\n {element.locked\n ? <LockGlyph className=\"h-3.5 w-3.5 text-amber-400\" />\n : <UnlockGlyph className=\"h-3.5 w-3.5\" />}\n </button>\n </div>\n )\n })}\n\n {rows.length > LAYERS_PANEL_ROW_LIMIT ? (\n <div className=\"px-3 py-2 text-[11px] text-[var(--text-muted)]\">\n +{rows.length - LAYERS_PANEL_ROW_LIMIT} more elements — select a group to scope the list\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n","/**\n * Pure layer-tree helpers: flatten a page's element array into an ordered list\n * for the LayersPanel, preserving group nesting. Extracted so render order,\n * depth, and selection math can be unit-tested independently of React.\n */\n\nimport type { SceneElement, ScenePage } from '../../design-canvas/model'\n\nexport interface LayerRow {\n element: SceneElement\n depth: number\n /** True when this row is a group whose children follow it in the list. */\n isGroup: boolean\n /** Index within its owner (page.elements or group.children). */\n ownerIndex: number\n /** Length of the owner array — needed for z-order bound math. */\n ownerLength: number\n /** Id of the containing group, or null for page-root elements. */\n parentGroupId: string | null\n}\n\n/**\n * Flatten `page.elements` into display order for the layers panel.\n *\n * Z-order is bottom→top in the element array; the layers panel shows\n * top→bottom (highest z-index first). A group row precedes its children\n * (the group is \"above\" its children in the panel, reflecting that the group\n * node itself paints last in Konva when empty; children paint inside it).\n *\n * Depth increases by 1 for each group level, starting at 0 for page-root\n * elements. Groups are expanded; collapsed state is a view concern the caller\n * tracks separately.\n */\nexport function flattenLayerTree(page: ScenePage): LayerRow[] {\n const rows: LayerRow[] = []\n\n function visit(elements: SceneElement[], depth: number, parentGroupId: string | null) {\n // Reverse so highest z-index appears at the top of the panel.\n for (let i = elements.length - 1; i >= 0; i -= 1) {\n const element = elements[i]!\n rows.push({\n element,\n depth,\n isGroup: element.kind === 'group',\n ownerIndex: i,\n ownerLength: elements.length,\n parentGroupId,\n })\n if (element.kind === 'group') {\n visit(element.children, depth + 1, element.id)\n }\n }\n }\n\n visit(page.elements, 0, null)\n return rows\n}\n\n/** The maximum number of rows the layers panel renders before truncating. */\nexport const LAYERS_PANEL_ROW_LIMIT = 500\n","/**\n * Lazy-loaded entry points for the design canvas editor. Products that do not\n * need the editor on initial load import `DesignCanvasLazy` (the batteries-\n * included composition) or `DesignCanvasChromeLazy` (raw chrome only) instead\n * of importing the components directly. Each chunk loads on first render; a\n * Suspense boundary in the product provides the fallback.\n *\n * `DesignCanvasLazy` — `DesignCanvasEditor`: chrome + workspace sharing one\n * command stack. This is what products mount.\n *\n * `DesignCanvasChromeLazy` — `DesignCanvas`: raw chrome with render-prop API.\n * Use when the product supplies its own `renderWorkspace` and `renderThumbnail`.\n */\n\nimport { lazy } from 'react'\nimport type { DesignCanvasFullProps } from './components/DesignCanvas'\nimport type { DesignCanvasProps } from './contracts'\n\nexport type { DesignCanvasFullProps }\nexport type { DesignCanvasProps }\n\n/** Batteries-included editor: chrome + workspace on one shared stack. */\nexport const DesignCanvasLazy = lazy(\n () =>\n import('./components/DesignCanvasEditor').then((m) => ({ default: m.DesignCanvasEditor })),\n)\n\n/** Raw chrome only — use when supplying a custom renderWorkspace/renderThumbnail. */\nexport const DesignCanvasChromeLazy = lazy(\n () =>\n import('./components/DesignCanvas').then((m) => ({ default: m.DesignCanvas })),\n)\n"],"mappings":"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","names":["useRef","useState","jsx","jsxs","useState","useRef"]}
1
+ {"version":3,"sources":["../../src/design-canvas-react/insert-builders.ts","../../src/design-canvas-react/components/CanvasInsertPanel.tsx","../../src/design-canvas-react/components/LayersPanel.tsx","../../src/design-canvas-react/components/layer-tree.ts","../../src/design-canvas-react/lazy.tsx"],"sourcesContent":["/**\n * Pure builders for the human \"add element to the canvas\" path — the math and\n * `SceneOperation` construction behind {@link CanvasInsertPanel}, separated from\n * the React/DOM layer so every insert is unit-testable without Konva or a\n * browser. The panel calls these to produce `add_element` operations and hands\n * them to the host's `onApplyOperations` (server-validated, undoable) — the same\n * pipeline every other edit flows through.\n *\n * Media boundary: image inserts must pass {@link assertSceneMediaSrc} (remote\n * http(s) or a rooted `/api/` path — never `data:` blobs or sandbox-local\n * files). Builders that take a src assert it up front so a bad url fails here,\n * not deep in the apply layer.\n */\n\nimport { assertSceneMediaSrc } from '../design-canvas/model'\nimport type {\n EllipseElement,\n ImageElement,\n RectElement,\n SceneElement,\n TextElement,\n} from '../design-canvas/model'\nimport type { SceneOperation } from '../design-canvas/operations'\n\n/** Largest dimension (document px) a freshly inserted element is fitted to,\n * before the page-size cap applies. Keeps a huge upload from filling the page. */\nexport const MAX_INSERT_DIMENSION = 600\n\n/** Active page geometry an insert lands into — drives centering and fitting. */\nexport interface InsertPageGeometry {\n pageId: string\n width: number\n height: number\n}\n\n/** Mint a DOM-safe element id. Prefers `crypto.randomUUID`; falls back to a\n * time+random id where crypto is unavailable (older runtimes, some test envs). */\nexport function mintElementId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n return `el-${Date.now()}-${Math.random().toString(36).slice(2)}`\n}\n\n/** Fit (naturalW, naturalH) under a max-dimension cap (further bounded by the\n * page), preserving aspect ratio. Never upscales past natural size. */\nexport function fittedSize(\n naturalW: number,\n naturalH: number,\n pageWidth: number,\n pageHeight: number,\n): { width: number; height: number } {\n const cap = Math.min(MAX_INSERT_DIMENSION, pageWidth * 0.8, pageHeight * 0.8)\n const longest = Math.max(naturalW, naturalH)\n const scale = naturalW > 0 && naturalH > 0 ? Math.min(1, cap / longest) : 1\n const width = Math.max(1, Math.round((naturalW || cap) * scale))\n const height = Math.max(1, Math.round((naturalH || cap) * scale))\n return { width, height }\n}\n\n/** Top-left position that centers a (width, height) box on the page. */\nexport function centeredPosition(\n width: number,\n height: number,\n pageWidth: number,\n pageHeight: number,\n): { x: number; y: number } {\n return {\n x: Math.round((pageWidth - width) / 2),\n y: Math.round((pageHeight - height) / 2),\n }\n}\n\n/** Common element fields with a freshly minted id and unrotated/visible defaults. */\nfunction baseAttrs(name: string, x: number, y: number) {\n return { id: mintElementId(), name, x, y, rotation: 0, opacity: 1, locked: false, visible: true }\n}\n\n/** Wrap a single element in an `add_element` op for the active page. */\nfunction addElementOp(pageId: string, element: SceneElement): SceneOperation {\n return { type: 'add_element', pageId, element }\n}\n\n/**\n * Build an `add_element` op placing an image, fitted and centered on the page.\n * `naturalSize` is the probed image dimensions (the panel reads them in the\n * browser); pass `{ width: 0, height: 0 }` when unknown to fall back to the cap.\n *\n * Throws (via `assertSceneMediaSrc`) when `src` is not http(s) or a rooted\n * `/api/` path — callers should surface the error, never insert a `data:` src.\n */\nexport function buildInsertImageOp(\n src: string,\n naturalSize: { width: number; height: number },\n page: InsertPageGeometry,\n): SceneOperation {\n assertSceneMediaSrc(src, 'image src')\n const { width, height } = fittedSize(naturalSize.width, naturalSize.height, page.width, page.height)\n const { x, y } = centeredPosition(width, height, page.width, page.height)\n const element: ImageElement = {\n ...baseAttrs('Image', x, y),\n kind: 'image',\n width,\n height,\n src,\n fit: 'contain',\n }\n return addElementOp(page.pageId, element)\n}\n\n/** A template the insert panel can drop without the agent. `build` is pure and\n * produces the operations for the active page geometry. */\nexport interface InsertTemplate {\n id: string\n label: string\n build(page: InsertPageGeometry): SceneOperation[]\n}\n\n/** The built-in starter templates (heading, body text, rectangle, ellipse).\n * Consumers can pass their own list to {@link CanvasInsertPanel}; this is the\n * default so every canvas gets a usable set out of the box. */\nexport const DEFAULT_INSERT_TEMPLATES: readonly InsertTemplate[] = [\n {\n id: 'heading',\n label: 'Heading',\n build(page) {\n const width = Math.min(480, Math.round(page.width * 0.7))\n const { x, y } = centeredPosition(width, 60, page.width, page.height)\n const element: TextElement = {\n ...baseAttrs('Heading', x, y),\n kind: 'text',\n text: 'Add a headline',\n width,\n fontFamily: 'Inter',\n fontSize: 48,\n fontStyle: 'bold',\n fill: '#111827',\n align: 'left',\n lineHeight: 1.1,\n letterSpacing: 0,\n }\n return [addElementOp(page.pageId, element)]\n },\n },\n {\n id: 'body',\n label: 'Body text',\n build(page) {\n const width = Math.min(420, Math.round(page.width * 0.6))\n const { x, y } = centeredPosition(width, 80, page.width, page.height)\n const element: TextElement = {\n ...baseAttrs('Body', x, y),\n kind: 'text',\n text: 'Add a paragraph of supporting copy.',\n width,\n fontFamily: 'Inter',\n fontSize: 20,\n fontStyle: 'normal',\n fill: '#374151',\n align: 'left',\n lineHeight: 1.4,\n letterSpacing: 0,\n }\n return [addElementOp(page.pageId, element)]\n },\n },\n {\n id: 'rect',\n label: 'Rectangle',\n build(page) {\n const width = Math.min(320, Math.round(page.width * 0.4))\n const height = Math.min(200, Math.round(page.height * 0.3))\n const { x, y } = centeredPosition(width, height, page.width, page.height)\n const element: RectElement = {\n ...baseAttrs('Rectangle', x, y),\n kind: 'rect',\n width,\n height,\n fill: '#6366f1',\n cornerRadius: 12,\n }\n return [addElementOp(page.pageId, element)]\n },\n },\n {\n id: 'ellipse',\n label: 'Ellipse',\n build(page) {\n const size = Math.min(220, Math.round(Math.min(page.width, page.height) * 0.3))\n const { x, y } = centeredPosition(size, size, page.width, page.height)\n const element: EllipseElement = {\n ...baseAttrs('Ellipse', x, y),\n kind: 'ellipse',\n width: size,\n height: size,\n fill: '#10b981',\n }\n return [addElementOp(page.pageId, element)]\n },\n },\n]\n","/**\n * The human \"add element to the canvas\" side panel — a reusable left rail any\n * canvas consumer can mount so a person (not just the agent) can put images,\n * shapes, and text on the canvas. Designed for the `renderSidePanel` slot of\n * {@link DesignCanvasProps}; pass `renderSidePanel={(ctx) => <CanvasInsertPanel … />}`.\n *\n * Endpoints are per-app, so every I/O path is a callback — no product route is\n * hardcoded:\n * - `onUploadImage(file) => Promise<url>` — the host stores the file and returns\n * the src to insert. The url MUST be http(s) or a rooted `/api/` path\n * (enforced by `assertSceneMediaSrc` before insertion); a `data:` url is\n * rejected, matching the scene model's media boundary.\n * - `loadGenerations?()` — optional provider for \"already generated in this\n * workspace\" images; omit to hide the tab.\n * - `templates?` — optional template set; defaults to {@link DEFAULT_INSERT_TEMPLATES}.\n *\n * Insertion goes through `onInsert`, which the host wires to its\n * `onApplyOperations` pipeline (server-validated, undoable) — the same path\n * every other edit takes.\n *\n * Tokens/icons follow the canvas convention: CSS-var design tokens and inline\n * SVG glyphs, no icon-library or Tailwind-semantic-token dependency.\n */\n\nimport { useEffect, useRef, useState, type ReactElement } from 'react'\nimport type { SceneOperation } from '../../design-canvas/operations'\nimport { assertSceneMediaSrc } from '../../design-canvas/model'\nimport {\n DEFAULT_INSERT_TEMPLATES,\n buildInsertImageOp,\n type InsertPageGeometry,\n type InsertTemplate,\n} from '../insert-builders'\nimport { ImageGlyph, ShapesGlyph } from './glyphs'\n\n/** An already-generated image the panel can offer for one-click insertion. */\nexport interface InsertGeneration {\n id: string\n /** The image url to insert. Must satisfy the scene media boundary\n * (http(s) or rooted `/api/`); rejected otherwise at insert time. */\n url: string\n /** Optional prompt/label shown as the tile's title. */\n label?: string\n}\n\nexport interface CanvasInsertPanelProps {\n canWrite: boolean\n /** The active page new elements are added to. */\n page: InsertPageGeometry\n /** Submit operations through the host's apply pipeline. */\n onInsert(operations: SceneOperation[]): Promise<unknown>\n /** Store an uploaded file and return its src (http(s) or rooted `/api/`). */\n onUploadImage(file: File): Promise<string>\n /** Optional provider for the Generations tab; omit to hide it. */\n loadGenerations?(): Promise<InsertGeneration[]>\n /** Drop-in templates; defaults to the built-in starter set. */\n templates?: readonly InsertTemplate[]\n /** Accepted upload mime types. Default: PNG/JPEG/GIF/WebP. */\n accept?: string\n className?: string\n}\n\ntype Tab = 'uploads' | 'templates' | 'generations'\n\nconst DEFAULT_ACCEPT = 'image/png,image/jpeg,image/gif,image/webp'\n\n/** Read an image url's natural dimensions in the browser; resolves `{0,0}` when\n * Image is unavailable (SSR/tests), the load fails, or the probe times out —\n * builders fall back to the size cap, so a failed probe still inserts a sensibly\n * sized element rather than blocking insertion. */\nconst PROBE_TIMEOUT_MS = 4000\n\nfunction probeImageSize(url: string): Promise<{ width: number; height: number }> {\n return new Promise((resolve) => {\n if (typeof Image === 'undefined') return resolve({ width: 0, height: 0 })\n let settled = false\n const finish = (size: { width: number; height: number }) => {\n if (settled) return\n settled = true\n resolve(size)\n }\n const img = new Image()\n img.onload = () => finish({ width: img.naturalWidth, height: img.naturalHeight })\n img.onerror = () => finish({ width: 0, height: 0 })\n // A URL that never loads (or an environment that never fires load/error)\n // must not hang the insert forever.\n setTimeout(() => finish({ width: 0, height: 0 }), PROBE_TIMEOUT_MS)\n img.src = url\n })\n}\n\nfunction UploadGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 9l5-5 5 5M12 4v12\" />\n </svg>\n )\n}\n\nfunction SparkleGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1\" />\n </svg>\n )\n}\n\nfunction SpinnerGlyph({ className }: { className?: string }) {\n return (\n <svg className={`${className ?? ''} animate-spin`} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden>\n <path d=\"M21 12a9 9 0 1 1-6.2-8.6\" strokeLinecap=\"round\" />\n </svg>\n )\n}\n\n/** Probe-page geometry for deriving a template's primary element kind. The\n * result drives only the tile preview, so the exact dimensions don't matter. */\nconst PREVIEW_PROBE_PAGE: InsertPageGeometry = { pageId: 'preview', width: 1000, height: 1000 }\n\n/** The visual a template tile shows. Derived from the first element the\n * template's (pure) `build` produces, so custom templates render correctly\n * too — not just the built-in set. */\ntype TemplateShape = 'heading' | 'body' | 'rect' | 'ellipse' | 'other'\n\n/** A text element is treated as a heading (shown as \"T\") when it reads as a\n * title — bold or set in a large face; otherwise body copy (shown as \"¶\"). */\nconst HEADING_FONT_SIZE = 32\n\nfunction templateShape(tpl: InsertTemplate): TemplateShape {\n try {\n const ops = tpl.build(PREVIEW_PROBE_PAGE)\n const added = ops.find((op) => op.type === 'add_element')\n if (!added || added.type !== 'add_element') return 'other'\n const el = added.element\n if (el.kind === 'rect') return 'rect'\n if (el.kind === 'ellipse') return 'ellipse'\n if (el.kind === 'text') {\n const isHeading = el.fontStyle.includes('bold') || el.fontSize >= HEADING_FONT_SIZE\n return isHeading ? 'heading' : 'body'\n }\n return 'other'\n } catch {\n // A throwing build can't preview; fall through to the neutral shape glyph.\n return 'other'\n }\n}\n\n/** A small visual standing in for what the template inserts, so tiles are\n * distinguishable at a glance instead of four identical text chips. */\nfunction TemplatePreview({ shape }: { shape: TemplateShape }) {\n if (shape === 'rect') {\n return <span className=\"block h-6 w-9 rounded-sm bg-[var(--brand-primary)]\" aria-hidden />\n }\n if (shape === 'ellipse') {\n return <span className=\"block h-7 w-7 rounded-full bg-[var(--brand-primary)]\" aria-hidden />\n }\n // Text tiles show a glyph: a bold \"T\" for a heading, a paragraph mark \"¶\"\n // for body copy.\n if (shape === 'heading' || shape === 'body') {\n return (\n <span\n className={`block leading-none text-[var(--text-primary)] ${\n shape === 'heading' ? 'text-2xl font-bold' : 'text-lg'\n }`}\n aria-hidden\n >\n {shape === 'heading' ? 'T' : '¶'}\n </span>\n )\n }\n return <ShapesGlyph className=\"h-6 w-6 text-[var(--text-muted)]\" />\n}\n\nexport function CanvasInsertPanel({\n canWrite,\n page,\n onInsert,\n onUploadImage,\n loadGenerations,\n templates = DEFAULT_INSERT_TEMPLATES,\n accept = DEFAULT_ACCEPT,\n className,\n}: CanvasInsertPanelProps) {\n const [tab, setTab] = useState<Tab>('uploads')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState('')\n const [dragOver, setDragOver] = useState(false)\n const [generations, setGenerations] = useState<InsertGeneration[]>([])\n const [generationsLoaded, setGenerationsLoaded] = useState(false)\n const fileInputRef = useRef<HTMLInputElement | null>(null)\n\n useEffect(() => {\n if (tab !== 'generations' || generationsLoaded || !loadGenerations) return\n let cancelled = false\n void (async () => {\n try {\n const rows = await loadGenerations()\n if (!cancelled) setGenerations(rows.filter((g) => g.url))\n } catch (err) {\n if (!cancelled) setError(err instanceof Error ? err.message : 'Could not load generations')\n } finally {\n if (!cancelled) setGenerationsLoaded(true)\n }\n })()\n return () => {\n cancelled = true\n }\n }, [tab, generationsLoaded, loadGenerations])\n\n async function insertImageFromUrl(url: string) {\n // Validate the media boundary BEFORE probing — a data: or sandbox-local url\n // throws here (surfaced as an inline error) without a wasted image load.\n assertSceneMediaSrc(url, 'image src')\n const natural = await probeImageSize(url)\n await onInsert([buildInsertImageOp(url, natural, page)])\n }\n\n async function handleFiles(files: FileList | File[]) {\n if (!canWrite || busy) return\n const list = Array.from(files).filter((f) => f.type.startsWith('image/'))\n if (list.length === 0) {\n setError('Only image files can be added to the canvas')\n return\n }\n setBusy(true)\n setError('')\n try {\n for (const file of list) {\n const url = await onUploadImage(file)\n await insertImageFromUrl(url)\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Upload failed')\n } finally {\n setBusy(false)\n }\n }\n\n async function runInsert(build: () => SceneOperation[] | Promise<SceneOperation[]>) {\n if (!canWrite || busy) return\n setBusy(true)\n setError('')\n try {\n await onInsert(await build())\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Insert failed')\n } finally {\n setBusy(false)\n }\n }\n\n const tabs: Array<{ id: Tab; label: string; icon: (p: { className?: string }) => ReactElement; show: boolean }> = [\n { id: 'uploads', label: 'Uploads', icon: ImageGlyph, show: true },\n { id: 'templates', label: 'Templates', icon: ShapesGlyph, show: templates.length > 0 },\n { id: 'generations', label: 'Generations', icon: SparkleGlyph, show: !!loadGenerations },\n ]\n const visibleTabs = tabs.filter((t) => t.show)\n\n return (\n <div className={`flex h-full min-h-0 flex-col bg-[var(--bg-input)] text-[var(--text-primary)] ${className ?? ''}`}>\n <div className=\"flex shrink-0 border-b border-[var(--border-default)]\">\n {visibleTabs.map(({ id, label, icon: Icon }) => (\n <button\n key={id}\n type=\"button\"\n onClick={() => setTab(id)}\n className={`flex flex-1 items-center justify-center gap-1.5 px-2 py-2.5 text-xs font-medium transition-colors ${\n tab === id\n ? 'border-b-2 border-[var(--brand-primary)] text-[var(--text-primary)]'\n : 'text-[var(--text-muted)] hover:text-[var(--text-primary)]'\n }`}\n >\n <Icon className=\"h-3.5 w-3.5\" />\n {label}\n </button>\n ))}\n </div>\n\n {!canWrite ? (\n <div className=\"flex flex-1 items-center justify-center p-4 text-center text-sm text-[var(--text-muted)]\">\n You have view-only access to this design.\n </div>\n ) : (\n <div className=\"min-h-0 flex-1 overflow-y-auto p-3\">\n {tab === 'uploads' && (\n <div className=\"flex flex-col gap-3\">\n <button\n type=\"button\"\n disabled={busy}\n onClick={() => fileInputRef.current?.click()}\n onDragOver={(e) => {\n e.preventDefault()\n setDragOver(true)\n }}\n onDragLeave={() => setDragOver(false)}\n onDrop={(e) => {\n e.preventDefault()\n setDragOver(false)\n if (e.dataTransfer?.files?.length) void handleFiles(e.dataTransfer.files)\n }}\n className={`flex flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed px-4 py-8 text-center transition-colors disabled:opacity-50 ${\n dragOver\n ? 'border-[var(--brand-primary)] bg-[var(--brand-primary)]/5'\n : 'border-[var(--border-default)] hover:border-[var(--brand-primary)]/40'\n }`}\n >\n {busy ? (\n <SpinnerGlyph className=\"h-6 w-6 text-[var(--brand-primary)]\" />\n ) : (\n <UploadGlyph className=\"h-6 w-6 text-[var(--text-muted)]\" />\n )}\n <span className=\"text-sm font-medium text-[var(--text-primary)]\">\n {busy ? 'Uploading…' : 'Drop an image or click to upload'}\n </span>\n <span className=\"text-xs text-[var(--text-muted)]\">PNG, JPEG, GIF, or WebP</span>\n </button>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept={accept}\n multiple\n className=\"hidden\"\n onChange={(e) => {\n if (e.target.files?.length) void handleFiles(e.target.files)\n e.target.value = ''\n }}\n />\n </div>\n )}\n\n {tab === 'templates' && (\n <div className=\"grid grid-cols-2 gap-2\">\n {templates.map((tpl) => (\n <button\n key={tpl.id}\n type=\"button\"\n disabled={busy}\n onClick={() => void runInsert(() => tpl.build(page))}\n className=\"flex h-20 flex-col items-center justify-center gap-1.5 rounded-md border border-[var(--border-default)] bg-[var(--bg-input)] text-xs font-medium text-[var(--text-primary)] transition-colors hover:border-[var(--brand-primary)]/40 disabled:opacity-50\"\n >\n <span className=\"flex h-7 items-center justify-center\">\n <TemplatePreview shape={templateShape(tpl)} />\n </span>\n <span>{tpl.label}</span>\n </button>\n ))}\n </div>\n )}\n\n {tab === 'generations' && (\n <div className=\"flex flex-col gap-2\">\n {!generationsLoaded ? (\n <div className=\"flex items-center justify-center py-8 text-[var(--text-muted)]\">\n <SpinnerGlyph className=\"h-5 w-5\" />\n </div>\n ) : generations.length === 0 ? (\n <p className=\"px-1 py-6 text-center text-sm text-[var(--text-muted)]\">\n No generated images yet. Ask the agent to generate one.\n </p>\n ) : (\n <div className=\"grid grid-cols-2 gap-2\">\n {generations.map((gen) => (\n <button\n key={gen.id}\n type=\"button\"\n disabled={busy}\n onClick={() => {\n if (busy || !canWrite) return\n setBusy(true)\n setError('')\n insertImageFromUrl(gen.url)\n .catch((err) => setError(err instanceof Error ? err.message : 'Insert failed'))\n .finally(() => setBusy(false))\n }}\n title={gen.label}\n className=\"group relative aspect-square overflow-hidden rounded-md border border-[var(--border-default)] bg-[var(--bg-input)] transition-colors hover:border-[var(--brand-primary)] disabled:opacity-50\"\n >\n {/* eslint-disable-next-line jsx-a11y/img-redundant-alt */}\n <img src={gen.url} alt={gen.label || 'Generated image'} className=\"h-full w-full object-cover\" />\n </button>\n ))}\n </div>\n )}\n </div>\n )}\n\n {error ? <p className=\"mt-3 text-xs leading-5 text-[var(--text-danger,#dc2626)]\">{error}</p> : null}\n </div>\n )}\n </div>\n )\n}\n","/**\n * Layers panel — reverse-z list of the active page's elements. Highest z-index\n * is at the top. Groups show their children indented beneath them. Clicking\n * selects an element; meta-click adds to selection. Double-clicking the name\n * opens an inline rename input. Eye and lock icons toggle visibility/locked.\n * Rows can be drag-reordered within the list (siblings only; cross-group reorder\n * is a pending integrator concern). Capped at LAYERS_PANEL_ROW_LIMIT rows.\n */\n\nimport { useCallback, useMemo, useRef, useState } from 'react'\nimport type { SceneElement, ScenePage } from '../../design-canvas/model'\nimport { flattenLayerTree, LAYERS_PANEL_ROW_LIMIT } from './layer-tree'\nimport {\n EllipseGlyph,\n EyeGlyph,\n EyeOffGlyph,\n GroupGlyph,\n ImageGlyph,\n LineGlyph,\n LockGlyph,\n RectGlyph,\n SlotGlyph,\n TextGlyph,\n UnlockGlyph,\n VideoGlyph,\n} from './glyphs'\nimport type { SceneElementKind } from '../../design-canvas/model'\n\nfunction KindIcon({ kind, className }: { kind: SceneElementKind; className?: string }) {\n switch (kind) {\n case 'rect': return <RectGlyph className={className} />\n case 'ellipse': return <EllipseGlyph className={className} />\n case 'line': return <LineGlyph className={className} />\n case 'text': return <TextGlyph className={className} />\n case 'image': return <ImageGlyph className={className} />\n case 'video': return <VideoGlyph className={className} />\n case 'group': return <GroupGlyph className={className} />\n }\n}\n\nexport interface LayersPanelProps {\n page: ScenePage\n selectedElementIds: string[]\n canWrite: boolean\n /** Emit a set_attrs command for the given element. */\n onSetAttrs(elementId: string, attrs: Partial<Pick<SceneElement, 'name' | 'visible' | 'locked'>>): void\n /** Emit a reorder_element command. */\n onReorder(elementId: string, toIndex: number): void\n onSelect(elementId: string, additive: boolean): void\n}\n\nconst INDENT_PX = 16\n\nexport function LayersPanel({ page, selectedElementIds, canWrite, onSetAttrs, onReorder, onSelect }: LayersPanelProps) {\n const rows = useMemo(() => flattenLayerTree(page), [page])\n const [renamingId, setRenamingId] = useState<string | null>(null)\n const [renameValue, setRenameValue] = useState('')\n\n // Drag-reorder state: we track the dragged row's ownerIndex and the target\n // index within the SAME owner. Cross-owner reorder is not supported here.\n const dragRowRef = useRef<{ elementId: string; ownerIndex: number; ownerLength: number } | null>(null)\n const [dragOverIndex, setDragOverIndex] = useState<number | null>(null)\n\n function startRename(element: SceneElement) {\n setRenamingId(element.id)\n setRenameValue(element.name)\n }\n\n function commitRename(elementId: string) {\n const name = renameValue.trim()\n if (name.length > 0) onSetAttrs(elementId, { name })\n setRenamingId(null)\n }\n\n const visible = rows.length > LAYERS_PANEL_ROW_LIMIT ? rows.slice(0, LAYERS_PANEL_ROW_LIMIT) : rows\n\n return (\n <div className=\"flex h-full flex-col overflow-hidden text-[var(--text-primary)]\">\n <div className=\"shrink-0 border-b border-[var(--border-default)] px-3 py-1.5 text-[11px] font-medium uppercase tracking-wide text-[var(--text-muted)]\">\n Layers\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n {visible.map((row) => {\n const { element } = row\n const isSelected = selectedElementIds.includes(element.id)\n const isRenaming = renamingId === element.id\n\n return (\n <div\n key={element.id}\n data-layer-row={element.id}\n draggable={canWrite}\n onDragStart={() => {\n dragRowRef.current = {\n elementId: element.id,\n ownerIndex: row.ownerIndex,\n ownerLength: row.ownerLength,\n }\n }}\n onDragOver={(event) => {\n if (!dragRowRef.current) return\n // Only allow reorder within the same owner (same parentGroupId + depth)\n event.preventDefault()\n setDragOverIndex(row.ownerIndex)\n }}\n onDragLeave={() => setDragOverIndex(null)}\n onDrop={() => {\n const drag = dragRowRef.current\n if (!drag) return\n if (drag.elementId !== element.id) {\n onReorder(drag.elementId, row.ownerIndex)\n }\n dragRowRef.current = null\n setDragOverIndex(null)\n }}\n onDragEnd={() => {\n dragRowRef.current = null\n setDragOverIndex(null)\n }}\n onClick={(event) => onSelect(element.id, event.metaKey || event.ctrlKey)}\n className={[\n 'flex cursor-pointer items-center gap-1.5 py-1 pr-2 text-[13px] transition-colors',\n isSelected ? 'bg-[var(--brand-primary)]/15 text-[var(--text-primary)]' : 'hover:bg-[var(--border-default)]/40 text-[var(--text-secondary)]',\n dragOverIndex === row.ownerIndex ? 'border-t border-[var(--brand-primary)]' : '',\n ].join(' ')}\n style={{ paddingLeft: 8 + row.depth * INDENT_PX }}\n >\n <KindIcon kind={element.kind} className=\"h-3.5 w-3.5 shrink-0 opacity-60\" />\n\n {element.slot ? (\n <SlotGlyph className=\"h-3 w-3 shrink-0 text-[var(--brand-primary)]\" />\n ) : null}\n\n <span className=\"min-w-0 flex-1 truncate\">\n {isRenaming ? (\n <input\n autoFocus\n value={renameValue}\n onChange={(event) => setRenameValue(event.target.value)}\n onBlur={() => commitRename(element.id)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') commitRename(element.id)\n if (event.key === 'Escape') setRenamingId(null)\n event.stopPropagation()\n }}\n onClick={(event) => event.stopPropagation()}\n className=\"w-full rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-1 py-0 text-[13px] text-[var(--text-primary)] outline-none focus:border-[var(--brand-primary)]\"\n />\n ) : (\n <span\n onDoubleClick={(event) => {\n event.stopPropagation()\n if (canWrite) startRename(element)\n }}\n title={element.name}\n >\n {element.name}\n </span>\n )}\n </span>\n\n {/* Visibility toggle */}\n <button\n type=\"button\"\n aria-label={element.visible ? 'Hide element' : 'Show element'}\n onClick={(event) => {\n event.stopPropagation()\n if (canWrite) onSetAttrs(element.id, { visible: !element.visible })\n }}\n disabled={!canWrite}\n className=\"shrink-0 rounded p-0.5 opacity-0 transition-opacity hover:opacity-100 focus:opacity-100 group-hover:opacity-100 [.flex:hover_&]:opacity-60\"\n >\n {element.visible\n ? <EyeGlyph className=\"h-3.5 w-3.5\" />\n : <EyeOffGlyph className=\"h-3.5 w-3.5 text-[var(--text-muted)]\" />}\n </button>\n\n {/* Lock toggle */}\n <button\n type=\"button\"\n aria-label={element.locked ? 'Unlock element' : 'Lock element'}\n onClick={(event) => {\n event.stopPropagation()\n if (canWrite) onSetAttrs(element.id, { locked: !element.locked })\n }}\n disabled={!canWrite}\n className=\"shrink-0 rounded p-0.5 opacity-0 transition-opacity hover:opacity-100 focus:opacity-100 [.flex:hover_&]:opacity-60\"\n >\n {element.locked\n ? <LockGlyph className=\"h-3.5 w-3.5 text-amber-400\" />\n : <UnlockGlyph className=\"h-3.5 w-3.5\" />}\n </button>\n </div>\n )\n })}\n\n {rows.length > LAYERS_PANEL_ROW_LIMIT ? (\n <div className=\"px-3 py-2 text-[11px] text-[var(--text-muted)]\">\n +{rows.length - LAYERS_PANEL_ROW_LIMIT} more elements — select a group to scope the list\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n","/**\n * Pure layer-tree helpers: flatten a page's element array into an ordered list\n * for the LayersPanel, preserving group nesting. Extracted so render order,\n * depth, and selection math can be unit-tested independently of React.\n */\n\nimport type { SceneElement, ScenePage } from '../../design-canvas/model'\n\nexport interface LayerRow {\n element: SceneElement\n depth: number\n /** True when this row is a group whose children follow it in the list. */\n isGroup: boolean\n /** Index within its owner (page.elements or group.children). */\n ownerIndex: number\n /** Length of the owner array — needed for z-order bound math. */\n ownerLength: number\n /** Id of the containing group, or null for page-root elements. */\n parentGroupId: string | null\n}\n\n/**\n * Flatten `page.elements` into display order for the layers panel.\n *\n * Z-order is bottom→top in the element array; the layers panel shows\n * top→bottom (highest z-index first). A group row precedes its children\n * (the group is \"above\" its children in the panel, reflecting that the group\n * node itself paints last in Konva when empty; children paint inside it).\n *\n * Depth increases by 1 for each group level, starting at 0 for page-root\n * elements. Groups are expanded; collapsed state is a view concern the caller\n * tracks separately.\n */\nexport function flattenLayerTree(page: ScenePage): LayerRow[] {\n const rows: LayerRow[] = []\n\n function visit(elements: SceneElement[], depth: number, parentGroupId: string | null) {\n // Reverse so highest z-index appears at the top of the panel.\n for (let i = elements.length - 1; i >= 0; i -= 1) {\n const element = elements[i]!\n rows.push({\n element,\n depth,\n isGroup: element.kind === 'group',\n ownerIndex: i,\n ownerLength: elements.length,\n parentGroupId,\n })\n if (element.kind === 'group') {\n visit(element.children, depth + 1, element.id)\n }\n }\n }\n\n visit(page.elements, 0, null)\n return rows\n}\n\n/** The maximum number of rows the layers panel renders before truncating. */\nexport const LAYERS_PANEL_ROW_LIMIT = 500\n","/**\n * Lazy-loaded entry points for the design canvas editor. Products that do not\n * need the editor on initial load import `DesignCanvasLazy` (the batteries-\n * included composition) or `DesignCanvasChromeLazy` (raw chrome only) instead\n * of importing the components directly. Each chunk loads on first render; a\n * Suspense boundary in the product provides the fallback.\n *\n * `DesignCanvasLazy` — `DesignCanvasEditor`: chrome + workspace sharing one\n * command stack. This is what products mount.\n *\n * `DesignCanvasChromeLazy` — `DesignCanvas`: raw chrome with render-prop API.\n * Use when the product supplies its own `renderWorkspace` and `renderThumbnail`.\n */\n\nimport { lazy } from 'react'\nimport type { DesignCanvasFullProps } from './components/DesignCanvas'\nimport type { DesignCanvasProps } from './contracts'\n\nexport type { DesignCanvasFullProps }\nexport type { DesignCanvasProps }\n\n/** Batteries-included editor: chrome + workspace on one shared stack. */\nexport const DesignCanvasLazy = lazy(\n () =>\n import('./components/DesignCanvasEditor').then((m) => ({ default: m.DesignCanvasEditor })),\n)\n\n/** Raw chrome only — use when supplying a custom renderWorkspace/renderThumbnail. */\nexport const DesignCanvasChromeLazy = lazy(\n () =>\n import('./components/DesignCanvas').then((m) => ({ default: m.DesignCanvas })),\n)\n"],"mappings":"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","names":["useRef","useState","jsx","jsxs","useState","useRef"]}
package/dist/index.d.ts CHANGED
@@ -18,7 +18,7 @@ export { BufferedTurnEvent, D1LikeForTurns, JsonRecord, PersistedChatMessageForT
18
18
  export { HubExecClient, HubExecClientOptions, HubExecErrorCode, HubExecResult, HubInvokeDeps, HubInvokeInput, HubInvokeOutcome, ParsedIntegrationAction, invokeIntegrationHub, resolveIntegrationAction } from './integrations/index.js';
19
19
  export { CompleteMissionInput, CreateMissionInput, DEFAULT_MISSION_STEP_KINDS, InMemoryMissionStore, MISSION_CONTROL_CHANNEL_ID, MissionApprovalsPort, MissionAuditEvent, MissionConcurrencyError, MissionCostLedger, MissionEngine, MissionEngineOptions, MissionEventSink, MissionGateKind, MissionGateOptions, MissionGateProposal, MissionOutcome, MissionPlanRunOptions, MissionProposalResolution, MissionRecord, MissionService, MissionServiceOptions, MissionState, MissionStatus, MissionStep, MissionStepState, MissionStepStatus, MissionStorePort, MissionStreamEvent, MissionStreamStatus, MissionStreamStep, MissionStreamStepStatus, MissionUpdateGuard, MissionUpdatePatch, ParseMissionBlocksOptions, ParsedMission, ParsedMissionStep, PlanOutcome, RetryableStepError, SandboxDispatch, SandboxDispatchDoneResult, SandboxDispatchInProgressResult, SandboxDispatchInput, SandboxDispatchResult, SetStepStatusPatch, StepGateClassification, StepOutcome, applyMissionEvent, asMissionStreamEvent, budgetGateProposalId, buildAgentMissionPlan, createInMemoryMissionStore, createMissionEngine, createMissionService, isMissionStopRequested, isMissionTerminal, mergeMissionState, noopEventSink, parseMissionBlocks, parseSessionStreamEnvelope, reduceMissionEvents, stepGateProposalId, volumeGateProposalId } from './missions/index.js';
20
20
  export { S as StepAgentActivity, W as WithAgentActivity, s as stepAgentActivity } from './agent-activity-C8ZG0F0M.js';
21
- export { AppToolDescriptor, AuthenticatedSandboxUser, BuildAppToolMcpServersOptions, DEFAULT_SANDBOX_RESOURCES, EnsureWorkspaceSandboxOptions, LivenessProbeConfig, MemberSyncSeam, Outcome, ProfileComposeOptions, ProviderResolutionConfig, ResolvedModel, SandboxApiCredentials, SandboxBuildContext, SandboxClientCredentials, SandboxPermissionLevel, SandboxResourceConfig, SandboxRestoreSpec, SandboxRuntimeConfig, SandboxRuntimeConnection, SandboxScope, SandboxStepTransition, SandboxTerminalTokenOptions, SandboxTerminalTokenResult, SandboxTerminalTokenSubject, SandboxTerminalWsMatch, ScopedTokenResult, SecretStore, StreamSandboxPromptOptions, TerminalProxyIdentity, WorkspaceSandboxConnectionArgs, WorkspaceSandboxConnectionHandlerOptions, WorkspaceSandboxEnsureContext, WorkspaceSandboxInstanceLike, WorkspaceSandboxManager, WorkspaceSandboxManagerOptions, WorkspaceSandboxRuntimeProxyArgs, WorkspaceSandboxRuntimeProxyHandlerOptions, WorkspaceSandboxTerminalUpgradeHandlerOptions, attachReasoningEffort, bearerSubprotocolToken, bearerToken, buildAppToolMcpServers, buildSandboxRuntimeProxyHeaders, classifySeveredStream, createSandboxTerminalToken, createWorkspaceSandboxConnectionHandler, createWorkspaceSandboxManager, createWorkspaceSandboxRuntimeProxyHandler, createWorkspaceSandboxTerminalUpgradeHandler, deleteSecret, detectInteractiveQuestion, driveSandboxTurn, encodeSandboxRuntimePath, ensureWorkspaceSandbox, flattenHistory, getClient, isSandboxTerminalWsUpgrade, isTerminalPromptEvent, matchSandboxTerminalWsPath, mergeExtraMcp, mintSandboxScopedToken, mintTerminalProxyToken, readSecret, resetClientCache, resolveModel, runSandboxPrompt, secretStoreFromClient, storeSecret, streamSandboxPrompt, syncSandboxMemberAdd, syncSandboxMemberRemove, syncSandboxMemberRole, terminalTokenFromRequest, verifySandboxTerminalToken, verifyTerminalProxyToken } from './sandbox/index.js';
21
+ export { AppToolDescriptor, AuthenticatedSandboxUser, BuildAppToolMcpServersOptions, DEFAULT_SANDBOX_RESOURCES, EnsureWorkspaceSandboxOptions, LivenessProbeConfig, MemberSyncSeam, Outcome, ProfileComposeOptions, ProviderResolutionConfig, ResolvedModel, SandboxApiCredentials, SandboxBuildContext, SandboxClientCredentials, SandboxPermissionLevel, SandboxResourceConfig, SandboxRestoreSpec, SandboxRuntimeConfig, SandboxRuntimeConnection, SandboxScope, SandboxStepTransition, SandboxTerminalTokenOptions, SandboxTerminalTokenResult, SandboxTerminalTokenSubject, SandboxTerminalWsMatch, ScopedTokenResult, SecretStore, StreamSandboxPromptOptions, TerminalProxyIdentity, WorkspaceSandboxConnectionArgs, WorkspaceSandboxConnectionHandlerOptions, WorkspaceSandboxEnsureContext, WorkspaceSandboxInstanceLike, WorkspaceSandboxManager, WorkspaceSandboxManagerOptions, WorkspaceSandboxRuntimeProxyArgs, WorkspaceSandboxRuntimeProxyHandlerOptions, WorkspaceSandboxTerminalUpgradeHandlerOptions, attachReasoningEffort, bearerSubprotocolToken, bearerToken, buildAppToolMcpServers, buildSandboxRuntimeProxyHeaders, classifySeveredStream, createSandboxTerminalToken, createWorkspaceSandboxConnectionHandler, createWorkspaceSandboxManager, createWorkspaceSandboxRuntimeProxyHandler, createWorkspaceSandboxTerminalUpgradeHandler, deleteSecret, detectInteractiveQuestion, driveSandboxTurn, encodeSandboxRuntimePath, ensureWorkspaceSandbox, flattenHistory, getClient, isSandboxTerminalWsUpgrade, isTerminalPromptEvent, matchSandboxTerminalWsPath, mergeExtraMcp, mintSandboxScopedToken, mintTerminalProxyToken, readSecret, resetClientCache, resolveModel, runSandboxPrompt, secretStoreFromClient, splitDeferredProfileFiles, storeSecret, streamSandboxPrompt, syncSandboxMemberAdd, syncSandboxMemberRemove, syncSandboxMemberRole, terminalTokenFromRequest, verifySandboxTerminalToken, verifyTerminalProxyToken, writeProfileFilesToBox } from './sandbox/index.js';
22
22
  export { CookieOptions, JsonObject, KvLike, RateLimitResult, RequestContext, SecurityHeaderOptions, addSecurityHeaders, checkRateLimit, clearCookieHeader, extractRequestContext, parseJsonObjectBody, readCookieValue, requireString, serializeCookie } from './web/index.js';
23
23
  export { BuildRedactedDocumentOptions, DEFAULT_REDACTION_PATTERNS, RedactForIngestionOptions, RedactedDocSegment, RedactedDocument, RedactionPattern, RedactionSpan, RevealResult, RevealSpanOptions, buildRedactedDocument, detectSpans, maskSpans, redactForIngestion, revealSpan } from './redact/index.js';
24
24
  export { ApprovalEvent, ApprovalEventSchema, AssetContentMap, AssetFormat, AssetSpec, AssetStatus, AssetVariant, BrandTokens, BrandTokensSchema, ConversionMetrics, ConversionMetricsSchema, CopyContent, CopyContentSchema, CopyPlatform, EmailBodySection, EmailContent, EmailContentSchema, EmailCtaSection, EmailDividerSection, EmailFeatureSection, EmailHeroSection, EmailSection, EmailTestimonialSection, ImageBackground, ImageContent, ImageContentSchema, ImageImageLayer, ImageLayer, ImageLayerType, ImageLogoLayer, ImageShapeLayer, ImageSlide, ImageTextLayer, VideoCaption, VideoContent, VideoContentSchema, VideoCountdownScene, VideoImageRevealScene, VideoScene, VideoSlideScene, VideoTextAnimationScene, parseAssetSpec, safeParseAssetSpec } from './assets/index.js';
package/dist/index.js CHANGED
@@ -265,6 +265,7 @@ import {
265
265
  resolveModel,
266
266
  runSandboxPrompt,
267
267
  secretStoreFromClient,
268
+ splitDeferredProfileFiles,
268
269
  storeSecret,
269
270
  streamSandboxPrompt,
270
271
  syncSandboxMemberAdd,
@@ -272,8 +273,9 @@ import {
272
273
  syncSandboxMemberRole,
273
274
  terminalTokenFromRequest,
274
275
  verifySandboxTerminalToken,
275
- verifyTerminalProxyToken
276
- } from "./chunk-XDCHKFBX.js";
276
+ verifyTerminalProxyToken,
277
+ writeProfileFilesToBox
278
+ } from "./chunk-GZGF3JWQ.js";
277
279
  import {
278
280
  DEFAULT_HARNESS,
279
281
  HARNESS_MODEL_POLICIES,
@@ -638,6 +640,7 @@ export {
638
640
  snapHarnessToModel,
639
641
  snapModelToHarness,
640
642
  snapshotFrame,
643
+ splitDeferredProfileFiles,
641
644
  stepActivityFlowTrace,
642
645
  stepAgentActivity,
643
646
  stepGateProposalId,
@@ -678,6 +681,7 @@ export {
678
681
  verifySandboxTerminalToken,
679
682
  verifyTerminalProxyToken,
680
683
  volumeGateProposalId,
681
- weightedComposite
684
+ weightedComposite,
685
+ writeProfileFilesToBox
682
686
  };
683
687
  //# sourceMappingURL=index.js.map
@@ -258,6 +258,7 @@ interface SandboxRuntimeConfig {
258
258
  livenessProbe?: LivenessProbeConfig;
259
259
  resumeStopped?: boolean;
260
260
  backendModelAtCreate?: boolean;
261
+ deferProfileFiles?: boolean;
261
262
  }
262
263
  declare const DEFAULT_SANDBOX_RESOURCES: SandboxResourceConfig;
263
264
  declare function getClient(shell: SandboxRuntimeConfig): Sandbox;
@@ -281,6 +282,11 @@ interface EnsureWorkspaceSandboxOptions {
281
282
  harness: Harness;
282
283
  forceNew?: boolean;
283
284
  }
285
+ declare function splitDeferredProfileFiles(profile: AgentProfile): {
286
+ leanProfile: AgentProfile;
287
+ deferredFiles: AgentProfileFileMount[];
288
+ };
289
+ declare function writeProfileFilesToBox(box: SandboxInstance, files: AgentProfileFileMount[]): Promise<Outcome<void>>;
284
290
  declare function ensureWorkspaceSandbox(shell: SandboxRuntimeConfig, options: EnsureWorkspaceSandboxOptions): Promise<SandboxInstance>;
285
291
  interface ResolvedModel {
286
292
  model: string;
@@ -376,4 +382,4 @@ declare function classifySeveredStream(event: unknown): SandboxStepTransition |
376
382
  declare function isTerminalPromptEvent(event: unknown): boolean;
377
383
  declare function detectInteractiveQuestion(event: unknown): string | null;
378
384
 
379
- export { type AppToolDescriptor, type AuthenticatedSandboxUser, type BuildAppToolMcpServersOptions, DEFAULT_SANDBOX_RESOURCES, type EnsureWorkspaceSandboxOptions, type LivenessProbeConfig, type MemberSyncSeam, type Outcome, type ProfileComposeOptions, type ProviderResolutionConfig, type ResolvedModel, type SandboxApiCredentials, type SandboxBuildContext, type SandboxClientCredentials, type SandboxPermissionLevel, type SandboxResourceConfig, type SandboxRestoreSpec, type SandboxRuntimeConfig, type SandboxRuntimeConnection, type SandboxScope, type SandboxStepTransition, type SandboxTerminalTokenOptions, type SandboxTerminalTokenResult, type SandboxTerminalTokenSubject, type SandboxTerminalWsMatch, type ScopedTokenResult, type SecretStore, type StreamSandboxPromptOptions, type TerminalProxyIdentity, type WorkspaceSandboxConnectionArgs, type WorkspaceSandboxConnectionHandlerOptions, type WorkspaceSandboxEnsureContext, type WorkspaceSandboxInstanceLike, type WorkspaceSandboxManager, type WorkspaceSandboxManagerOptions, type WorkspaceSandboxRuntimeProxyArgs, type WorkspaceSandboxRuntimeProxyHandlerOptions, type WorkspaceSandboxTerminalUpgradeHandlerOptions, attachReasoningEffort, bearerSubprotocolToken, bearerToken, buildAppToolMcpServers, buildSandboxRuntimeProxyHeaders, classifySeveredStream, createSandboxTerminalToken, createWorkspaceSandboxConnectionHandler, createWorkspaceSandboxManager, createWorkspaceSandboxRuntimeProxyHandler, createWorkspaceSandboxTerminalUpgradeHandler, deleteSecret, detectInteractiveQuestion, driveSandboxTurn, encodeSandboxRuntimePath, ensureWorkspaceSandbox, flattenHistory, getClient, isSandboxTerminalWsUpgrade, isTerminalPromptEvent, matchSandboxTerminalWsPath, mergeExtraMcp, mintSandboxScopedToken, mintTerminalProxyToken, readSecret, resetClientCache, resolveModel, runSandboxPrompt, secretStoreFromClient, storeSecret, streamSandboxPrompt, syncSandboxMemberAdd, syncSandboxMemberRemove, syncSandboxMemberRole, terminalTokenFromRequest, verifySandboxTerminalToken, verifyTerminalProxyToken };
385
+ export { type AppToolDescriptor, type AuthenticatedSandboxUser, type BuildAppToolMcpServersOptions, DEFAULT_SANDBOX_RESOURCES, type EnsureWorkspaceSandboxOptions, type LivenessProbeConfig, type MemberSyncSeam, type Outcome, type ProfileComposeOptions, type ProviderResolutionConfig, type ResolvedModel, type SandboxApiCredentials, type SandboxBuildContext, type SandboxClientCredentials, type SandboxPermissionLevel, type SandboxResourceConfig, type SandboxRestoreSpec, type SandboxRuntimeConfig, type SandboxRuntimeConnection, type SandboxScope, type SandboxStepTransition, type SandboxTerminalTokenOptions, type SandboxTerminalTokenResult, type SandboxTerminalTokenSubject, type SandboxTerminalWsMatch, type ScopedTokenResult, type SecretStore, type StreamSandboxPromptOptions, type TerminalProxyIdentity, type WorkspaceSandboxConnectionArgs, type WorkspaceSandboxConnectionHandlerOptions, type WorkspaceSandboxEnsureContext, type WorkspaceSandboxInstanceLike, type WorkspaceSandboxManager, type WorkspaceSandboxManagerOptions, type WorkspaceSandboxRuntimeProxyArgs, type WorkspaceSandboxRuntimeProxyHandlerOptions, type WorkspaceSandboxTerminalUpgradeHandlerOptions, attachReasoningEffort, bearerSubprotocolToken, bearerToken, buildAppToolMcpServers, buildSandboxRuntimeProxyHeaders, classifySeveredStream, createSandboxTerminalToken, createWorkspaceSandboxConnectionHandler, createWorkspaceSandboxManager, createWorkspaceSandboxRuntimeProxyHandler, createWorkspaceSandboxTerminalUpgradeHandler, deleteSecret, detectInteractiveQuestion, driveSandboxTurn, encodeSandboxRuntimePath, ensureWorkspaceSandbox, flattenHistory, getClient, isSandboxTerminalWsUpgrade, isTerminalPromptEvent, matchSandboxTerminalWsPath, mergeExtraMcp, mintSandboxScopedToken, mintTerminalProxyToken, readSecret, resetClientCache, resolveModel, runSandboxPrompt, secretStoreFromClient, splitDeferredProfileFiles, storeSecret, streamSandboxPrompt, syncSandboxMemberAdd, syncSandboxMemberRemove, syncSandboxMemberRole, terminalTokenFromRequest, verifySandboxTerminalToken, verifyTerminalProxyToken, writeProfileFilesToBox };