@agimon-ai/doompi-style-system 0.0.1-alpha.77 → 0.0.1-alpha.78

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.
@@ -119,6 +119,18 @@ function normalizedAnnotationRect(start, end) {
119
119
  height: Math.abs(end.y - start.y)
120
120
  };
121
121
  }
122
+ function previewAnnotationLocation(tool, start, end) {
123
+ if (tool === "comment") return {
124
+ mode: "point",
125
+ point: end
126
+ };
127
+ const rect = normalizedAnnotationRect(start, end);
128
+ return rect.width === 0 || rect.height === 0 ? null : {
129
+ mode: "region",
130
+ point: rect,
131
+ rect
132
+ };
133
+ }
122
134
  //#endregion
123
135
  //#region src/extensions/workspaces/sessions/(frontend)/_components/StoryPreviewPanel.tsx
124
136
  function imageDownload(data, storyExport) {
@@ -140,7 +152,7 @@ function preferredExport(exports) {
140
152
  function errorText(reason) {
141
153
  return reason instanceof Error ? reason.message : String(reason);
142
154
  }
143
- function StoryPreviewPanel({ sessionId, submitCapture, seed, source }) {
155
+ function StoryPreviewPanel({ sessionId, submitCapture, seed, source, activeTool = "select", displayedAnnotations = [], pendingCandidate = false, onAnnotationCandidate }) {
144
156
  const contextualSource = source ?? seed?.source;
145
157
  const sourcePath = contextualSource?.path ?? "";
146
158
  const sourceDirty = contextualSource?.hasUnsavedChanges === true;
@@ -166,6 +178,7 @@ function StoryPreviewPanel({ sessionId, submitCapture, seed, source }) {
166
178
  const builtRequestRef = useRef(void 0);
167
179
  const workingRef = useRef(false);
168
180
  const dirtyRef = useRef(sourceDirty);
181
+ const annotationExportRef = useRef("");
169
182
  dirtyRef.current = sourceDirty;
170
183
  const request = useMemo(() => ({
171
184
  appPath,
@@ -344,13 +357,74 @@ function StoryPreviewPanel({ sessionId, submitCapture, seed, source }) {
344
357
  setAnnotationRect(void 0);
345
358
  setStatus("Annotation image ready. Drag a region or comment on the whole preview.");
346
359
  }
360
+ useEffect(() => {
361
+ if (onAnnotationCandidate === void 0) return;
362
+ if (activeTool === "select") {
363
+ annotationExportRef.current = "";
364
+ setAnnotationImage(void 0);
365
+ setAnnotationStart(void 0);
366
+ setAnnotationRect(void 0);
367
+ return;
368
+ }
369
+ if (preview === void 0 || annotationImage !== void 0 || working || annotationExportRef.current === preview.handle) return;
370
+ const generation = generationRef.current;
371
+ image().then((result) => {
372
+ if (result === void 0 || generationRef.current !== generation) return;
373
+ annotationExportRef.current = preview.handle;
374
+ setAnnotationImage(result);
375
+ setStatus("Frozen source-backed image ready for Author annotations.");
376
+ });
377
+ }, [
378
+ activeTool,
379
+ annotationImage,
380
+ image,
381
+ onAnnotationCandidate,
382
+ preview,
383
+ working
384
+ ]);
347
385
  function updateAnnotation(event) {
348
386
  if (annotationStart === void 0) return;
349
387
  const end = point(event);
350
388
  setAnnotationRect(normalizedAnnotationRect(annotationStart, end));
351
389
  }
390
+ function authorCandidate(location, rect) {
391
+ if (annotationImage === void 0 || onAnnotationCandidate === void 0 || builtRequest === void 0) return;
392
+ const binary = atob(annotationImage.data);
393
+ const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
394
+ const evidence = new Blob([bytes], { type: annotationImage.mimeType });
395
+ const thumbnailUrl = URL.createObjectURL(evidence);
396
+ try {
397
+ onAnnotationCandidate({
398
+ mode: rect === void 0 ? "point" : "region",
399
+ point: location,
400
+ ...rect === void 0 ? {} : { rect },
401
+ preview: {
402
+ version: 1,
403
+ provider: "style-system",
404
+ projectPath: builtRequest.appPath,
405
+ storyPath: annotationImage.storyPath,
406
+ storyExport: annotationImage.storyExport,
407
+ buildRevision: annotationImage.sourceSha256,
408
+ viewport: {
409
+ width: annotationImage.width,
410
+ height: annotationImage.height
411
+ },
412
+ sources: [{
413
+ path: annotationImage.storyPath,
414
+ sha256: annotationImage.sourceSha256
415
+ }]
416
+ },
417
+ evidence,
418
+ thumbnailUrl
419
+ });
420
+ } catch (reason) {
421
+ URL.revokeObjectURL(thumbnailUrl);
422
+ setStatus(errorText(reason));
423
+ }
424
+ }
352
425
  const multipleExports = storyExports !== void 0 && storyExports.length > 1;
353
426
  const previewVisible = preview !== void 0;
427
+ const authorAnnotationActive = onAnnotationCandidate !== void 0 && activeTool !== "select";
354
428
  return /* @__PURE__ */ jsxs("section", {
355
429
  className: "flex h-full min-h-0 min-w-0 flex-1 flex-col gap-3 p-3",
356
430
  "data-testid": "style-system-preview-panel",
@@ -447,12 +521,12 @@ function StoryPreviewPanel({ sessionId, submitCapture, seed, source }) {
447
521
  onClick: () => void image().then((result) => result && imageDownload(result.data, result.storyExport)),
448
522
  children: "Export PNG"
449
523
  }),
450
- /* @__PURE__ */ jsx(Button, {
524
+ onAnnotationCandidate === void 0 ? /* @__PURE__ */ jsx(Button, {
451
525
  variant: "outline",
452
526
  disabled: preview === void 0 || working,
453
527
  onClick: () => void prepareAnnotation(),
454
528
  children: "Prepare annotation"
455
- }),
529
+ }) : null,
456
530
  /* @__PURE__ */ jsxs("label", {
457
531
  className: "flex items-center gap-1 text-xs text-doom-dim",
458
532
  children: [/* @__PURE__ */ jsx("input", {
@@ -476,6 +550,80 @@ function StoryPreviewPanel({ sessionId, submitCapture, seed, source }) {
476
550
  preview === void 0 ? /* @__PURE__ */ jsx("div", {
477
551
  className: "grid min-h-48 place-items-center rounded border border-dashed border-doom-border text-sm text-doom-faint",
478
552
  children: "No preview built."
553
+ }) : authorAnnotationActive && annotationImage !== void 0 ? /* @__PURE__ */ jsx("div", {
554
+ className: "grid min-h-0 flex-1 place-items-center overflow-hidden rounded border border-doom-border bg-doom-deep",
555
+ children: /* @__PURE__ */ jsxs("div", {
556
+ className: "relative max-h-full w-fit max-w-full touch-none overflow-hidden",
557
+ "aria-label": activeTool === "comment" ? "Click to place an annotation point" : "Drag to mark an annotation region",
558
+ "data-testid": "style-system-author-annotation-overlay",
559
+ onPointerDown: (event) => {
560
+ if (pendingCandidate || activeTool !== "mark" && activeTool !== "comment") return;
561
+ event.currentTarget.setPointerCapture(event.pointerId);
562
+ const start = point(event);
563
+ setAnnotationStart(start);
564
+ setAnnotationRect(activeTool === "mark" ? {
565
+ ...start,
566
+ width: 0,
567
+ height: 0
568
+ } : void 0);
569
+ },
570
+ onPointerMove: (event) => {
571
+ if (activeTool === "mark") updateAnnotation(event);
572
+ },
573
+ onPointerUp: (event) => {
574
+ const start = annotationStart;
575
+ if (start === void 0) return;
576
+ const end = point(event);
577
+ setAnnotationStart(void 0);
578
+ const location = previewAnnotationLocation(activeTool === "comment" ? "comment" : "mark", start, end);
579
+ if (location === null) return;
580
+ setAnnotationRect(location.mode === "region" ? location.rect : void 0);
581
+ authorCandidate(location.point, location.mode === "region" ? location.rect : void 0);
582
+ },
583
+ onPointerCancel: () => {
584
+ setAnnotationStart(void 0);
585
+ setAnnotationRect(void 0);
586
+ },
587
+ children: [
588
+ /* @__PURE__ */ jsx("img", {
589
+ className: "block max-h-full max-w-full select-none",
590
+ src: `data:${annotationImage.mimeType};base64,${annotationImage.data}`,
591
+ alt: "Frozen source-backed story render for Author annotation",
592
+ draggable: false
593
+ }),
594
+ displayedAnnotations.map((annotation) => annotation.mode === "point" || annotation.rect === void 0 ? /* @__PURE__ */ jsx("span", {
595
+ "data-author-point": annotation.ordinal,
596
+ className: "pointer-events-none absolute z-10 flex h-5 min-w-5 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border-2 border-doom-deep bg-doom-yellow px-1 text-2xs font-bold text-doom-deep",
597
+ style: {
598
+ left: `${String(annotation.point.x * 100)}%`,
599
+ top: `${String(annotation.point.y * 100)}%`
600
+ },
601
+ children: annotation.ordinal
602
+ }, annotation.ordinal) : /* @__PURE__ */ jsx("div", {
603
+ "data-author-region": annotation.ordinal,
604
+ className: "pointer-events-none absolute border border-doom-yellow bg-doom-yellow/10",
605
+ style: {
606
+ left: `${String(annotation.rect.x * 100)}%`,
607
+ top: `${String(annotation.rect.y * 100)}%`,
608
+ width: `${String(annotation.rect.width * 100)}%`,
609
+ height: `${String(annotation.rect.height * 100)}%`
610
+ },
611
+ children: /* @__PURE__ */ jsx("span", {
612
+ className: "bg-doom-yellow text-doom-deep",
613
+ children: annotation.ordinal
614
+ })
615
+ }, annotation.ordinal)),
616
+ annotationRect === void 0 ? null : /* @__PURE__ */ jsx("div", {
617
+ className: "pointer-events-none absolute border-2 border-red-500 bg-red-500/10",
618
+ style: {
619
+ left: `${String(annotationRect.x * 100)}%`,
620
+ top: `${String(annotationRect.y * 100)}%`,
621
+ width: `${String(annotationRect.width * 100)}%`,
622
+ height: `${String(annotationRect.height * 100)}%`
623
+ }
624
+ })
625
+ ]
626
+ })
479
627
  }) : /* @__PURE__ */ jsx("iframe", {
480
628
  title: `${preview.storyExport} story preview`,
481
629
  sandbox: "allow-scripts",
@@ -483,7 +631,7 @@ function StoryPreviewPanel({ sessionId, submitCapture, seed, source }) {
483
631
  srcDoc: isolatedPreviewHtml(preview.html),
484
632
  className: "min-h-0 flex-1 rounded border border-doom-border bg-white"
485
633
  }, preview.handle),
486
- annotationImage === void 0 ? null : /* @__PURE__ */ jsxs("div", {
634
+ annotationImage === void 0 || onAnnotationCandidate !== void 0 ? null : /* @__PURE__ */ jsxs("div", {
487
635
  className: "grid gap-2",
488
636
  children: [/* @__PURE__ */ jsxs("div", {
489
637
  className: "relative max-h-80 w-fit max-w-full touch-none overflow-hidden rounded border border-doom-border",
@@ -522,7 +670,7 @@ function StoryPreviewPanel({ sessionId, submitCapture, seed, source }) {
522
670
  children: "This image is rebuilt from source and does not preserve transient iframe interactions."
523
671
  })]
524
672
  }),
525
- preview === void 0 ? null : /* @__PURE__ */ jsxs("div", {
673
+ preview === void 0 || onAnnotationCandidate !== void 0 ? null : /* @__PURE__ */ jsxs("div", {
526
674
  className: "flex gap-2",
527
675
  children: [/* @__PURE__ */ jsxs("label", {
528
676
  className: "grid min-w-0 flex-1 gap-1 text-xs text-doom-dim",
@@ -1 +1 @@
1
- {"version":3,"file":"web.mjs","names":["routes","fillStyleSystem"],"sources":["../../src/types/apiRoutes.ts","../../generated/client.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/previewApi.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/previewFrame.ts","../../src/extensions/workspaces/sessions/(frontend)/_components/StoryPreviewPanel.tsx","../../src/extensions/workspaces/sessions/(frontend)/_components/StoryPreviewLauncher.tsx","../../src/extensions/workspaces/sessions/(frontend)/fill/StyleSystem.author.preview-action.web.tsx","../../generated/web.ts"],"sourcesContent":["import { apiResponse, defineApiRoutes } from '@agimon-ai/doompi-core/web';\n\nimport type {\n BuildStoryPreviewView,\n DisposeStoryPreviewView,\n ExportStoryPreviewImageView,\n StoryPreviewMetadataView,\n} from './previewApi';\n\nexport default defineApiRoutes({\n metadata: {\n method: 'POST',\n path: '/metadata',\n response: apiResponse<StoryPreviewMetadataView>(),\n },\n build: {\n method: 'POST',\n path: '/build',\n response: apiResponse<BuildStoryPreviewView>(),\n },\n image: {\n method: 'POST',\n path: '/image',\n response: apiResponse<ExportStoryPreviewImageView>(),\n },\n dispose: {\n method: 'DELETE',\n path: '/artifact',\n response: apiResponse<DisposeStoryPreviewView>(),\n },\n});\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { createApiClient, sessionApiAddress } from '@agimon-ai/doompi-core/web';\nimport { sealedTransport } from '@agimon-ai/doompi-web-security/browser';\n\nimport routes from '../src/types/apiRoutes';\n\n/** Scopes the tree mounts 'style-system-preview' at. */\nexport const apiScopes = ['session'] as const;\n\nexport const api = createApiClient(routes, {\n scopes: apiScopes,\n basePath: 'style-system-preview',\n transport: (input, init) => sealedTransport.fetch(input, init),\n sessionAddress: sessionApiAddress,\n});\n","import { api } from '../../../../../../generated/client';\nimport type {\n BuildStoryPreviewRequest,\n BuildStoryPreviewView,\n ExportStoryPreviewImageView,\n StoryPreviewMetadataRequest,\n StoryPreviewMetadataView,\n} from '../../../../../types/previewApi';\n\nfunction errorMessage(result: { status: number; error: string }): string {\n if (result.status === 0) return 'The preview service is unreachable.';\n return result.error === '' ? `The preview service answered ${String(result.status)}.` : result.error;\n}\n\nexport async function storyMetadata(\n sessionId: string,\n request: StoryPreviewMetadataRequest,\n): Promise<{ ok: true; metadata: StoryPreviewMetadataView } | { ok: false; error: string }> {\n const result = await api.session(sessionId).metadata({ body: request });\n return result.ok ? { ok: true, metadata: result.data } : { ok: false, error: errorMessage(result) };\n}\n\nexport async function buildPreview(\n sessionId: string,\n request: BuildStoryPreviewRequest,\n): Promise<{ ok: true; preview: BuildStoryPreviewView } | { ok: false; error: string }> {\n const result = await api.session(sessionId).build({ body: request });\n return result.ok ? { ok: true, preview: result.data } : { ok: false, error: errorMessage(result) };\n}\n\nexport async function exportPreviewImage(\n sessionId: string,\n request: BuildStoryPreviewRequest,\n): Promise<{ ok: true; image: ExportStoryPreviewImageView } | { ok: false; error: string }> {\n const result = await api.session(sessionId).image({ body: request });\n return result.ok ? { ok: true, image: result.data } : { ok: false, error: errorMessage(result) };\n}\n\nexport async function disposePreview(sessionId: string, handle: string): Promise<void> {\n await api.session(sessionId).dispose({ body: { handle } });\n}\n","const PREVIEW_CSP =\n \"default-src 'none'; base-uri 'none'; connect-src 'none'; font-src data:; form-action 'none'; frame-src 'none'; img-src data: blob:; media-src data: blob:; object-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'\";\n\nexport interface PreviewPoint {\n x: number;\n y: number;\n}\n\nexport interface PreviewAnnotationRect extends PreviewPoint {\n width: number;\n height: number;\n}\n\nfunction findHeadInsertion(html: string): number | undefined {\n const lower = html.toLowerCase();\n let index = 0;\n while (index < html.length) {\n if (lower.startsWith('<!--', index)) {\n const end = lower.indexOf('-->', index + 4);\n index = end < 0 ? html.length : end + 3;\n continue;\n }\n if (html[index] !== '<') {\n index += 1;\n continue;\n }\n const tag = html.slice(index).match(/^<([a-z][a-z0-9:-]*)(?:\\s[^>]*)?>/i);\n if (tag === null) {\n index += 1;\n continue;\n }\n const name = tag[1]!.toLowerCase();\n const end = index + tag[0].length;\n if (name === 'head') return end;\n if (name === 'script' || name === 'style' || name === 'template' || name === 'textarea' || name === 'title') {\n const closing = lower.indexOf(`</${name}`, end);\n index = closing < 0 ? html.length : closing;\n continue;\n }\n index = end;\n }\n return undefined;\n}\n\nexport function isolatedPreviewHtml(html: string): string {\n const policy = `<meta http-equiv=\"Content-Security-Policy\" content=\"${PREVIEW_CSP}\">`;\n const headInsertion = findHeadInsertion(html);\n if (headInsertion !== undefined) return `${html.slice(0, headInsertion)}${policy}${html.slice(headInsertion)}`;\n const doctype = html.match(/^\\s*<!doctype[^>]*>/i);\n if (doctype !== null) return `${doctype[0]}<head>${policy}</head>${html.slice(doctype[0].length)}`;\n return `<head>${policy}</head>${html}`;\n}\n\nexport function normalizedAnnotationRect(start: PreviewPoint, end: PreviewPoint): PreviewAnnotationRect {\n return {\n x: Math.min(start.x, end.x),\n y: Math.min(start.y, end.y),\n width: Math.abs(end.x - start.x),\n height: Math.abs(end.y - start.y),\n };\n}\n","import type { WebPluginSlotProps } from '@agimon-ai/doompi-core/web';\nimport { Button } from '@agimon-ai/doompi-web-components';\nimport { useCallback, useEffect, useMemo, useRef, useState, type PointerEvent as ReactPointerEvent } from 'react';\n\nimport type {\n BuildStoryPreviewRequest,\n BuildStoryPreviewView,\n ExportStoryPreviewImageView,\n StoryPreviewMetadataView,\n StoryPreviewSeed,\n} from '../../../../../types/previewApi';\nimport { buildPreview, disposePreview, exportPreviewImage, storyMetadata } from '../_lib/previewApi';\nimport {\n isolatedPreviewHtml,\n normalizedAnnotationRect,\n type PreviewAnnotationRect,\n type PreviewPoint,\n} from '../_lib/previewFrame';\n\nfunction imageDownload(data: string, storyExport: string): void {\n const link = document.createElement('a');\n link.href = `data:image/png;base64,${data}`;\n link.download = `${storyExport.replace(/[^A-Za-z0-9_-]/g, '-')}.png`;\n link.click();\n}\n\nfunction point(event: ReactPointerEvent<HTMLElement>): PreviewPoint {\n const bounds = event.currentTarget.getBoundingClientRect();\n return {\n x: Math.min(1, Math.max(0, (event.clientX - bounds.left) / bounds.width)),\n y: Math.min(1, Math.max(0, (event.clientY - bounds.top) / bounds.height)),\n };\n}\n\ninterface StoryPreviewSource {\n path: string;\n hasUnsavedChanges: boolean;\n kind?: string;\n revision?: number;\n sourceSha256?: string;\n}\n\ninterface StoryPreviewPanelProps extends WebPluginSlotProps {\n seed?: StoryPreviewSeed;\n source?: StoryPreviewSource;\n}\n\nfunction preferredExport(exports: readonly StoryPreviewMetadataView['exports'][number][]): string {\n return exports.find((entry) => entry.exportName === 'Playground')?.exportName ?? exports[0]?.exportName ?? '';\n}\n\nfunction errorText(reason: unknown): string {\n return reason instanceof Error ? reason.message : String(reason);\n}\n\nexport function StoryPreviewPanel({ sessionId, submitCapture, seed, source }: StoryPreviewPanelProps) {\n const contextualSource = source ?? seed?.source;\n const sourcePath = contextualSource?.path ?? '';\n const sourceDirty = contextualSource?.hasUnsavedChanges === true;\n const sourceRevision = contextualSource?.revision ?? 0;\n const sourceSha256 = contextualSource?.sourceSha256 ?? '';\n const contextual = contextualSource !== undefined;\n const [appPath, setAppPath] = useState(contextual ? '' : '.');\n const [storyPath, setStoryPath] = useState(sourcePath);\n const [storyExport, setStoryExport] = useState(contextual ? '' : 'Playground');\n const [storyExports, setStoryExports] = useState<\n readonly StoryPreviewMetadataView['exports'][number][] | undefined\n >();\n const [darkMode, setDarkMode] = useState(false);\n const [preview, setPreview] = useState<BuildStoryPreviewView | undefined>(undefined);\n const [builtRequest, setBuiltRequest] = useState<BuildStoryPreviewRequest | undefined>(undefined);\n const [annotationImage, setAnnotationImage] = useState<ExportStoryPreviewImageView | undefined>(undefined);\n const [annotationStart, setAnnotationStart] = useState<PreviewPoint | undefined>(undefined);\n const [annotationRect, setAnnotationRect] = useState<PreviewAnnotationRect | undefined>(undefined);\n const [feedback, setFeedback] = useState('');\n const [status, setStatus] = useState(\n contextual\n ? sourceDirty\n ? 'Loading story metadata. Preview uses saved source.'\n : 'Loading story metadata…'\n : 'Choose an exact story file and export.',\n );\n const [working, setWorking] = useState(false);\n const generationRef = useRef(0);\n const operationRef = useRef(0);\n const previewRef = useRef<BuildStoryPreviewView | undefined>(undefined);\n const builtRequestRef = useRef<BuildStoryPreviewRequest | undefined>(undefined);\n const workingRef = useRef(false);\n const dirtyRef = useRef(sourceDirty);\n\n dirtyRef.current = sourceDirty;\n\n const request = useMemo<BuildStoryPreviewRequest>(\n () => ({ appPath, storyPath, storyExport, darkMode }),\n [appPath, storyPath, storyExport, darkMode],\n );\n\n useEffect(() => {\n operationRef.current += 1;\n if (!workingRef.current) return;\n workingRef.current = false;\n setWorking(false);\n }, [request]);\n\n useEffect(() => {\n const generation = generationRef.current + 1;\n generationRef.current = generation;\n operationRef.current += 1;\n workingRef.current = false;\n setWorking(false);\n setStoryExports(undefined);\n setAppPath(contextual ? '' : '.');\n setStoryPath(sourcePath);\n setStoryExport(contextual ? '' : 'Playground');\n builtRequestRef.current = undefined;\n setBuiltRequest(undefined);\n setAnnotationImage(undefined);\n setAnnotationStart(undefined);\n setAnnotationRect(undefined);\n\n if (sessionId === null || sourcePath === '') {\n setPreview(undefined);\n previewRef.current = undefined;\n return () => {\n if (generationRef.current === generation) generationRef.current += 1;\n };\n }\n\n const previous = previewRef.current;\n previewRef.current = undefined;\n setPreview(undefined);\n if (previous !== undefined) void disposePreview(sessionId, previous.handle);\n\n let current = true;\n setStatus(sourceDirty ? 'Loading story metadata. Preview uses saved source.' : 'Loading story metadata…');\n void storyMetadata(sessionId, { storyPath: sourcePath })\n .then((result) => {\n if (!current || generationRef.current !== generation) return;\n if (!result.ok) {\n setStoryExports(undefined);\n setStatus(result.error);\n return;\n }\n const exports = result.metadata.exports;\n setAppPath(result.metadata.appPath);\n setStoryPath(result.metadata.storyPath);\n setStoryExports(exports);\n setStoryExport(preferredExport(exports));\n if (exports.length === 0) {\n setStatus('No story exports were found in this file.');\n } else if (dirtyRef.current) {\n setStatus('Save changes to preview. Preview uses saved source.');\n } else if (exports.length === 1) {\n setStatus('Story found. Building preview…');\n } else {\n setStatus('Story found. Building the Playground export…');\n }\n })\n .catch((reason: unknown) => {\n if (current && generationRef.current === generation) setStatus(errorText(reason));\n });\n\n return () => {\n current = false;\n if (generationRef.current === generation) generationRef.current += 1;\n operationRef.current += 1;\n const ownedPreview = previewRef.current;\n previewRef.current = undefined;\n if (ownedPreview !== undefined) void disposePreview(sessionId, ownedPreview.handle);\n };\n }, [contextual, sessionId, sourcePath, sourceRevision, sourceSha256]);\n\n useEffect(() => {\n if (!contextual || !sourceDirty) return;\n setStatus('Save changes to preview. Preview uses saved source.');\n }, [contextual, sourceDirty]);\n\n const rebuild = useCallback(async (): Promise<boolean> => {\n if (\n sessionId === null ||\n workingRef.current ||\n request.appPath.trim() === '' ||\n request.storyPath.trim() === '' ||\n request.storyExport.trim() === ''\n )\n return false;\n if (dirtyRef.current) {\n setStatus('Save changes to preview. Preview uses saved source.');\n return false;\n }\n\n const generation = generationRef.current;\n const operation = operationRef.current + 1;\n operationRef.current = operation;\n const requestSessionId = sessionId;\n workingRef.current = true;\n setWorking(true);\n setStatus('Building preview…');\n try {\n const result = await buildPreview(requestSessionId, request);\n if (generationRef.current !== generation || operationRef.current !== operation) {\n if (result.ok) void disposePreview(requestSessionId, result.preview.handle);\n return false;\n }\n if (!result.ok) {\n setStatus(result.error);\n return false;\n }\n const previous = previewRef.current;\n previewRef.current = result.preview;\n builtRequestRef.current = request;\n setPreview(result.preview);\n setBuiltRequest(request);\n setAnnotationImage(undefined);\n setAnnotationStart(undefined);\n setAnnotationRect(undefined);\n if (previous !== undefined && previous.handle !== result.preview.handle) {\n void disposePreview(requestSessionId, previous.handle);\n }\n setStatus(\n dirtyRef.current\n ? 'Preview ready from saved source. Save changes to refresh.'\n : 'Preview ready. Refresh after source edits.',\n );\n return true;\n } catch (reason: unknown) {\n if (generationRef.current === generation && operationRef.current === operation) setStatus(errorText(reason));\n return false;\n } finally {\n if (generationRef.current === generation && operationRef.current === operation) {\n workingRef.current = false;\n setWorking(false);\n }\n }\n }, [request, sessionId]);\n\n useEffect(() => {\n if (\n !contextual ||\n sessionId === null ||\n sourceDirty ||\n storyExports === undefined ||\n storyExport.trim() === '' ||\n appPath.trim() === '' ||\n storyPath.trim() === ''\n )\n return;\n void rebuild();\n }, [appPath, contextual, rebuild, sessionId, sourceDirty, storyExport, storyExports, storyPath]);\n\n const image = useCallback(async (): Promise<ExportStoryPreviewImageView | undefined> => {\n const currentRequest = builtRequestRef.current;\n if (sessionId === null || workingRef.current || currentRequest === undefined) return undefined;\n const generation = generationRef.current;\n const operation = operationRef.current + 1;\n operationRef.current = operation;\n const requestSessionId = sessionId;\n workingRef.current = true;\n setWorking(true);\n setStatus('Rendering a fresh source-backed image…');\n try {\n const result = await exportPreviewImage(requestSessionId, currentRequest);\n if (generationRef.current !== generation || operationRef.current !== operation) return undefined;\n if (!result.ok) {\n setStatus(result.error);\n return undefined;\n }\n setStatus('Image rendered from the current source. Transient iframe interactions are not included.');\n return result.image;\n } catch (reason: unknown) {\n if (generationRef.current === generation && operationRef.current === operation) setStatus(errorText(reason));\n return undefined;\n } finally {\n if (generationRef.current === generation && operationRef.current === operation) {\n workingRef.current = false;\n setWorking(false);\n }\n }\n }, [sessionId]);\n\n async function prepareAnnotation(): Promise<void> {\n const generation = generationRef.current;\n const result = await image();\n if (result === undefined || generationRef.current !== generation) return;\n setAnnotationImage(result);\n setAnnotationRect(undefined);\n setStatus('Annotation image ready. Drag a region or comment on the whole preview.');\n }\n\n function updateAnnotation(event: ReactPointerEvent<HTMLDivElement>): void {\n if (annotationStart === undefined) return;\n const end = point(event);\n setAnnotationRect(normalizedAnnotationRect(annotationStart, end));\n }\n\n const multipleExports = storyExports !== undefined && storyExports.length > 1;\n const previewVisible = preview !== undefined;\n\n return (\n <section className=\"flex h-full min-h-0 min-w-0 flex-1 flex-col gap-3 p-3\" data-testid=\"style-system-preview-panel\">\n {contextual ? (\n <header className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-doom-dim\">\n <span>\n Story: <strong className=\"text-doom-text\">{storyPath || sourcePath}</strong>\n </span>\n <span>\n Project: <strong className=\"text-doom-text\">{appPath || 'resolving…'}</strong>\n </span>\n {multipleExports ? (\n <label className=\"flex items-center gap-1\">\n Export\n <select\n className=\"rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text\"\n value={storyExport}\n aria-label=\"story export\"\n onChange={(event) => {\n setStoryExport(event.target.value);\n builtRequestRef.current = undefined;\n setBuiltRequest(undefined);\n setAnnotationImage(undefined);\n setAnnotationRect(undefined);\n }}\n >\n {storyExports.map((entry) => (\n <option key={entry.exportName} value={entry.exportName}>\n {entry.label ?? entry.exportName}\n </option>\n ))}\n </select>\n </label>\n ) : storyExport !== '' ? (\n <span>\n Export: <strong className=\"text-doom-text\">{storyExport}</strong>\n </span>\n ) : null}\n </header>\n ) : (\n <header className=\"grid gap-2 md:grid-cols-3\">\n <label className=\"grid gap-1 text-xs text-doom-dim\">\n Project path\n <input\n className=\"rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text\"\n value={appPath}\n onChange={(event) => setAppPath(event.target.value)}\n />\n </label>\n <label className=\"grid gap-1 text-xs text-doom-dim\">\n Story file\n <input\n className=\"rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text\"\n placeholder=\"packages/ui/Button.stories.tsx\"\n value={storyPath}\n onChange={(event) => {\n setStoryPath(event.target.value);\n setStoryExports(undefined);\n }}\n />\n </label>\n <label className=\"grid gap-1 text-xs text-doom-dim\">\n Story export\n {storyExports === undefined ? (\n <input\n className=\"rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text\"\n value={storyExport}\n onChange={(event) => setStoryExport(event.target.value)}\n />\n ) : (\n <select\n className=\"rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text\"\n value={storyExport}\n onChange={(event) => setStoryExport(event.target.value)}\n >\n <option value=\"\">Choose an export</option>\n {storyExports.map((entry) => (\n <option key={entry.exportName} value={entry.exportName}>\n {entry.label ?? entry.exportName}\n </option>\n ))}\n </select>\n )}\n </label>\n </header>\n )}\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button\n disabled={\n sessionId === null ||\n working ||\n sourceDirty ||\n request.storyPath.trim() === '' ||\n request.storyExport.trim() === ''\n }\n onClick={() => void rebuild()}\n >\n {previewVisible ? 'Refresh preview' : 'Build preview'}\n </Button>\n <Button\n variant=\"outline\"\n disabled={preview === undefined || working}\n onClick={() => void image().then((result) => result && imageDownload(result.data, result.storyExport))}\n >\n Export PNG\n </Button>\n <Button variant=\"outline\" disabled={preview === undefined || working} onClick={() => void prepareAnnotation()}>\n Prepare annotation\n </Button>\n <label className=\"flex items-center gap-1 text-xs text-doom-dim\">\n <input type=\"checkbox\" checked={darkMode} onChange={(event) => setDarkMode(event.target.checked)} /> Dark mode\n </label>\n <output className=\"text-xs text-doom-dim\" aria-live=\"polite\">\n {status}\n </output>\n </div>\n {sourceDirty ? (\n <p data-testid=\"style-system-preview-dirty\" className=\"text-xs text-doom-yellow\">\n Save changes to preview. Preview uses saved source.\n </p>\n ) : null}\n {preview === undefined ? (\n <div className=\"grid min-h-48 place-items-center rounded border border-dashed border-doom-border text-sm text-doom-faint\">\n No preview built.\n </div>\n ) : (\n <iframe\n key={preview.handle}\n title={`${preview.storyExport} story preview`}\n sandbox=\"allow-scripts\"\n referrerPolicy=\"no-referrer\"\n srcDoc={isolatedPreviewHtml(preview.html)}\n className=\"min-h-0 flex-1 rounded border border-doom-border bg-white\"\n />\n )}\n {annotationImage === undefined ? null : (\n <div className=\"grid gap-2\">\n <div\n className=\"relative max-h-80 w-fit max-w-full touch-none overflow-hidden rounded border border-doom-border\"\n aria-label=\"Drag to mark an annotation region\"\n onPointerDown={(event) => {\n event.currentTarget.setPointerCapture(event.pointerId);\n const start = point(event);\n setAnnotationStart(start);\n setAnnotationRect({ ...start, width: 0, height: 0 });\n }}\n onPointerMove={updateAnnotation}\n onPointerUp={(event) => {\n updateAnnotation(event);\n setAnnotationStart(undefined);\n }}\n >\n <img\n className=\"block max-h-80 max-w-full select-none\"\n src={`data:${annotationImage.mimeType};base64,${annotationImage.data}`}\n alt=\"Fresh source-backed story render for annotation\"\n draggable={false}\n />\n {annotationRect === undefined ? null : (\n <div\n className=\"pointer-events-none absolute border-2 border-red-500 bg-red-500/10\"\n style={{\n left: `${String(annotationRect.x * 100)}%`,\n top: `${String(annotationRect.y * 100)}%`,\n width: `${String(annotationRect.width * 100)}%`,\n height: `${String(annotationRect.height * 100)}%`,\n }}\n />\n )}\n </div>\n <p className=\"text-xs text-doom-faint\">\n This image is rebuilt from source and does not preserve transient iframe interactions.\n </p>\n </div>\n )}\n {preview === undefined ? null : (\n <div className=\"flex gap-2\">\n <label className=\"grid min-w-0 flex-1 gap-1 text-xs text-doom-dim\">\n AI design feedback\n <textarea\n className=\"min-h-20 rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text\"\n value={feedback}\n onChange={(event) => setFeedback(event.target.value)}\n />\n </label>\n <Button\n className=\"self-end\"\n variant=\"outline\"\n disabled={working || submitCapture === undefined || feedback.trim() === '' || annotationImage === undefined}\n onClick={() => {\n if (submitCapture === undefined || annotationImage === undefined) return;\n const location =\n annotationRect === undefined\n ? 'Whole preview'\n : `Region: x=${annotationRect.x.toFixed(3)}, y=${annotationRect.y.toFixed(3)}, width=${annotationRect.width.toFixed(3)}, height=${annotationRect.height.toFixed(3)}`;\n const generation = generationRef.current;\n void submitCapture({\n ...annotationImage,\n context: {\n kind: 'author-capture',\n source: 'style-system',\n id: crypto.randomUUID(),\n label: `${annotationImage.storyPath}#${annotationImage.storyExport}`,\n content: [\n 'Edit the underlying story or component source, never the generated preview HTML.',\n `Project: ${builtRequest?.appPath ?? appPath}`,\n `Story: ${annotationImage.storyPath}#${annotationImage.storyExport}`,\n `Story source sha256: ${annotationImage.sourceSha256}`,\n `Source-backed image revision: ${annotationImage.sourceSha256}`,\n location,\n `Feedback: ${feedback.trim()}`,\n 'After editing, rebuild this preview before treating it as visually verified.',\n ].join('\\n'),\n },\n })\n .then(() => {\n if (generationRef.current !== generation) return;\n setStatus('Feedback and the frozen source-backed annotation image were submitted to the agent.');\n setFeedback('');\n })\n .catch((reason: unknown) => {\n if (generationRef.current === generation) setStatus(errorText(reason));\n });\n }}\n >\n Send to AI\n </Button>\n </div>\n )}\n </section>\n );\n}\n","import type { TransientTab } from '@agimon-ai/doompi-core/web';\n\nimport type { StoryPreviewSeed } from '../../../../../types/previewApi';\nimport { StoryPreviewPanel } from './StoryPreviewPanel';\n\nfunction sourceKey(path: string): string {\n let hash = 2_166_136_261;\n for (const character of path) hash = Math.imul(hash ^ character.codePointAt(0)!, 16_777_619);\n return (hash >>> 0).toString(36);\n}\n\nexport function storyPreviewTab(seed?: StoryPreviewSeed): TransientTab {\n const sourcePath = seed?.source?.path;\n return {\n id: sourcePath === undefined ? 'style-system-story-preview' : `style-system-story-preview-${sourceKey(sourcePath)}`,\n label: 'Story preview',\n panel: (props) => <StoryPreviewPanel {...props} seed={seed} />,\n retainComposer: true,\n };\n}\n","import { defineFill } from '@agimon-ai/doompi-core/web';\n\nimport { storyPreviewTab } from '../_components/StoryPreviewLauncher';\nimport { StoryPreviewPanel } from '../_components/StoryPreviewPanel';\n\nexport default defineFill({\n slot: 'author.preview-action',\n id: 'style-system',\n data: {\n version: 1 as const,\n label: 'Preview',\n detail: 'Build, inspect, refresh, and send source-targeted visual feedback.',\n supportsSource: (source: { kind?: string }) => source.kind === 'story-preview',\n embeddedPanel: StoryPreviewPanel,\n createTab: ({ source }: { source?: { path: string; hasUnsavedChanges: boolean } }) =>\n storyPreviewTab(source === undefined ? undefined : { source }),\n },\n});\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineWebPlugin, type WebPluginContributions } from '@agimon-ai/doompi-core/web';\n\nimport fillStyleSystem from '../src/extensions/workspaces/sessions/(frontend)/fill/StyleSystem.author.preview-action.web';\n\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T => ({ ...identity, ...(value as object) }) as T;\n\nexport const webPlugin = defineWebPlugin({\n id: 'style-system',\n session: {\n get fills(): WebPluginContributions['fills'] { return [via({ slot: 'author.preview-action', id: 'style-system' }, fillStyleSystem)]; },\n },\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"web.mjs","names":["routes","fillStyleSystem"],"sources":["../../src/types/apiRoutes.ts","../../generated/client.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/previewApi.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/previewFrame.ts","../../src/extensions/workspaces/sessions/(frontend)/_components/StoryPreviewPanel.tsx","../../src/extensions/workspaces/sessions/(frontend)/_components/StoryPreviewLauncher.tsx","../../src/extensions/workspaces/sessions/(frontend)/fill/StyleSystem.author.preview-action.web.tsx","../../generated/web.ts"],"sourcesContent":["import { apiResponse, defineApiRoutes } from '@agimon-ai/doompi-core/web';\n\nimport type {\n BuildStoryPreviewView,\n DisposeStoryPreviewView,\n ExportStoryPreviewImageView,\n StoryPreviewMetadataView,\n} from './previewApi';\n\nexport default defineApiRoutes({\n metadata: {\n method: 'POST',\n path: '/metadata',\n response: apiResponse<StoryPreviewMetadataView>(),\n },\n build: {\n method: 'POST',\n path: '/build',\n response: apiResponse<BuildStoryPreviewView>(),\n },\n image: {\n method: 'POST',\n path: '/image',\n response: apiResponse<ExportStoryPreviewImageView>(),\n },\n dispose: {\n method: 'DELETE',\n path: '/artifact',\n response: apiResponse<DisposeStoryPreviewView>(),\n },\n});\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { createApiClient, sessionApiAddress } from '@agimon-ai/doompi-core/web';\nimport { sealedTransport } from '@agimon-ai/doompi-web-security/browser';\n\nimport routes from '../src/types/apiRoutes';\n\n/** Scopes the tree mounts 'style-system-preview' at. */\nexport const apiScopes = ['session'] as const;\n\nexport const api = createApiClient(routes, {\n scopes: apiScopes,\n basePath: 'style-system-preview',\n transport: (input, init) => sealedTransport.fetch(input, init),\n sessionAddress: sessionApiAddress,\n});\n","import { api } from '../../../../../../generated/client';\nimport type {\n BuildStoryPreviewRequest,\n BuildStoryPreviewView,\n ExportStoryPreviewImageView,\n StoryPreviewMetadataRequest,\n StoryPreviewMetadataView,\n} from '../../../../../types/previewApi';\n\nfunction errorMessage(result: { status: number; error: string }): string {\n if (result.status === 0) return 'The preview service is unreachable.';\n return result.error === '' ? `The preview service answered ${String(result.status)}.` : result.error;\n}\n\nexport async function storyMetadata(\n sessionId: string,\n request: StoryPreviewMetadataRequest,\n): Promise<{ ok: true; metadata: StoryPreviewMetadataView } | { ok: false; error: string }> {\n const result = await api.session(sessionId).metadata({ body: request });\n return result.ok ? { ok: true, metadata: result.data } : { ok: false, error: errorMessage(result) };\n}\n\nexport async function buildPreview(\n sessionId: string,\n request: BuildStoryPreviewRequest,\n): Promise<{ ok: true; preview: BuildStoryPreviewView } | { ok: false; error: string }> {\n const result = await api.session(sessionId).build({ body: request });\n return result.ok ? { ok: true, preview: result.data } : { ok: false, error: errorMessage(result) };\n}\n\nexport async function exportPreviewImage(\n sessionId: string,\n request: BuildStoryPreviewRequest,\n): Promise<{ ok: true; image: ExportStoryPreviewImageView } | { ok: false; error: string }> {\n const result = await api.session(sessionId).image({ body: request });\n return result.ok ? { ok: true, image: result.data } : { ok: false, error: errorMessage(result) };\n}\n\nexport async function disposePreview(sessionId: string, handle: string): Promise<void> {\n await api.session(sessionId).dispose({ body: { handle } });\n}\n","const PREVIEW_CSP =\n \"default-src 'none'; base-uri 'none'; connect-src 'none'; font-src data:; form-action 'none'; frame-src 'none'; img-src data: blob:; media-src data: blob:; object-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'\";\n\nexport interface PreviewPoint {\n x: number;\n y: number;\n}\n\nexport interface PreviewAnnotationRect extends PreviewPoint {\n width: number;\n height: number;\n}\n\nfunction findHeadInsertion(html: string): number | undefined {\n const lower = html.toLowerCase();\n let index = 0;\n while (index < html.length) {\n if (lower.startsWith('<!--', index)) {\n const end = lower.indexOf('-->', index + 4);\n index = end < 0 ? html.length : end + 3;\n continue;\n }\n if (html[index] !== '<') {\n index += 1;\n continue;\n }\n const tag = html.slice(index).match(/^<([a-z][a-z0-9:-]*)(?:\\s[^>]*)?>/i);\n if (tag === null) {\n index += 1;\n continue;\n }\n const name = tag[1]!.toLowerCase();\n const end = index + tag[0].length;\n if (name === 'head') return end;\n if (name === 'script' || name === 'style' || name === 'template' || name === 'textarea' || name === 'title') {\n const closing = lower.indexOf(`</${name}`, end);\n index = closing < 0 ? html.length : closing;\n continue;\n }\n index = end;\n }\n return undefined;\n}\n\nexport function isolatedPreviewHtml(html: string): string {\n const policy = `<meta http-equiv=\"Content-Security-Policy\" content=\"${PREVIEW_CSP}\">`;\n const headInsertion = findHeadInsertion(html);\n if (headInsertion !== undefined) return `${html.slice(0, headInsertion)}${policy}${html.slice(headInsertion)}`;\n const doctype = html.match(/^\\s*<!doctype[^>]*>/i);\n if (doctype !== null) return `${doctype[0]}<head>${policy}</head>${html.slice(doctype[0].length)}`;\n return `<head>${policy}</head>${html}`;\n}\n\nexport function normalizedAnnotationRect(start: PreviewPoint, end: PreviewPoint): PreviewAnnotationRect {\n return {\n x: Math.min(start.x, end.x),\n y: Math.min(start.y, end.y),\n width: Math.abs(end.x - start.x),\n height: Math.abs(end.y - start.y),\n };\n}\n\nexport function previewAnnotationLocation(\n tool: 'mark' | 'comment',\n start: PreviewPoint,\n end: PreviewPoint,\n):\n | { mode: 'region'; point: PreviewPoint; rect: PreviewAnnotationRect }\n | { mode: 'point'; point: PreviewPoint }\n | null {\n if (tool === 'comment') return { mode: 'point', point: end };\n const rect = normalizedAnnotationRect(start, end);\n return rect.width === 0 || rect.height === 0 ? null : { mode: 'region', point: rect, rect };\n}\n","import type { WebPluginSlotProps } from '@agimon-ai/doompi-core/web';\nimport { Button } from '@agimon-ai/doompi-web-components';\nimport { useCallback, useEffect, useMemo, useRef, useState, type PointerEvent as ReactPointerEvent } from 'react';\n\nimport type {\n BuildStoryPreviewRequest,\n BuildStoryPreviewView,\n ExportStoryPreviewImageView,\n StoryPreviewMetadataView,\n StoryPreviewSeed,\n} from '../../../../../types/previewApi';\nimport { buildPreview, disposePreview, exportPreviewImage, storyMetadata } from '../_lib/previewApi';\nimport {\n isolatedPreviewHtml,\n normalizedAnnotationRect,\n previewAnnotationLocation,\n type PreviewAnnotationRect,\n type PreviewPoint,\n} from '../_lib/previewFrame';\n\nfunction imageDownload(data: string, storyExport: string): void {\n const link = document.createElement('a');\n link.href = `data:image/png;base64,${data}`;\n link.download = `${storyExport.replace(/[^A-Za-z0-9_-]/g, '-')}.png`;\n link.click();\n}\n\nfunction point(event: ReactPointerEvent<HTMLElement>): PreviewPoint {\n const bounds = event.currentTarget.getBoundingClientRect();\n return {\n x: Math.min(1, Math.max(0, (event.clientX - bounds.left) / bounds.width)),\n y: Math.min(1, Math.max(0, (event.clientY - bounds.top) / bounds.height)),\n };\n}\n\ninterface StoryPreviewSource {\n path: string;\n hasUnsavedChanges: boolean;\n kind?: string;\n revision?: number;\n sourceSha256?: string;\n}\n\ninterface StoryPreviewIdentity {\n version: 1;\n provider: string;\n projectPath: string;\n storyPath: string;\n storyExport: string;\n buildRevision: string;\n viewport: { width: number; height: number };\n sources: readonly { path: string; sha256: string }[];\n}\n\ninterface StoryPreviewPanelProps extends WebPluginSlotProps {\n seed?: StoryPreviewSeed;\n source?: StoryPreviewSource;\n activeTool?: 'select' | 'mark' | 'comment';\n displayedAnnotations?: readonly {\n ordinal: number;\n mode: 'region' | 'point';\n point: PreviewPoint;\n rect?: PreviewAnnotationRect;\n }[];\n pendingCandidate?: boolean;\n onAnnotationCandidate?: (candidate: {\n mode: 'region' | 'point';\n point: PreviewPoint;\n rect?: PreviewAnnotationRect;\n preview: StoryPreviewIdentity;\n evidence: Blob;\n thumbnailUrl: string;\n }) => void;\n}\n\nfunction preferredExport(exports: readonly StoryPreviewMetadataView['exports'][number][]): string {\n return exports.find((entry) => entry.exportName === 'Playground')?.exportName ?? exports[0]?.exportName ?? '';\n}\n\nfunction errorText(reason: unknown): string {\n return reason instanceof Error ? reason.message : String(reason);\n}\n\nexport function StoryPreviewPanel({\n sessionId,\n submitCapture,\n seed,\n source,\n activeTool = 'select',\n displayedAnnotations = [],\n pendingCandidate = false,\n onAnnotationCandidate,\n}: StoryPreviewPanelProps) {\n const contextualSource = source ?? seed?.source;\n const sourcePath = contextualSource?.path ?? '';\n const sourceDirty = contextualSource?.hasUnsavedChanges === true;\n const sourceRevision = contextualSource?.revision ?? 0;\n const sourceSha256 = contextualSource?.sourceSha256 ?? '';\n const contextual = contextualSource !== undefined;\n const [appPath, setAppPath] = useState(contextual ? '' : '.');\n const [storyPath, setStoryPath] = useState(sourcePath);\n const [storyExport, setStoryExport] = useState(contextual ? '' : 'Playground');\n const [storyExports, setStoryExports] = useState<\n readonly StoryPreviewMetadataView['exports'][number][] | undefined\n >();\n const [darkMode, setDarkMode] = useState(false);\n const [preview, setPreview] = useState<BuildStoryPreviewView | undefined>(undefined);\n const [builtRequest, setBuiltRequest] = useState<BuildStoryPreviewRequest | undefined>(undefined);\n const [annotationImage, setAnnotationImage] = useState<ExportStoryPreviewImageView | undefined>(undefined);\n const [annotationStart, setAnnotationStart] = useState<PreviewPoint | undefined>(undefined);\n const [annotationRect, setAnnotationRect] = useState<PreviewAnnotationRect | undefined>(undefined);\n const [feedback, setFeedback] = useState('');\n const [status, setStatus] = useState(\n contextual\n ? sourceDirty\n ? 'Loading story metadata. Preview uses saved source.'\n : 'Loading story metadata…'\n : 'Choose an exact story file and export.',\n );\n const [working, setWorking] = useState(false);\n const generationRef = useRef(0);\n const operationRef = useRef(0);\n const previewRef = useRef<BuildStoryPreviewView | undefined>(undefined);\n const builtRequestRef = useRef<BuildStoryPreviewRequest | undefined>(undefined);\n const workingRef = useRef(false);\n const dirtyRef = useRef(sourceDirty);\n const annotationExportRef = useRef('');\n\n dirtyRef.current = sourceDirty;\n\n const request = useMemo<BuildStoryPreviewRequest>(\n () => ({ appPath, storyPath, storyExport, darkMode }),\n [appPath, storyPath, storyExport, darkMode],\n );\n\n useEffect(() => {\n operationRef.current += 1;\n if (!workingRef.current) return;\n workingRef.current = false;\n setWorking(false);\n }, [request]);\n\n useEffect(() => {\n const generation = generationRef.current + 1;\n generationRef.current = generation;\n operationRef.current += 1;\n workingRef.current = false;\n setWorking(false);\n setStoryExports(undefined);\n setAppPath(contextual ? '' : '.');\n setStoryPath(sourcePath);\n setStoryExport(contextual ? '' : 'Playground');\n builtRequestRef.current = undefined;\n setBuiltRequest(undefined);\n setAnnotationImage(undefined);\n setAnnotationStart(undefined);\n setAnnotationRect(undefined);\n\n if (sessionId === null || sourcePath === '') {\n setPreview(undefined);\n previewRef.current = undefined;\n return () => {\n if (generationRef.current === generation) generationRef.current += 1;\n };\n }\n\n const previous = previewRef.current;\n previewRef.current = undefined;\n setPreview(undefined);\n if (previous !== undefined) void disposePreview(sessionId, previous.handle);\n\n let current = true;\n setStatus(sourceDirty ? 'Loading story metadata. Preview uses saved source.' : 'Loading story metadata…');\n void storyMetadata(sessionId, { storyPath: sourcePath })\n .then((result) => {\n if (!current || generationRef.current !== generation) return;\n if (!result.ok) {\n setStoryExports(undefined);\n setStatus(result.error);\n return;\n }\n const exports = result.metadata.exports;\n setAppPath(result.metadata.appPath);\n setStoryPath(result.metadata.storyPath);\n setStoryExports(exports);\n setStoryExport(preferredExport(exports));\n if (exports.length === 0) {\n setStatus('No story exports were found in this file.');\n } else if (dirtyRef.current) {\n setStatus('Save changes to preview. Preview uses saved source.');\n } else if (exports.length === 1) {\n setStatus('Story found. Building preview…');\n } else {\n setStatus('Story found. Building the Playground export…');\n }\n })\n .catch((reason: unknown) => {\n if (current && generationRef.current === generation) setStatus(errorText(reason));\n });\n\n return () => {\n current = false;\n if (generationRef.current === generation) generationRef.current += 1;\n operationRef.current += 1;\n const ownedPreview = previewRef.current;\n previewRef.current = undefined;\n if (ownedPreview !== undefined) void disposePreview(sessionId, ownedPreview.handle);\n };\n }, [contextual, sessionId, sourcePath, sourceRevision, sourceSha256]);\n\n useEffect(() => {\n if (!contextual || !sourceDirty) return;\n setStatus('Save changes to preview. Preview uses saved source.');\n }, [contextual, sourceDirty]);\n\n const rebuild = useCallback(async (): Promise<boolean> => {\n if (\n sessionId === null ||\n workingRef.current ||\n request.appPath.trim() === '' ||\n request.storyPath.trim() === '' ||\n request.storyExport.trim() === ''\n )\n return false;\n if (dirtyRef.current) {\n setStatus('Save changes to preview. Preview uses saved source.');\n return false;\n }\n\n const generation = generationRef.current;\n const operation = operationRef.current + 1;\n operationRef.current = operation;\n const requestSessionId = sessionId;\n workingRef.current = true;\n setWorking(true);\n setStatus('Building preview…');\n try {\n const result = await buildPreview(requestSessionId, request);\n if (generationRef.current !== generation || operationRef.current !== operation) {\n if (result.ok) void disposePreview(requestSessionId, result.preview.handle);\n return false;\n }\n if (!result.ok) {\n setStatus(result.error);\n return false;\n }\n const previous = previewRef.current;\n previewRef.current = result.preview;\n builtRequestRef.current = request;\n setPreview(result.preview);\n setBuiltRequest(request);\n setAnnotationImage(undefined);\n setAnnotationStart(undefined);\n setAnnotationRect(undefined);\n if (previous !== undefined && previous.handle !== result.preview.handle) {\n void disposePreview(requestSessionId, previous.handle);\n }\n setStatus(\n dirtyRef.current\n ? 'Preview ready from saved source. Save changes to refresh.'\n : 'Preview ready. Refresh after source edits.',\n );\n return true;\n } catch (reason: unknown) {\n if (generationRef.current === generation && operationRef.current === operation) setStatus(errorText(reason));\n return false;\n } finally {\n if (generationRef.current === generation && operationRef.current === operation) {\n workingRef.current = false;\n setWorking(false);\n }\n }\n }, [request, sessionId]);\n\n useEffect(() => {\n if (\n !contextual ||\n sessionId === null ||\n sourceDirty ||\n storyExports === undefined ||\n storyExport.trim() === '' ||\n appPath.trim() === '' ||\n storyPath.trim() === ''\n )\n return;\n void rebuild();\n }, [appPath, contextual, rebuild, sessionId, sourceDirty, storyExport, storyExports, storyPath]);\n\n const image = useCallback(async (): Promise<ExportStoryPreviewImageView | undefined> => {\n const currentRequest = builtRequestRef.current;\n if (sessionId === null || workingRef.current || currentRequest === undefined) return undefined;\n const generation = generationRef.current;\n const operation = operationRef.current + 1;\n operationRef.current = operation;\n const requestSessionId = sessionId;\n workingRef.current = true;\n setWorking(true);\n setStatus('Rendering a fresh source-backed image…');\n try {\n const result = await exportPreviewImage(requestSessionId, currentRequest);\n if (generationRef.current !== generation || operationRef.current !== operation) return undefined;\n if (!result.ok) {\n setStatus(result.error);\n return undefined;\n }\n setStatus('Image rendered from the current source. Transient iframe interactions are not included.');\n return result.image;\n } catch (reason: unknown) {\n if (generationRef.current === generation && operationRef.current === operation) setStatus(errorText(reason));\n return undefined;\n } finally {\n if (generationRef.current === generation && operationRef.current === operation) {\n workingRef.current = false;\n setWorking(false);\n }\n }\n }, [sessionId]);\n\n async function prepareAnnotation(): Promise<void> {\n const generation = generationRef.current;\n const result = await image();\n if (result === undefined || generationRef.current !== generation) return;\n setAnnotationImage(result);\n setAnnotationRect(undefined);\n setStatus('Annotation image ready. Drag a region or comment on the whole preview.');\n }\n\n useEffect(() => {\n if (onAnnotationCandidate === undefined) return;\n if (activeTool === 'select') {\n annotationExportRef.current = '';\n setAnnotationImage(undefined);\n setAnnotationStart(undefined);\n setAnnotationRect(undefined);\n return;\n }\n if (\n preview === undefined ||\n annotationImage !== undefined ||\n working ||\n annotationExportRef.current === preview.handle\n )\n return;\n const generation = generationRef.current;\n void image().then((result) => {\n if (result === undefined || generationRef.current !== generation) return;\n annotationExportRef.current = preview.handle;\n setAnnotationImage(result);\n setStatus('Frozen source-backed image ready for Author annotations.');\n });\n }, [activeTool, annotationImage, image, onAnnotationCandidate, preview, working]);\n\n function updateAnnotation(event: ReactPointerEvent<HTMLDivElement>): void {\n if (annotationStart === undefined) return;\n const end = point(event);\n setAnnotationRect(normalizedAnnotationRect(annotationStart, end));\n }\n\n function authorCandidate(location: PreviewPoint, rect?: PreviewAnnotationRect): void {\n if (annotationImage === undefined || onAnnotationCandidate === undefined || builtRequest === undefined) return;\n const binary = atob(annotationImage.data);\n const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));\n const evidence = new Blob([bytes], { type: annotationImage.mimeType });\n const thumbnailUrl = URL.createObjectURL(evidence);\n try {\n onAnnotationCandidate({\n mode: rect === undefined ? 'point' : 'region',\n point: location,\n ...(rect === undefined ? {} : { rect }),\n preview: {\n version: 1,\n provider: 'style-system',\n projectPath: builtRequest.appPath,\n storyPath: annotationImage.storyPath,\n storyExport: annotationImage.storyExport,\n buildRevision: annotationImage.sourceSha256,\n viewport: { width: annotationImage.width, height: annotationImage.height },\n sources: [{ path: annotationImage.storyPath, sha256: annotationImage.sourceSha256 }],\n },\n evidence,\n thumbnailUrl,\n });\n } catch (reason) {\n URL.revokeObjectURL(thumbnailUrl);\n setStatus(errorText(reason));\n }\n }\n\n const multipleExports = storyExports !== undefined && storyExports.length > 1;\n const previewVisible = preview !== undefined;\n const authorAnnotationActive = onAnnotationCandidate !== undefined && activeTool !== 'select';\n\n return (\n <section className=\"flex h-full min-h-0 min-w-0 flex-1 flex-col gap-3 p-3\" data-testid=\"style-system-preview-panel\">\n {contextual ? (\n <header className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-doom-dim\">\n <span>\n Story: <strong className=\"text-doom-text\">{storyPath || sourcePath}</strong>\n </span>\n <span>\n Project: <strong className=\"text-doom-text\">{appPath || 'resolving…'}</strong>\n </span>\n {multipleExports ? (\n <label className=\"flex items-center gap-1\">\n Export\n <select\n className=\"rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text\"\n value={storyExport}\n aria-label=\"story export\"\n onChange={(event) => {\n setStoryExport(event.target.value);\n builtRequestRef.current = undefined;\n setBuiltRequest(undefined);\n setAnnotationImage(undefined);\n setAnnotationRect(undefined);\n }}\n >\n {storyExports.map((entry) => (\n <option key={entry.exportName} value={entry.exportName}>\n {entry.label ?? entry.exportName}\n </option>\n ))}\n </select>\n </label>\n ) : storyExport !== '' ? (\n <span>\n Export: <strong className=\"text-doom-text\">{storyExport}</strong>\n </span>\n ) : null}\n </header>\n ) : (\n <header className=\"grid gap-2 md:grid-cols-3\">\n <label className=\"grid gap-1 text-xs text-doom-dim\">\n Project path\n <input\n className=\"rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text\"\n value={appPath}\n onChange={(event) => setAppPath(event.target.value)}\n />\n </label>\n <label className=\"grid gap-1 text-xs text-doom-dim\">\n Story file\n <input\n className=\"rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text\"\n placeholder=\"packages/ui/Button.stories.tsx\"\n value={storyPath}\n onChange={(event) => {\n setStoryPath(event.target.value);\n setStoryExports(undefined);\n }}\n />\n </label>\n <label className=\"grid gap-1 text-xs text-doom-dim\">\n Story export\n {storyExports === undefined ? (\n <input\n className=\"rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text\"\n value={storyExport}\n onChange={(event) => setStoryExport(event.target.value)}\n />\n ) : (\n <select\n className=\"rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text\"\n value={storyExport}\n onChange={(event) => setStoryExport(event.target.value)}\n >\n <option value=\"\">Choose an export</option>\n {storyExports.map((entry) => (\n <option key={entry.exportName} value={entry.exportName}>\n {entry.label ?? entry.exportName}\n </option>\n ))}\n </select>\n )}\n </label>\n </header>\n )}\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button\n disabled={\n sessionId === null ||\n working ||\n sourceDirty ||\n request.storyPath.trim() === '' ||\n request.storyExport.trim() === ''\n }\n onClick={() => void rebuild()}\n >\n {previewVisible ? 'Refresh preview' : 'Build preview'}\n </Button>\n <Button\n variant=\"outline\"\n disabled={preview === undefined || working}\n onClick={() => void image().then((result) => result && imageDownload(result.data, result.storyExport))}\n >\n Export PNG\n </Button>\n {onAnnotationCandidate === undefined ? (\n <Button\n variant=\"outline\"\n disabled={preview === undefined || working}\n onClick={() => void prepareAnnotation()}\n >\n Prepare annotation\n </Button>\n ) : null}\n <label className=\"flex items-center gap-1 text-xs text-doom-dim\">\n <input type=\"checkbox\" checked={darkMode} onChange={(event) => setDarkMode(event.target.checked)} /> Dark mode\n </label>\n <output className=\"text-xs text-doom-dim\" aria-live=\"polite\">\n {status}\n </output>\n </div>\n {sourceDirty ? (\n <p data-testid=\"style-system-preview-dirty\" className=\"text-xs text-doom-yellow\">\n Save changes to preview. Preview uses saved source.\n </p>\n ) : null}\n {preview === undefined ? (\n <div className=\"grid min-h-48 place-items-center rounded border border-dashed border-doom-border text-sm text-doom-faint\">\n No preview built.\n </div>\n ) : authorAnnotationActive && annotationImage !== undefined ? (\n <div className=\"grid min-h-0 flex-1 place-items-center overflow-hidden rounded border border-doom-border bg-doom-deep\">\n <div\n className=\"relative max-h-full w-fit max-w-full touch-none overflow-hidden\"\n aria-label={\n activeTool === 'comment' ? 'Click to place an annotation point' : 'Drag to mark an annotation region'\n }\n data-testid=\"style-system-author-annotation-overlay\"\n onPointerDown={(event) => {\n if (pendingCandidate || (activeTool !== 'mark' && activeTool !== 'comment')) return;\n event.currentTarget.setPointerCapture(event.pointerId);\n const start = point(event);\n setAnnotationStart(start);\n setAnnotationRect(activeTool === 'mark' ? { ...start, width: 0, height: 0 } : undefined);\n }}\n onPointerMove={(event) => {\n if (activeTool === 'mark') updateAnnotation(event);\n }}\n onPointerUp={(event) => {\n const start = annotationStart;\n if (start === undefined) return;\n const end = point(event);\n setAnnotationStart(undefined);\n const location = previewAnnotationLocation(activeTool === 'comment' ? 'comment' : 'mark', start, end);\n if (location === null) return;\n setAnnotationRect(location.mode === 'region' ? location.rect : undefined);\n authorCandidate(location.point, location.mode === 'region' ? location.rect : undefined);\n }}\n onPointerCancel={() => {\n setAnnotationStart(undefined);\n setAnnotationRect(undefined);\n }}\n >\n <img\n className=\"block max-h-full max-w-full select-none\"\n src={`data:${annotationImage.mimeType};base64,${annotationImage.data}`}\n alt=\"Frozen source-backed story render for Author annotation\"\n draggable={false}\n />\n {displayedAnnotations.map((annotation) =>\n annotation.mode === 'point' || annotation.rect === undefined ? (\n <span\n key={annotation.ordinal}\n data-author-point={annotation.ordinal}\n className=\"pointer-events-none absolute z-10 flex h-5 min-w-5 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border-2 border-doom-deep bg-doom-yellow px-1 text-2xs font-bold text-doom-deep\"\n style={{\n left: `${String(annotation.point.x * 100)}%`,\n top: `${String(annotation.point.y * 100)}%`,\n }}\n >\n {annotation.ordinal}\n </span>\n ) : (\n <div\n key={annotation.ordinal}\n data-author-region={annotation.ordinal}\n className=\"pointer-events-none absolute border border-doom-yellow bg-doom-yellow/10\"\n style={{\n left: `${String(annotation.rect.x * 100)}%`,\n top: `${String(annotation.rect.y * 100)}%`,\n width: `${String(annotation.rect.width * 100)}%`,\n height: `${String(annotation.rect.height * 100)}%`,\n }}\n >\n <span className=\"bg-doom-yellow text-doom-deep\">{annotation.ordinal}</span>\n </div>\n ),\n )}\n {annotationRect === undefined ? null : (\n <div\n className=\"pointer-events-none absolute border-2 border-red-500 bg-red-500/10\"\n style={{\n left: `${String(annotationRect.x * 100)}%`,\n top: `${String(annotationRect.y * 100)}%`,\n width: `${String(annotationRect.width * 100)}%`,\n height: `${String(annotationRect.height * 100)}%`,\n }}\n />\n )}\n </div>\n </div>\n ) : (\n <iframe\n key={preview.handle}\n title={`${preview.storyExport} story preview`}\n sandbox=\"allow-scripts\"\n referrerPolicy=\"no-referrer\"\n srcDoc={isolatedPreviewHtml(preview.html)}\n className=\"min-h-0 flex-1 rounded border border-doom-border bg-white\"\n />\n )}\n {annotationImage === undefined || onAnnotationCandidate !== undefined ? null : (\n <div className=\"grid gap-2\">\n <div\n className=\"relative max-h-80 w-fit max-w-full touch-none overflow-hidden rounded border border-doom-border\"\n aria-label=\"Drag to mark an annotation region\"\n onPointerDown={(event) => {\n event.currentTarget.setPointerCapture(event.pointerId);\n const start = point(event);\n setAnnotationStart(start);\n setAnnotationRect({ ...start, width: 0, height: 0 });\n }}\n onPointerMove={updateAnnotation}\n onPointerUp={(event) => {\n updateAnnotation(event);\n setAnnotationStart(undefined);\n }}\n >\n <img\n className=\"block max-h-80 max-w-full select-none\"\n src={`data:${annotationImage.mimeType};base64,${annotationImage.data}`}\n alt=\"Fresh source-backed story render for annotation\"\n draggable={false}\n />\n {annotationRect === undefined ? null : (\n <div\n className=\"pointer-events-none absolute border-2 border-red-500 bg-red-500/10\"\n style={{\n left: `${String(annotationRect.x * 100)}%`,\n top: `${String(annotationRect.y * 100)}%`,\n width: `${String(annotationRect.width * 100)}%`,\n height: `${String(annotationRect.height * 100)}%`,\n }}\n />\n )}\n </div>\n <p className=\"text-xs text-doom-faint\">\n This image is rebuilt from source and does not preserve transient iframe interactions.\n </p>\n </div>\n )}\n {preview === undefined || onAnnotationCandidate !== undefined ? null : (\n <div className=\"flex gap-2\">\n <label className=\"grid min-w-0 flex-1 gap-1 text-xs text-doom-dim\">\n AI design feedback\n <textarea\n className=\"min-h-20 rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text\"\n value={feedback}\n onChange={(event) => setFeedback(event.target.value)}\n />\n </label>\n <Button\n className=\"self-end\"\n variant=\"outline\"\n disabled={working || submitCapture === undefined || feedback.trim() === '' || annotationImage === undefined}\n onClick={() => {\n if (submitCapture === undefined || annotationImage === undefined) return;\n const location =\n annotationRect === undefined\n ? 'Whole preview'\n : `Region: x=${annotationRect.x.toFixed(3)}, y=${annotationRect.y.toFixed(3)}, width=${annotationRect.width.toFixed(3)}, height=${annotationRect.height.toFixed(3)}`;\n const generation = generationRef.current;\n void submitCapture({\n ...annotationImage,\n context: {\n kind: 'author-capture',\n source: 'style-system',\n id: crypto.randomUUID(),\n label: `${annotationImage.storyPath}#${annotationImage.storyExport}`,\n content: [\n 'Edit the underlying story or component source, never the generated preview HTML.',\n `Project: ${builtRequest?.appPath ?? appPath}`,\n `Story: ${annotationImage.storyPath}#${annotationImage.storyExport}`,\n `Story source sha256: ${annotationImage.sourceSha256}`,\n `Source-backed image revision: ${annotationImage.sourceSha256}`,\n location,\n `Feedback: ${feedback.trim()}`,\n 'After editing, rebuild this preview before treating it as visually verified.',\n ].join('\\n'),\n },\n })\n .then(() => {\n if (generationRef.current !== generation) return;\n setStatus('Feedback and the frozen source-backed annotation image were submitted to the agent.');\n setFeedback('');\n })\n .catch((reason: unknown) => {\n if (generationRef.current === generation) setStatus(errorText(reason));\n });\n }}\n >\n Send to AI\n </Button>\n </div>\n )}\n </section>\n );\n}\n","import type { TransientTab } from '@agimon-ai/doompi-core/web';\n\nimport type { StoryPreviewSeed } from '../../../../../types/previewApi';\nimport { StoryPreviewPanel } from './StoryPreviewPanel';\n\nfunction sourceKey(path: string): string {\n let hash = 2_166_136_261;\n for (const character of path) hash = Math.imul(hash ^ character.codePointAt(0)!, 16_777_619);\n return (hash >>> 0).toString(36);\n}\n\nexport function storyPreviewTab(seed?: StoryPreviewSeed): TransientTab {\n const sourcePath = seed?.source?.path;\n return {\n id: sourcePath === undefined ? 'style-system-story-preview' : `style-system-story-preview-${sourceKey(sourcePath)}`,\n label: 'Story preview',\n panel: (props) => <StoryPreviewPanel {...props} seed={seed} />,\n retainComposer: true,\n };\n}\n","import { defineFill } from '@agimon-ai/doompi-core/web';\n\nimport { storyPreviewTab } from '../_components/StoryPreviewLauncher';\nimport { StoryPreviewPanel } from '../_components/StoryPreviewPanel';\n\nexport default defineFill({\n slot: 'author.preview-action',\n id: 'style-system',\n data: {\n version: 1 as const,\n label: 'Preview',\n detail: 'Build, inspect, refresh, and send source-targeted visual feedback.',\n supportsSource: (source: { kind?: string }) => source.kind === 'story-preview',\n embeddedPanel: StoryPreviewPanel,\n createTab: ({ source }: { source?: { path: string; hasUnsavedChanges: boolean } }) =>\n storyPreviewTab(source === undefined ? undefined : { source }),\n },\n});\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineWebPlugin, type WebPluginContributions } from '@agimon-ai/doompi-core/web';\n\nimport fillStyleSystem from '../src/extensions/workspaces/sessions/(frontend)/fill/StyleSystem.author.preview-action.web';\n\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T => ({ ...identity, ...(value as object) }) as T;\n\nexport const webPlugin = defineWebPlugin({\n id: 'style-system',\n session: {\n get fills(): WebPluginContributions['fills'] { return [via({ slot: 'author.preview-action', id: 'style-system' }, fillStyleSystem)]; },\n },\n});\n"],"mappings":"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"}
@@ -3,10 +3,24 @@ let node_fs = require("node:fs");
3
3
  let node_path = require("node:path");
4
4
  node_path = require_runtime.__toESM(node_path, 1);
5
5
  let node_crypto = require("node:crypto");
6
+ let node_os = require("node:os");
7
+ node_os = require_runtime.__toESM(node_os, 1);
6
8
  let _agimon_ai_style_system = require("@agimon-ai/style-system");
7
9
  //#region src/services/storyPreview/index.ts
8
10
  const STORY_FILE_PATTERN = /\.stories\.(?:js|jsx|ts|tsx)$/u;
9
11
  const STORY_EXPORT_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
12
+ const PNG_SIGNATURE = Buffer.from([
13
+ 137,
14
+ 80,
15
+ 78,
16
+ 71,
17
+ 13,
18
+ 10,
19
+ 26,
20
+ 10
21
+ ]);
22
+ const MAX_IMAGE_BYTES = 2097152;
23
+ const MAX_IMAGE_DIMENSION = 1600;
10
24
  const NON_STORY_EXPORTS = /* @__PURE__ */ new Set([
11
25
  "afterAll",
12
26
  "afterEach",
@@ -120,16 +134,67 @@ const defaultDependencies = {
120
134
  createBundler(_config) {
121
135
  return (0, _agimon_ai_style_system.createDefaultBundlerService)();
122
136
  },
137
+ createRenderer(config, appPath) {
138
+ return new _agimon_ai_style_system.ComponentRendererService(config, appPath);
139
+ },
140
+ rendererTemporaryRoot: () => node_path.default.join(node_os.default.tmpdir(), "style-system"),
123
141
  createHandle: node_crypto.randomUUID
124
142
  };
143
+ function pngCrc(data) {
144
+ let crc = 4294967295;
145
+ for (const byte of data) {
146
+ crc ^= byte;
147
+ for (let bit = 0; bit < 8; bit += 1) crc = crc >>> 1 ^ (crc & 1 ? 3988292384 : 0);
148
+ }
149
+ return (crc ^ 4294967295) >>> 0;
150
+ }
151
+ function pngDimensions(image) {
152
+ if (image.length < PNG_SIGNATURE.length || !image.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) throw new Error("Style-system returned an invalid PNG image");
153
+ let offset = PNG_SIGNATURE.length;
154
+ let width = 0;
155
+ let height = 0;
156
+ let chunks = 0;
157
+ let hasImageData = false;
158
+ let hasEnd = false;
159
+ while (offset < image.length) {
160
+ if (image.length - offset < 12) throw new Error("Style-system returned an invalid PNG image");
161
+ const length = image.readUInt32BE(offset);
162
+ const end = offset + 12 + length;
163
+ if (end > image.length) throw new Error("Style-system returned an invalid PNG image");
164
+ const type = image.toString("ascii", offset + 4, offset + 8);
165
+ if (!/^[A-Za-z]{4}$/u.test(type)) throw new Error("Style-system returned an invalid PNG image");
166
+ if (image.readUInt32BE(end - 4) !== pngCrc(image.subarray(offset + 4, end - 4))) throw new Error("Style-system returned an invalid PNG image");
167
+ if (chunks === 0) {
168
+ if (type !== "IHDR" || length !== 13) throw new Error("Style-system returned an invalid PNG image");
169
+ width = image.readUInt32BE(offset + 8);
170
+ height = image.readUInt32BE(offset + 12);
171
+ } else if (type === "IHDR") throw new Error("Style-system returned an invalid PNG image");
172
+ if (type === "IDAT") hasImageData = true;
173
+ if (type === "IEND") {
174
+ if (length !== 0 || end !== image.length) throw new Error("Style-system returned an invalid PNG image");
175
+ hasEnd = true;
176
+ }
177
+ chunks += 1;
178
+ offset = end;
179
+ }
180
+ if (!hasImageData || !hasEnd) throw new Error("Style-system returned an invalid PNG image");
181
+ if (width === 0 || height === 0 || width > MAX_IMAGE_DIMENSION || height > MAX_IMAGE_DIMENSION) throw new Error("Style-system returned PNG dimensions outside the supported range");
182
+ return {
183
+ width,
184
+ height
185
+ };
186
+ }
125
187
  /** Owns disposable static story-preview artifacts for one trusted workspace. */
126
188
  var StoryPreviewService = class {
127
189
  root;
128
190
  dependencies;
129
191
  artifacts = /* @__PURE__ */ new Map();
130
- constructor(workspaceRoot, dependencies = defaultDependencies) {
192
+ constructor(workspaceRoot, dependencies = {}) {
131
193
  this.root = node_path.default.resolve(workspaceRoot);
132
- this.dependencies = dependencies;
194
+ this.dependencies = {
195
+ ...defaultDependencies,
196
+ ...dependencies
197
+ };
133
198
  }
134
199
  async metadata(input) {
135
200
  const root = await node_fs.promises.realpath(this.root);
@@ -233,27 +298,36 @@ var StoryPreviewService = class {
233
298
  const storyPath = await this.resolveContainedPath(root, input.storyPath, "storyPath");
234
299
  if (!STORY_FILE_PATTERN.test(storyPath)) throw new Error("storyPath must name a .stories.js, .stories.jsx, .stories.ts, or .stories.tsx file");
235
300
  const [source, config] = await Promise.all([node_fs.promises.readFile(storyPath, "utf8"), this.dependencies.loadConfig(appPath)]);
236
- const renderer = new _agimon_ai_style_system.ComponentRendererService(config, appPath);
237
- let imagePath;
301
+ const renderer = this.dependencies.createRenderer(config, appPath);
302
+ let ownedImagePath;
238
303
  try {
239
304
  const component = await this.exactStory(root, storyPath, input.storyExport);
240
305
  const rendered = await renderer.renderComponent(component, {
241
306
  storyName: input.storyExport,
242
307
  darkMode: input.darkMode
243
308
  });
244
- const renderedImagePath = await node_fs.promises.realpath(rendered.imagePath);
245
- if (!isContained(await node_fs.promises.realpath(node_path.default.join(appPath, ".tmp")), renderedImagePath)) throw new Error("Style-system returned an image artifact outside the project temporary directory");
246
- imagePath = renderedImagePath;
309
+ const [renderedImagePath, imageRoot] = await Promise.all([node_fs.promises.realpath(rendered.imagePath), node_fs.promises.realpath(this.dependencies.rendererTemporaryRoot())]);
310
+ if (renderedImagePath === imageRoot || !isContained(imageRoot, renderedImagePath)) throw new Error("Style-system returned an image artifact outside its temporary directory");
311
+ const imageStat = await node_fs.promises.stat(renderedImagePath);
312
+ if (!imageStat.isFile()) throw new Error("Style-system returned a non-regular PNG image");
313
+ ownedImagePath = renderedImagePath;
314
+ if (imageStat.size > MAX_IMAGE_BYTES) throw new Error("Style-system returned an oversized PNG image");
315
+ const image = await node_fs.promises.readFile(renderedImagePath);
316
+ if (image.length !== imageStat.size) throw new Error("Style-system PNG changed while it was being read");
317
+ const { width, height } = pngDimensions(image);
247
318
  return {
248
- data: (await node_fs.promises.readFile(imagePath)).toString("base64"),
319
+ data: image.toString("base64"),
249
320
  mimeType: "image/png",
321
+ captureId: this.dependencies.createHandle(),
322
+ width,
323
+ height,
250
324
  storyPath: relativeWorkspacePath(root, storyPath),
251
325
  storyExport: input.storyExport,
252
326
  sourceSha256: (0, node_crypto.createHash)("sha256").update(source).digest("hex")
253
327
  };
254
328
  } finally {
255
329
  try {
256
- if (imagePath !== void 0) await node_fs.promises.rm(imagePath, { force: true });
330
+ if (ownedImagePath !== void 0) await node_fs.promises.rm(ownedImagePath, { force: true });
257
331
  } finally {
258
332
  await renderer.dispose();
259
333
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["path","fs","getAppDesignSystemConfig","createDefaultBundlerService","randomUUID","StoriesIndexService","createHash","ComponentRendererService"],"sources":["../../../../src/services/storyPreview/index.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport {\n ComponentRendererService,\n createDefaultBundlerService,\n getAppDesignSystemConfig,\n StoriesIndexService,\n type BaseBundlerService,\n type DesignSystemConfig,\n} from '@agimon-ai/style-system';\n\nimport type { StoryPreviewExport, StoryPreviewMetadataRequest, StoryPreviewMetadataView } from '../../types/previewApi';\nimport type {\n StoryPreviewBuildInput,\n StoryPreviewBuildResult,\n StoryPreviewImageResult,\n StoryPreviewServiceDependencies,\n} from './type';\n\nconst STORY_FILE_PATTERN = /\\.stories\\.(?:js|jsx|ts|tsx)$/u;\nconst STORY_EXPORT_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\nconst NON_STORY_EXPORTS = new Set([\n 'afterAll',\n 'afterEach',\n 'argTypes',\n 'beforeAll',\n 'beforeEach',\n 'component',\n 'decorators',\n 'excludeStories',\n 'globals',\n 'includeStories',\n 'loaders',\n 'meta',\n 'parameters',\n 'render',\n 'tags',\n]);\n\ninterface OwnedArtifact {\n directory: string;\n}\n\nfunction isContained(root: string, candidate: string): boolean {\n const relative = path.relative(root, candidate);\n return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));\n}\n\nfunction stripComments(source: string): string {\n let output = '';\n let quote: \"'\" | '\"' | '`' | undefined;\n let escaped = false;\n for (let index = 0; index < source.length; index += 1) {\n const character = source[index];\n const next = source[index + 1];\n if (quote !== undefined) {\n output += character === '\\n' ? '\\n' : ' ';\n if (escaped) escaped = false;\n else if (character === '\\\\') escaped = true;\n else if (character === quote) quote = undefined;\n continue;\n }\n if (character === '\"' || character === \"'\" || character === '`') {\n quote = character;\n output += ' ';\n } else if (character === '/' && next === '/') {\n output += ' ';\n index += 2;\n while (index < source.length && source[index] !== '\\n') {\n output += ' ';\n index += 1;\n }\n if (index < source.length) output += '\\n';\n } else if (character === '/' && next === '*') {\n output += ' ';\n index += 2;\n while (index < source.length && !(source[index - 1] === '*' && source[index] === '/')) {\n output += source[index] === '\\n' ? '\\n' : ' ';\n index += 1;\n }\n } else output += character;\n }\n return output;\n}\n\nfunction storyLabel(source: string, clean: string, exportName: string): string | undefined {\n const escapedExport = exportName.replace(/[.*+?^${}()|[\\]\\\\]/gu, '\\\\$&');\n const declaration = new RegExp(`\\\\bexport\\\\s+(?:const|let|var)\\\\s+${escapedExport}\\\\b[^=]*=\\\\s*\\\\{`, 'u');\n const match = clean.match(declaration);\n if (match === null || match.index === undefined) return undefined;\n const opening = clean.indexOf('{', match.index);\n let depth = 0;\n for (let index = opening; index < clean.length; index += 1) {\n if (clean[index] === '{') depth += 1;\n else if (clean[index] === '}') {\n depth -= 1;\n if (depth === 0) {\n const label = source\n .slice(opening, index)\n .match(/\\bname\\s*:\\s*(['\"])(.*?)\\1/u)?.[2]\n ?.trim();\n return label === '' ? undefined : label;\n }\n }\n }\n return undefined;\n}\n\n/** Extracts named CSF candidates without evaluating workspace source. */\nexport function extractStoryExports(source: string): readonly StoryPreviewExport[] {\n const names: string[] = [];\n const seen = new Set<string>();\n const add = (name: string): void => {\n if (name === 'default' || NON_STORY_EXPORTS.has(name) || seen.has(name)) return;\n seen.add(name);\n names.push(name);\n };\n const clean = stripComments(source);\n const declarations = /\\bexport\\s+(?:async\\s+)?(?:const|let|var|function|class)\\s+([A-Za-z_$][A-Za-z0-9_$]*)/gu;\n for (const match of clean.matchAll(declarations)) add(match[1]!);\n const lists = /\\bexport\\s*\\{([^}]*)\\}/gu;\n for (const match of clean.matchAll(lists)) {\n for (const item of match[1]!.split(',')) {\n const parts = item.trim().split(/\\s+as\\s+/u);\n const name = (parts.at(-1) ?? '').trim();\n if (/^[A-Za-z_$][A-Za-z0-9_$]*$/u.test(name)) add(name);\n }\n }\n return names.map((exportName) => {\n const label = storyLabel(source, clean, exportName);\n return label === undefined ? { exportName } : { exportName, label };\n });\n}\n\nasync function exists(filePath: string): Promise<boolean> {\n try {\n await fs.stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction relativeWorkspacePath(root: string, candidate: string): string {\n const relative = path.relative(root, candidate).split(path.sep).join('/');\n return relative === '' ? '.' : relative;\n}\n\nconst defaultDependencies: StoryPreviewServiceDependencies = {\n loadConfig: getAppDesignSystemConfig,\n createBundler(_config: DesignSystemConfig): BaseBundlerService {\n return createDefaultBundlerService();\n },\n createHandle: randomUUID,\n};\n\n/** Owns disposable static story-preview artifacts for one trusted workspace. */\nexport class StoryPreviewService {\n private readonly root: string;\n private readonly dependencies: StoryPreviewServiceDependencies;\n private readonly artifacts = new Map<string, OwnedArtifact>();\n\n constructor(workspaceRoot: string, dependencies: StoryPreviewServiceDependencies = defaultDependencies) {\n this.root = path.resolve(workspaceRoot);\n this.dependencies = dependencies;\n }\n\n async metadata(input: StoryPreviewMetadataRequest): Promise<StoryPreviewMetadataView> {\n const root = await fs.realpath(this.root);\n const storyPath = await this.resolveContainedPath(root, input.storyPath, 'storyPath');\n if (!STORY_FILE_PATTERN.test(storyPath))\n throw new Error('storyPath must name a .stories.js, .stories.jsx, .stories.ts, or .stories.tsx file');\n const project = await this.resolveProject(root, storyPath, input.appPath);\n const source = await fs.readFile(storyPath, 'utf8');\n return {\n storyPath: relativeWorkspacePath(root, storyPath),\n appPath: relativeWorkspacePath(root, project.path),\n projectResolution: project.resolution,\n exports: extractStoryExports(source),\n };\n }\n\n private async resolveProject(\n root: string,\n storyPath: string,\n requestedPath: string | undefined,\n ): Promise<{ path: string; resolution: StoryPreviewMetadataView['projectResolution'] }> {\n if (requestedPath !== undefined && requestedPath.trim() !== '') {\n return { path: await this.resolveContainedPath(root, requestedPath, 'appPath'), resolution: 'explicit' };\n }\n let directory = path.dirname(storyPath);\n let packagePath: string | undefined;\n while (isContained(root, directory)) {\n if (await exists(path.join(directory, 'style-system.config.yaml'))) {\n return { path: directory, resolution: 'config' };\n }\n if (packagePath === undefined && (await exists(path.join(directory, 'package.json')))) packagePath = directory;\n if (directory === root) break;\n directory = path.dirname(directory);\n }\n return packagePath === undefined\n ? { path: root, resolution: 'workspace' }\n : { path: packagePath, resolution: 'package' };\n }\n\n private async exactStory(root: string, storyPath: string, storyExport: string) {\n const source = await fs.readFile(storyPath, 'utf8');\n if (!extractStoryExports(source).some((entry) => entry.exportName === storyExport)) {\n throw new Error(`Story export \"${storyExport}\" was not found in ${relativeWorkspacePath(root, storyPath)}.`);\n }\n const index = new StoriesIndexService({ workspaceRoot: root, storyFiles: [storyPath], searchRoots: [] });\n await index.initialize();\n const component = index.getAllComponents().find((entry) => path.resolve(entry.filePath) === storyPath);\n if (component === undefined) throw new Error('The requested story file could not be indexed.');\n return component;\n }\n\n async build(input: StoryPreviewBuildInput): Promise<StoryPreviewBuildResult> {\n if (!STORY_EXPORT_PATTERN.test(input.storyExport)) {\n throw new Error('storyExport must be an exact JavaScript named export');\n }\n\n const root = await fs.realpath(this.root);\n const appPath = await this.resolveContainedPath(root, input.appPath, 'appPath');\n const storyPath = await this.resolveContainedPath(root, input.storyPath, 'storyPath');\n if (!STORY_FILE_PATTERN.test(storyPath))\n throw new Error('storyPath must name a .stories.js, .stories.jsx, .stories.ts, or .stories.tsx file');\n await this.exactStory(root, storyPath, input.storyExport);\n\n const [source, config] = await Promise.all([fs.readFile(storyPath, 'utf8'), this.dependencies.loadConfig(appPath)]);\n const bundler = this.dependencies.createBundler(config);\n let artifactDirectory: string | undefined;\n try {\n const rendered = await bundler.prerenderComponent({\n componentPath: storyPath,\n storyName: input.storyExport,\n args: input.args,\n darkMode: input.darkMode,\n appPath,\n themePath: config.themeProvider,\n cssFiles: config.cssFiles,\n rootComponent: config.rootComponent,\n unistylesConfig: config.unistylesConfig,\n });\n\n const htmlPath = await fs.realpath(rendered.htmlFilePath);\n const candidateDirectory = path.dirname(htmlPath);\n const temporaryRoot = path.join(appPath, '.tmp');\n if (candidateDirectory === temporaryRoot || !isContained(temporaryRoot, candidateDirectory)) {\n throw new Error('Style-system returned a preview artifact outside the project temporary directory');\n }\n artifactDirectory = candidateDirectory;\n\n const handle = this.dependencies.createHandle();\n const html = await fs.readFile(htmlPath, 'utf8');\n this.artifacts.set(handle, { directory: artifactDirectory });\n return {\n handle,\n html,\n storyPath: relativeWorkspacePath(root, storyPath),\n storyExport: input.storyExport,\n sourceSha256: createHash('sha256').update(source).digest('hex'),\n };\n } catch (reason) {\n if (artifactDirectory !== undefined) await fs.rm(artifactDirectory, { recursive: true, force: true });\n throw reason;\n }\n }\n\n /** Renders a fresh source-backed PNG. This does not preserve transient iframe interaction state. */\n async exportImage(input: StoryPreviewBuildInput): Promise<StoryPreviewImageResult> {\n if (!STORY_EXPORT_PATTERN.test(input.storyExport)) {\n throw new Error('storyExport must be an exact JavaScript named export');\n }\n const root = await fs.realpath(this.root);\n const appPath = await this.resolveContainedPath(root, input.appPath, 'appPath');\n const storyPath = await this.resolveContainedPath(root, input.storyPath, 'storyPath');\n if (!STORY_FILE_PATTERN.test(storyPath))\n throw new Error('storyPath must name a .stories.js, .stories.jsx, .stories.ts, or .stories.tsx file');\n\n const [source, config] = await Promise.all([fs.readFile(storyPath, 'utf8'), this.dependencies.loadConfig(appPath)]);\n const renderer = new ComponentRendererService(config, appPath);\n let imagePath: string | undefined;\n try {\n const component = await this.exactStory(root, storyPath, input.storyExport);\n const rendered = await renderer.renderComponent(component, {\n storyName: input.storyExport,\n darkMode: input.darkMode,\n });\n const renderedImagePath = await fs.realpath(rendered.imagePath);\n const imageRoot = await fs.realpath(path.join(appPath, '.tmp'));\n if (!isContained(imageRoot, renderedImagePath)) {\n throw new Error('Style-system returned an image artifact outside the project temporary directory');\n }\n imagePath = renderedImagePath;\n const image = await fs.readFile(imagePath);\n return {\n data: image.toString('base64'),\n mimeType: 'image/png',\n storyPath: relativeWorkspacePath(root, storyPath),\n storyExport: input.storyExport,\n sourceSha256: createHash('sha256').update(source).digest('hex'),\n };\n } finally {\n try {\n if (imagePath !== undefined) await fs.rm(imagePath, { force: true });\n } finally {\n await renderer.dispose();\n }\n }\n }\n\n async dispose(handle: string): Promise<boolean> {\n const artifact = this.artifacts.get(handle);\n if (artifact === undefined) return false;\n this.artifacts.delete(handle);\n await fs.rm(artifact.directory, { recursive: true, force: true });\n return true;\n }\n\n async disposeAll(): Promise<void> {\n await Promise.all([...this.artifacts.keys()].map((handle) => this.dispose(handle)));\n }\n\n private async resolveContainedPath(root: string, inputPath: string, field: string): Promise<string> {\n if (inputPath.trim() === '') throw new Error(`${field} is required`);\n const candidate = path.isAbsolute(inputPath) ? inputPath : path.resolve(root, inputPath);\n const canonical = await fs.realpath(candidate);\n if (!isContained(root, canonical)) throw new Error(`${field} must remain inside the workspace root`);\n return canonical;\n }\n}\n\nexport type {\n StoryPreviewBuildInput,\n StoryPreviewBuildResult,\n StoryPreviewImageResult,\n StoryPreviewServiceDependencies,\n} from './type';\n"],"mappings":";;;;;;;AAqBA,MAAM,qBAAqB;AAC3B,MAAM,uBAAuB;AAC7B,MAAM,oCAAoB,IAAI,IAAI;CAChC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAMD,SAAS,YAAY,MAAc,WAA4B;CAC7D,MAAM,WAAWA,UAAAA,QAAK,SAAS,MAAM,SAAS;CAC9C,OAAO,aAAa,MAAO,CAAC,SAAS,WAAW,KAAKA,UAAAA,QAAK,KAAK,KAAK,aAAa,QAAQ,CAACA,UAAAA,QAAK,WAAW,QAAQ;AACpH;AAEA,SAAS,cAAc,QAAwB;CAC7C,IAAI,SAAS;CACb,IAAI;CACJ,IAAI,UAAU;CACd,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SAAS,GAAG;EACrD,MAAM,YAAY,OAAO;EACzB,MAAM,OAAO,OAAO,QAAQ;EAC5B,IAAI,UAAU,KAAA,GAAW;GACvB,UAAU,cAAc,OAAO,OAAO;GACtC,IAAI,SAAS,UAAU;QAClB,IAAI,cAAc,MAAM,UAAU;QAClC,IAAI,cAAc,OAAO,QAAQ,KAAA;GACtC;EACF;EACA,IAAI,cAAc,QAAO,cAAc,OAAO,cAAc,KAAK;GAC/D,QAAQ;GACR,UAAU;EACZ,OAAO,IAAI,cAAc,OAAO,SAAS,KAAK;GAC5C,UAAU;GACV,SAAS;GACT,OAAO,QAAQ,OAAO,UAAU,OAAO,WAAW,MAAM;IACtD,UAAU;IACV,SAAS;GACX;GACA,IAAI,QAAQ,OAAO,QAAQ,UAAU;EACvC,OAAO,IAAI,cAAc,OAAO,SAAS,KAAK;GAC5C,UAAU;GACV,SAAS;GACT,OAAO,QAAQ,OAAO,UAAU,EAAE,OAAO,QAAQ,OAAO,OAAO,OAAO,WAAW,MAAM;IACrF,UAAU,OAAO,WAAW,OAAO,OAAO;IAC1C,SAAS;GACX;EACF,OAAO,UAAU;CACnB;CACA,OAAO;AACT;AAEA,SAAS,WAAW,QAAgB,OAAe,YAAwC;CACzF,MAAM,gBAAgB,WAAW,QAAQ,wBAAwB,MAAM;CACvE,MAAM,cAAc,IAAI,OAAO,qCAAqC,cAAc,mBAAmB,GAAG;CACxG,MAAM,QAAQ,MAAM,MAAM,WAAW;CACrC,IAAI,UAAU,QAAQ,MAAM,UAAU,KAAA,GAAW,OAAO,KAAA;CACxD,MAAM,UAAU,MAAM,QAAQ,KAAK,MAAM,KAAK;CAC9C,IAAI,QAAQ;CACZ,KAAK,IAAI,QAAQ,SAAS,QAAQ,MAAM,QAAQ,SAAS,GACvD,IAAI,MAAM,WAAW,KAAK,SAAS;MAC9B,IAAI,MAAM,WAAW,KAAK;EAC7B,SAAS;EACT,IAAI,UAAU,GAAG;GACf,MAAM,QAAQ,OACX,MAAM,SAAS,KAAK,CAAC,CACrB,MAAM,6BAA6B,CAAC,GAAG,EAAE,EACxC,KAAK;GACT,OAAO,UAAU,KAAK,KAAA,IAAY;EACpC;CACF;AAGJ;;AAGA,SAAgB,oBAAoB,QAA+C;CACjF,MAAM,QAAkB,CAAC;CACzB,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,OAAO,SAAuB;EAClC,IAAI,SAAS,aAAa,kBAAkB,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,GAAG;EACzE,KAAK,IAAI,IAAI;EACb,MAAM,KAAK,IAAI;CACjB;CACA,MAAM,QAAQ,cAAc,MAAM;CAElC,KAAK,MAAM,SAAS,MAAM,SAAS,yFAAY,GAAG,IAAI,MAAM,EAAG;CAE/D,KAAK,MAAM,SAAS,MAAM,SAAS,0BAAK,GACtC,KAAK,MAAM,QAAQ,MAAM,EAAE,CAAE,MAAM,GAAG,GAAG;EAEvC,MAAM,QADQ,KAAK,KAAK,CAAC,CAAC,MAAM,WACd,CAAC,CAAC,GAAG,EAAE,KAAK,GAAA,CAAI,KAAK;EACvC,IAAI,8BAA8B,KAAK,IAAI,GAAG,IAAI,IAAI;CACxD;CAEF,OAAO,MAAM,KAAK,eAAe;EAC/B,MAAM,QAAQ,WAAW,QAAQ,OAAO,UAAU;EAClD,OAAO,UAAU,KAAA,IAAY,EAAE,WAAW,IAAI;GAAE;GAAY;EAAM;CACpE,CAAC;AACH;AAEA,eAAe,OAAO,UAAoC;CACxD,IAAI;EACF,MAAMC,QAAAA,SAAG,KAAK,QAAQ;EACtB,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,sBAAsB,MAAc,WAA2B;CACtE,MAAM,WAAWD,UAAAA,QAAK,SAAS,MAAM,SAAS,CAAC,CAAC,MAAMA,UAAAA,QAAK,GAAG,CAAC,CAAC,KAAK,GAAG;CACxE,OAAO,aAAa,KAAK,MAAM;AACjC;AAEA,MAAM,sBAAuD;CAC3D,YAAYE,wBAAAA;CACZ,cAAc,SAAiD;EAC7D,QAAA,GAAOC,wBAAAA,4BAAAA,CAA4B;CACrC;CACA,cAAcC,YAAAA;AAChB;;AAGA,IAAa,sBAAb,MAAiC;CAC/B;CACA;CACA,4BAA6B,IAAI,IAA2B;CAE5D,YAAY,eAAuB,eAAgD,qBAAqB;EACtG,KAAK,OAAOJ,UAAAA,QAAK,QAAQ,aAAa;EACtC,KAAK,eAAe;CACtB;CAEA,MAAM,SAAS,OAAuE;EACpF,MAAM,OAAO,MAAMC,QAAAA,SAAG,SAAS,KAAK,IAAI;EACxC,MAAM,YAAY,MAAM,KAAK,qBAAqB,MAAM,MAAM,WAAW,WAAW;EACpF,IAAI,CAAC,mBAAmB,KAAK,SAAS,GACpC,MAAM,IAAI,MAAM,oFAAoF;EACtG,MAAM,UAAU,MAAM,KAAK,eAAe,MAAM,WAAW,MAAM,OAAO;EACxE,MAAM,SAAS,MAAMA,QAAAA,SAAG,SAAS,WAAW,MAAM;EAClD,OAAO;GACL,WAAW,sBAAsB,MAAM,SAAS;GAChD,SAAS,sBAAsB,MAAM,QAAQ,IAAI;GACjD,mBAAmB,QAAQ;GAC3B,SAAS,oBAAoB,MAAM;EACrC;CACF;CAEA,MAAc,eACZ,MACA,WACA,eACsF;EACtF,IAAI,kBAAkB,KAAA,KAAa,cAAc,KAAK,MAAM,IAC1D,OAAO;GAAE,MAAM,MAAM,KAAK,qBAAqB,MAAM,eAAe,SAAS;GAAG,YAAY;EAAW;EAEzG,IAAI,YAAYD,UAAAA,QAAK,QAAQ,SAAS;EACtC,IAAI;EACJ,OAAO,YAAY,MAAM,SAAS,GAAG;GACnC,IAAI,MAAM,OAAOA,UAAAA,QAAK,KAAK,WAAW,0BAA0B,CAAC,GAC/D,OAAO;IAAE,MAAM;IAAW,YAAY;GAAS;GAEjD,IAAI,gBAAgB,KAAA,KAAc,MAAM,OAAOA,UAAAA,QAAK,KAAK,WAAW,cAAc,CAAC,GAAI,cAAc;GACrG,IAAI,cAAc,MAAM;GACxB,YAAYA,UAAAA,QAAK,QAAQ,SAAS;EACpC;EACA,OAAO,gBAAgB,KAAA,IACnB;GAAE,MAAM;GAAM,YAAY;EAAY,IACtC;GAAE,MAAM;GAAa,YAAY;EAAU;CACjD;CAEA,MAAc,WAAW,MAAc,WAAmB,aAAqB;EAE7E,IAAI,CAAC,oBAAoB,MADJC,QAAAA,SAAG,SAAS,WAAW,MAAM,CACnB,CAAC,CAAC,MAAM,UAAU,MAAM,eAAe,WAAW,GAC/E,MAAM,IAAI,MAAM,iBAAiB,YAAY,qBAAqB,sBAAsB,MAAM,SAAS,EAAE,EAAE;EAE7G,MAAM,QAAQ,IAAII,wBAAAA,oBAAoB;GAAE,eAAe;GAAM,YAAY,CAAC,SAAS;GAAG,aAAa,CAAC;EAAE,CAAC;EACvG,MAAM,MAAM,WAAW;EACvB,MAAM,YAAY,MAAM,iBAAiB,CAAC,CAAC,MAAM,UAAUL,UAAAA,QAAK,QAAQ,MAAM,QAAQ,MAAM,SAAS;EACrG,IAAI,cAAc,KAAA,GAAW,MAAM,IAAI,MAAM,gDAAgD;EAC7F,OAAO;CACT;CAEA,MAAM,MAAM,OAAiE;EAC3E,IAAI,CAAC,qBAAqB,KAAK,MAAM,WAAW,GAC9C,MAAM,IAAI,MAAM,sDAAsD;EAGxE,MAAM,OAAO,MAAMC,QAAAA,SAAG,SAAS,KAAK,IAAI;EACxC,MAAM,UAAU,MAAM,KAAK,qBAAqB,MAAM,MAAM,SAAS,SAAS;EAC9E,MAAM,YAAY,MAAM,KAAK,qBAAqB,MAAM,MAAM,WAAW,WAAW;EACpF,IAAI,CAAC,mBAAmB,KAAK,SAAS,GACpC,MAAM,IAAI,MAAM,oFAAoF;EACtG,MAAM,KAAK,WAAW,MAAM,WAAW,MAAM,WAAW;EAExD,MAAM,CAAC,QAAQ,UAAU,MAAM,QAAQ,IAAI,CAACA,QAAAA,SAAG,SAAS,WAAW,MAAM,GAAG,KAAK,aAAa,WAAW,OAAO,CAAC,CAAC;EAClH,MAAM,UAAU,KAAK,aAAa,cAAc,MAAM;EACtD,IAAI;EACJ,IAAI;GACF,MAAM,WAAW,MAAM,QAAQ,mBAAmB;IAChD,eAAe;IACf,WAAW,MAAM;IACjB,MAAM,MAAM;IACZ,UAAU,MAAM;IAChB;IACA,WAAW,OAAO;IAClB,UAAU,OAAO;IACjB,eAAe,OAAO;IACtB,iBAAiB,OAAO;GAC1B,CAAC;GAED,MAAM,WAAW,MAAMA,QAAAA,SAAG,SAAS,SAAS,YAAY;GACxD,MAAM,qBAAqBD,UAAAA,QAAK,QAAQ,QAAQ;GAChD,MAAM,gBAAgBA,UAAAA,QAAK,KAAK,SAAS,MAAM;GAC/C,IAAI,uBAAuB,iBAAiB,CAAC,YAAY,eAAe,kBAAkB,GACxF,MAAM,IAAI,MAAM,kFAAkF;GAEpG,oBAAoB;GAEpB,MAAM,SAAS,KAAK,aAAa,aAAa;GAC9C,MAAM,OAAO,MAAMC,QAAAA,SAAG,SAAS,UAAU,MAAM;GAC/C,KAAK,UAAU,IAAI,QAAQ,EAAE,WAAW,kBAAkB,CAAC;GAC3D,OAAO;IACL;IACA;IACA,WAAW,sBAAsB,MAAM,SAAS;IAChD,aAAa,MAAM;IACnB,eAAA,GAAcK,YAAAA,WAAAA,CAAW,QAAQ,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,OAAO,KAAK;GAChE;EACF,SAAS,QAAQ;GACf,IAAI,sBAAsB,KAAA,GAAW,MAAML,QAAAA,SAAG,GAAG,mBAAmB;IAAE,WAAW;IAAM,OAAO;GAAK,CAAC;GACpG,MAAM;EACR;CACF;;CAGA,MAAM,YAAY,OAAiE;EACjF,IAAI,CAAC,qBAAqB,KAAK,MAAM,WAAW,GAC9C,MAAM,IAAI,MAAM,sDAAsD;EAExE,MAAM,OAAO,MAAMA,QAAAA,SAAG,SAAS,KAAK,IAAI;EACxC,MAAM,UAAU,MAAM,KAAK,qBAAqB,MAAM,MAAM,SAAS,SAAS;EAC9E,MAAM,YAAY,MAAM,KAAK,qBAAqB,MAAM,MAAM,WAAW,WAAW;EACpF,IAAI,CAAC,mBAAmB,KAAK,SAAS,GACpC,MAAM,IAAI,MAAM,oFAAoF;EAEtG,MAAM,CAAC,QAAQ,UAAU,MAAM,QAAQ,IAAI,CAACA,QAAAA,SAAG,SAAS,WAAW,MAAM,GAAG,KAAK,aAAa,WAAW,OAAO,CAAC,CAAC;EAClH,MAAM,WAAW,IAAIM,wBAAAA,yBAAyB,QAAQ,OAAO;EAC7D,IAAI;EACJ,IAAI;GACF,MAAM,YAAY,MAAM,KAAK,WAAW,MAAM,WAAW,MAAM,WAAW;GAC1E,MAAM,WAAW,MAAM,SAAS,gBAAgB,WAAW;IACzD,WAAW,MAAM;IACjB,UAAU,MAAM;GAClB,CAAC;GACD,MAAM,oBAAoB,MAAMN,QAAAA,SAAG,SAAS,SAAS,SAAS;GAE9D,IAAI,CAAC,YAAY,MADOA,QAAAA,SAAG,SAASD,UAAAA,QAAK,KAAK,SAAS,MAAM,CAAC,GAClC,iBAAiB,GAC3C,MAAM,IAAI,MAAM,iFAAiF;GAEnG,YAAY;GAEZ,OAAO;IACL,OAAM,MAFYC,QAAAA,SAAG,SAAS,SAAS,EAAA,CAE3B,SAAS,QAAQ;IAC7B,UAAU;IACV,WAAW,sBAAsB,MAAM,SAAS;IAChD,aAAa,MAAM;IACnB,eAAA,GAAcK,YAAAA,WAAAA,CAAW,QAAQ,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,OAAO,KAAK;GAChE;EACF,UAAU;GACR,IAAI;IACF,IAAI,cAAc,KAAA,GAAW,MAAML,QAAAA,SAAG,GAAG,WAAW,EAAE,OAAO,KAAK,CAAC;GACrE,UAAU;IACR,MAAM,SAAS,QAAQ;GACzB;EACF;CACF;CAEA,MAAM,QAAQ,QAAkC;EAC9C,MAAM,WAAW,KAAK,UAAU,IAAI,MAAM;EAC1C,IAAI,aAAa,KAAA,GAAW,OAAO;EACnC,KAAK,UAAU,OAAO,MAAM;EAC5B,MAAMA,QAAAA,SAAG,GAAG,SAAS,WAAW;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;EAChE,OAAO;CACT;CAEA,MAAM,aAA4B;EAChC,MAAM,QAAQ,IAAI,CAAC,GAAG,KAAK,UAAU,KAAK,CAAC,CAAC,CAAC,KAAK,WAAW,KAAK,QAAQ,MAAM,CAAC,CAAC;CACpF;CAEA,MAAc,qBAAqB,MAAc,WAAmB,OAAgC;EAClG,IAAI,UAAU,KAAK,MAAM,IAAI,MAAM,IAAI,MAAM,GAAG,MAAM,aAAa;EACnE,MAAM,YAAYD,UAAAA,QAAK,WAAW,SAAS,IAAI,YAAYA,UAAAA,QAAK,QAAQ,MAAM,SAAS;EACvF,MAAM,YAAY,MAAMC,QAAAA,SAAG,SAAS,SAAS;EAC7C,IAAI,CAAC,YAAY,MAAM,SAAS,GAAG,MAAM,IAAI,MAAM,GAAG,MAAM,uCAAuC;EACnG,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"index.cjs","names":["path","fs","getAppDesignSystemConfig","createDefaultBundlerService","ComponentRendererService","os","randomUUID","StoriesIndexService","createHash"],"sources":["../../../../src/services/storyPreview/index.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport { promises as fs } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport {\n ComponentRendererService,\n createDefaultBundlerService,\n getAppDesignSystemConfig,\n StoriesIndexService,\n type BaseBundlerService,\n type DesignSystemConfig,\n} from '@agimon-ai/style-system';\n\nimport type { StoryPreviewExport, StoryPreviewMetadataRequest, StoryPreviewMetadataView } from '../../types/previewApi';\nimport type {\n StoryPreviewBuildInput,\n StoryPreviewBuildResult,\n StoryPreviewImageResult,\n StoryPreviewServiceDependencies,\n} from './type';\n\nconst STORY_FILE_PATTERN = /\\.stories\\.(?:js|jsx|ts|tsx)$/u;\nconst STORY_EXPORT_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\nconst PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);\nconst MAX_IMAGE_BYTES = 2 * 1024 * 1024;\nconst MAX_IMAGE_DIMENSION = 1600;\nconst NON_STORY_EXPORTS = new Set([\n 'afterAll',\n 'afterEach',\n 'argTypes',\n 'beforeAll',\n 'beforeEach',\n 'component',\n 'decorators',\n 'excludeStories',\n 'globals',\n 'includeStories',\n 'loaders',\n 'meta',\n 'parameters',\n 'render',\n 'tags',\n]);\n\ninterface OwnedArtifact {\n directory: string;\n}\n\nfunction isContained(root: string, candidate: string): boolean {\n const relative = path.relative(root, candidate);\n return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));\n}\n\nfunction stripComments(source: string): string {\n let output = '';\n let quote: \"'\" | '\"' | '`' | undefined;\n let escaped = false;\n for (let index = 0; index < source.length; index += 1) {\n const character = source[index];\n const next = source[index + 1];\n if (quote !== undefined) {\n output += character === '\\n' ? '\\n' : ' ';\n if (escaped) escaped = false;\n else if (character === '\\\\') escaped = true;\n else if (character === quote) quote = undefined;\n continue;\n }\n if (character === '\"' || character === \"'\" || character === '`') {\n quote = character;\n output += ' ';\n } else if (character === '/' && next === '/') {\n output += ' ';\n index += 2;\n while (index < source.length && source[index] !== '\\n') {\n output += ' ';\n index += 1;\n }\n if (index < source.length) output += '\\n';\n } else if (character === '/' && next === '*') {\n output += ' ';\n index += 2;\n while (index < source.length && !(source[index - 1] === '*' && source[index] === '/')) {\n output += source[index] === '\\n' ? '\\n' : ' ';\n index += 1;\n }\n } else output += character;\n }\n return output;\n}\n\nfunction storyLabel(source: string, clean: string, exportName: string): string | undefined {\n const escapedExport = exportName.replace(/[.*+?^${}()|[\\]\\\\]/gu, '\\\\$&');\n const declaration = new RegExp(`\\\\bexport\\\\s+(?:const|let|var)\\\\s+${escapedExport}\\\\b[^=]*=\\\\s*\\\\{`, 'u');\n const match = clean.match(declaration);\n if (match === null || match.index === undefined) return undefined;\n const opening = clean.indexOf('{', match.index);\n let depth = 0;\n for (let index = opening; index < clean.length; index += 1) {\n if (clean[index] === '{') depth += 1;\n else if (clean[index] === '}') {\n depth -= 1;\n if (depth === 0) {\n const label = source\n .slice(opening, index)\n .match(/\\bname\\s*:\\s*(['\"])(.*?)\\1/u)?.[2]\n ?.trim();\n return label === '' ? undefined : label;\n }\n }\n }\n return undefined;\n}\n\n/** Extracts named CSF candidates without evaluating workspace source. */\nexport function extractStoryExports(source: string): readonly StoryPreviewExport[] {\n const names: string[] = [];\n const seen = new Set<string>();\n const add = (name: string): void => {\n if (name === 'default' || NON_STORY_EXPORTS.has(name) || seen.has(name)) return;\n seen.add(name);\n names.push(name);\n };\n const clean = stripComments(source);\n const declarations = /\\bexport\\s+(?:async\\s+)?(?:const|let|var|function|class)\\s+([A-Za-z_$][A-Za-z0-9_$]*)/gu;\n for (const match of clean.matchAll(declarations)) add(match[1]!);\n const lists = /\\bexport\\s*\\{([^}]*)\\}/gu;\n for (const match of clean.matchAll(lists)) {\n for (const item of match[1]!.split(',')) {\n const parts = item.trim().split(/\\s+as\\s+/u);\n const name = (parts.at(-1) ?? '').trim();\n if (/^[A-Za-z_$][A-Za-z0-9_$]*$/u.test(name)) add(name);\n }\n }\n return names.map((exportName) => {\n const label = storyLabel(source, clean, exportName);\n return label === undefined ? { exportName } : { exportName, label };\n });\n}\n\nasync function exists(filePath: string): Promise<boolean> {\n try {\n await fs.stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction relativeWorkspacePath(root: string, candidate: string): string {\n const relative = path.relative(root, candidate).split(path.sep).join('/');\n return relative === '' ? '.' : relative;\n}\n\nconst defaultDependencies: StoryPreviewServiceDependencies = {\n loadConfig: getAppDesignSystemConfig,\n createBundler(_config: DesignSystemConfig): BaseBundlerService {\n return createDefaultBundlerService();\n },\n createRenderer(config, appPath) {\n return new ComponentRendererService(config, appPath);\n },\n rendererTemporaryRoot: () => path.join(os.tmpdir(), 'style-system'),\n createHandle: randomUUID,\n};\n\nfunction pngCrc(data: Buffer): number {\n let crc = 0xffffffff;\n for (const byte of data) {\n crc ^= byte;\n for (let bit = 0; bit < 8; bit += 1) crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);\n }\n return (crc ^ 0xffffffff) >>> 0;\n}\n\nfunction pngDimensions(image: Buffer): { width: number; height: number } {\n if (image.length < PNG_SIGNATURE.length || !image.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) {\n throw new Error('Style-system returned an invalid PNG image');\n }\n let offset = PNG_SIGNATURE.length;\n let width = 0;\n let height = 0;\n let chunks = 0;\n let hasImageData = false;\n let hasEnd = false;\n while (offset < image.length) {\n if (image.length - offset < 12) throw new Error('Style-system returned an invalid PNG image');\n const length = image.readUInt32BE(offset);\n const end = offset + 12 + length;\n if (end > image.length) throw new Error('Style-system returned an invalid PNG image');\n const type = image.toString('ascii', offset + 4, offset + 8);\n if (!/^[A-Za-z]{4}$/u.test(type)) throw new Error('Style-system returned an invalid PNG image');\n if (image.readUInt32BE(end - 4) !== pngCrc(image.subarray(offset + 4, end - 4))) {\n throw new Error('Style-system returned an invalid PNG image');\n }\n if (chunks === 0) {\n if (type !== 'IHDR' || length !== 13) throw new Error('Style-system returned an invalid PNG image');\n width = image.readUInt32BE(offset + 8);\n height = image.readUInt32BE(offset + 12);\n } else if (type === 'IHDR') throw new Error('Style-system returned an invalid PNG image');\n if (type === 'IDAT') hasImageData = true;\n if (type === 'IEND') {\n if (length !== 0 || end !== image.length) throw new Error('Style-system returned an invalid PNG image');\n hasEnd = true;\n }\n chunks += 1;\n offset = end;\n }\n if (!hasImageData || !hasEnd) throw new Error('Style-system returned an invalid PNG image');\n if (width === 0 || height === 0 || width > MAX_IMAGE_DIMENSION || height > MAX_IMAGE_DIMENSION) {\n throw new Error('Style-system returned PNG dimensions outside the supported range');\n }\n return { width, height };\n}\n\n/** Owns disposable static story-preview artifacts for one trusted workspace. */\nexport class StoryPreviewService {\n private readonly root: string;\n private readonly dependencies: StoryPreviewServiceDependencies;\n private readonly artifacts = new Map<string, OwnedArtifact>();\n\n constructor(workspaceRoot: string, dependencies: Partial<StoryPreviewServiceDependencies> = {}) {\n this.root = path.resolve(workspaceRoot);\n this.dependencies = { ...defaultDependencies, ...dependencies };\n }\n\n async metadata(input: StoryPreviewMetadataRequest): Promise<StoryPreviewMetadataView> {\n const root = await fs.realpath(this.root);\n const storyPath = await this.resolveContainedPath(root, input.storyPath, 'storyPath');\n if (!STORY_FILE_PATTERN.test(storyPath))\n throw new Error('storyPath must name a .stories.js, .stories.jsx, .stories.ts, or .stories.tsx file');\n const project = await this.resolveProject(root, storyPath, input.appPath);\n const source = await fs.readFile(storyPath, 'utf8');\n return {\n storyPath: relativeWorkspacePath(root, storyPath),\n appPath: relativeWorkspacePath(root, project.path),\n projectResolution: project.resolution,\n exports: extractStoryExports(source),\n };\n }\n\n private async resolveProject(\n root: string,\n storyPath: string,\n requestedPath: string | undefined,\n ): Promise<{ path: string; resolution: StoryPreviewMetadataView['projectResolution'] }> {\n if (requestedPath !== undefined && requestedPath.trim() !== '') {\n return { path: await this.resolveContainedPath(root, requestedPath, 'appPath'), resolution: 'explicit' };\n }\n let directory = path.dirname(storyPath);\n let packagePath: string | undefined;\n while (isContained(root, directory)) {\n if (await exists(path.join(directory, 'style-system.config.yaml'))) {\n return { path: directory, resolution: 'config' };\n }\n if (packagePath === undefined && (await exists(path.join(directory, 'package.json')))) packagePath = directory;\n if (directory === root) break;\n directory = path.dirname(directory);\n }\n return packagePath === undefined\n ? { path: root, resolution: 'workspace' }\n : { path: packagePath, resolution: 'package' };\n }\n\n private async exactStory(root: string, storyPath: string, storyExport: string) {\n const source = await fs.readFile(storyPath, 'utf8');\n if (!extractStoryExports(source).some((entry) => entry.exportName === storyExport)) {\n throw new Error(`Story export \"${storyExport}\" was not found in ${relativeWorkspacePath(root, storyPath)}.`);\n }\n const index = new StoriesIndexService({ workspaceRoot: root, storyFiles: [storyPath], searchRoots: [] });\n await index.initialize();\n const component = index.getAllComponents().find((entry) => path.resolve(entry.filePath) === storyPath);\n if (component === undefined) throw new Error('The requested story file could not be indexed.');\n return component;\n }\n\n async build(input: StoryPreviewBuildInput): Promise<StoryPreviewBuildResult> {\n if (!STORY_EXPORT_PATTERN.test(input.storyExport)) {\n throw new Error('storyExport must be an exact JavaScript named export');\n }\n\n const root = await fs.realpath(this.root);\n const appPath = await this.resolveContainedPath(root, input.appPath, 'appPath');\n const storyPath = await this.resolveContainedPath(root, input.storyPath, 'storyPath');\n if (!STORY_FILE_PATTERN.test(storyPath))\n throw new Error('storyPath must name a .stories.js, .stories.jsx, .stories.ts, or .stories.tsx file');\n await this.exactStory(root, storyPath, input.storyExport);\n\n const [source, config] = await Promise.all([fs.readFile(storyPath, 'utf8'), this.dependencies.loadConfig(appPath)]);\n const bundler = this.dependencies.createBundler(config);\n let artifactDirectory: string | undefined;\n try {\n const rendered = await bundler.prerenderComponent({\n componentPath: storyPath,\n storyName: input.storyExport,\n args: input.args,\n darkMode: input.darkMode,\n appPath,\n themePath: config.themeProvider,\n cssFiles: config.cssFiles,\n rootComponent: config.rootComponent,\n unistylesConfig: config.unistylesConfig,\n });\n\n const htmlPath = await fs.realpath(rendered.htmlFilePath);\n const candidateDirectory = path.dirname(htmlPath);\n const temporaryRoot = path.join(appPath, '.tmp');\n if (candidateDirectory === temporaryRoot || !isContained(temporaryRoot, candidateDirectory)) {\n throw new Error('Style-system returned a preview artifact outside the project temporary directory');\n }\n artifactDirectory = candidateDirectory;\n\n const handle = this.dependencies.createHandle();\n const html = await fs.readFile(htmlPath, 'utf8');\n this.artifacts.set(handle, { directory: artifactDirectory });\n return {\n handle,\n html,\n storyPath: relativeWorkspacePath(root, storyPath),\n storyExport: input.storyExport,\n sourceSha256: createHash('sha256').update(source).digest('hex'),\n };\n } catch (reason) {\n if (artifactDirectory !== undefined) await fs.rm(artifactDirectory, { recursive: true, force: true });\n throw reason;\n }\n }\n\n /** Renders a fresh source-backed PNG. This does not preserve transient iframe interaction state. */\n async exportImage(input: StoryPreviewBuildInput): Promise<StoryPreviewImageResult> {\n if (!STORY_EXPORT_PATTERN.test(input.storyExport)) {\n throw new Error('storyExport must be an exact JavaScript named export');\n }\n const root = await fs.realpath(this.root);\n const appPath = await this.resolveContainedPath(root, input.appPath, 'appPath');\n const storyPath = await this.resolveContainedPath(root, input.storyPath, 'storyPath');\n if (!STORY_FILE_PATTERN.test(storyPath))\n throw new Error('storyPath must name a .stories.js, .stories.jsx, .stories.ts, or .stories.tsx file');\n\n const [source, config] = await Promise.all([fs.readFile(storyPath, 'utf8'), this.dependencies.loadConfig(appPath)]);\n const renderer = this.dependencies.createRenderer(config, appPath);\n let ownedImagePath: string | undefined;\n try {\n const component = await this.exactStory(root, storyPath, input.storyExport);\n const rendered = await renderer.renderComponent(component, {\n storyName: input.storyExport,\n darkMode: input.darkMode,\n });\n const [renderedImagePath, imageRoot] = await Promise.all([\n fs.realpath(rendered.imagePath),\n fs.realpath(this.dependencies.rendererTemporaryRoot()),\n ]);\n if (renderedImagePath === imageRoot || !isContained(imageRoot, renderedImagePath)) {\n throw new Error('Style-system returned an image artifact outside its temporary directory');\n }\n const imageStat = await fs.stat(renderedImagePath);\n if (!imageStat.isFile()) throw new Error('Style-system returned a non-regular PNG image');\n ownedImagePath = renderedImagePath;\n if (imageStat.size > MAX_IMAGE_BYTES) throw new Error('Style-system returned an oversized PNG image');\n const image = await fs.readFile(renderedImagePath);\n if (image.length !== imageStat.size) throw new Error('Style-system PNG changed while it was being read');\n const { width, height } = pngDimensions(image);\n return {\n data: image.toString('base64'),\n mimeType: 'image/png',\n captureId: this.dependencies.createHandle(),\n width,\n height,\n storyPath: relativeWorkspacePath(root, storyPath),\n storyExport: input.storyExport,\n sourceSha256: createHash('sha256').update(source).digest('hex'),\n };\n } finally {\n try {\n if (ownedImagePath !== undefined) await fs.rm(ownedImagePath, { force: true });\n } finally {\n await renderer.dispose();\n }\n }\n }\n\n async dispose(handle: string): Promise<boolean> {\n const artifact = this.artifacts.get(handle);\n if (artifact === undefined) return false;\n this.artifacts.delete(handle);\n await fs.rm(artifact.directory, { recursive: true, force: true });\n return true;\n }\n\n async disposeAll(): Promise<void> {\n await Promise.all([...this.artifacts.keys()].map((handle) => this.dispose(handle)));\n }\n\n private async resolveContainedPath(root: string, inputPath: string, field: string): Promise<string> {\n if (inputPath.trim() === '') throw new Error(`${field} is required`);\n const candidate = path.isAbsolute(inputPath) ? inputPath : path.resolve(root, inputPath);\n const canonical = await fs.realpath(candidate);\n if (!isContained(root, canonical)) throw new Error(`${field} must remain inside the workspace root`);\n return canonical;\n }\n}\n\nexport type {\n StoryPreviewBuildInput,\n StoryPreviewBuildResult,\n StoryPreviewImageResult,\n StoryPreviewRenderer,\n StoryPreviewServiceDependencies,\n} from './type';\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { StoryPreviewExport, StoryPreviewMetadataRequest, StoryPreviewMetadataView } from "../../types/previewApi.cjs";
2
- import { StoryPreviewBuildInput, StoryPreviewBuildResult, StoryPreviewImageResult, StoryPreviewServiceDependencies } from "./type.cjs";
2
+ import { StoryPreviewBuildInput, StoryPreviewBuildResult, StoryPreviewImageResult, StoryPreviewRenderer, StoryPreviewServiceDependencies } from "./type.cjs";
3
3
  //#region src/services/storyPreview/index.d.ts
4
4
  /** Extracts named CSF candidates without evaluating workspace source. */
5
5
  export declare function extractStoryExports(source: string): readonly StoryPreviewExport[];
@@ -8,7 +8,7 @@ export declare class StoryPreviewService {
8
8
  private readonly root;
9
9
  private readonly dependencies;
10
10
  private readonly artifacts;
11
- constructor(workspaceRoot: string, dependencies?: StoryPreviewServiceDependencies);
11
+ constructor(workspaceRoot: string, dependencies?: Partial<StoryPreviewServiceDependencies>);
12
12
  metadata(input: StoryPreviewMetadataRequest): Promise<StoryPreviewMetadataView>;
13
13
  private resolveProject;
14
14
  private exactStory;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../../../src/services/storyPreview/index.ts"],"mappings":";;;;wBA+GgB,oBAAoB,0BAA0B;;qBAgDjD;mBACM;mBACA;mBACA;EAEjB,YAAY,uBAAuB,eAAc;EAK3C,SAAS,OAAO,8BAA8B,QAAQ;UAe9C;UAuBA;EAYR,MAAM,OAAO,yBAAyB,QAAQ;;EAqD9C,YAAY,OAAO,yBAAyB,QAAQ;EA0CpD,QAAQ,iBAAiB;EAQzB,cAAc;UAIN"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../../../src/services/storyPreview/index.ts"],"mappings":";;;;wBAmHgB,oBAAoB,0BAA0B;;qBAqGjD;mBACM;mBACA;mBACA;EAEjB,YAAY,uBAAuB,eAAc,QAAQ;EAKnD,SAAS,OAAO,8BAA8B,QAAQ;UAe9C;UAuBA;EAYR,MAAM,OAAO,yBAAyB,QAAQ;;EAqD9C,YAAY,OAAO,yBAAyB,QAAQ;EAoDpD,QAAQ,iBAAiB;EAQzB,cAAc;UAIN"}
@@ -1,5 +1,5 @@
1
1
  import { StoryPreviewExport, StoryPreviewMetadataRequest, StoryPreviewMetadataView } from "../../types/previewApi.mjs";
2
- import { StoryPreviewBuildInput, StoryPreviewBuildResult, StoryPreviewImageResult, StoryPreviewServiceDependencies } from "./type.mjs";
2
+ import { StoryPreviewBuildInput, StoryPreviewBuildResult, StoryPreviewImageResult, StoryPreviewRenderer, StoryPreviewServiceDependencies } from "./type.mjs";
3
3
  //#region src/services/storyPreview/index.d.ts
4
4
  /** Extracts named CSF candidates without evaluating workspace source. */
5
5
  export declare function extractStoryExports(source: string): readonly StoryPreviewExport[];
@@ -8,7 +8,7 @@ export declare class StoryPreviewService {
8
8
  private readonly root;
9
9
  private readonly dependencies;
10
10
  private readonly artifacts;
11
- constructor(workspaceRoot: string, dependencies?: StoryPreviewServiceDependencies);
11
+ constructor(workspaceRoot: string, dependencies?: Partial<StoryPreviewServiceDependencies>);
12
12
  metadata(input: StoryPreviewMetadataRequest): Promise<StoryPreviewMetadataView>;
13
13
  private resolveProject;
14
14
  private exactStory;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/services/storyPreview/index.ts"],"mappings":";;;;wBA+GgB,oBAAoB,0BAA0B;;qBAgDjD;mBACM;mBACA;mBACA;EAEjB,YAAY,uBAAuB,eAAc;EAK3C,SAAS,OAAO,8BAA8B,QAAQ;UAe9C;UAuBA;EAYR,MAAM,OAAO,yBAAyB,QAAQ;;EAqD9C,YAAY,OAAO,yBAAyB,QAAQ;EA0CpD,QAAQ,iBAAiB;EAQzB,cAAc;UAIN"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/services/storyPreview/index.ts"],"mappings":";;;;wBAmHgB,oBAAoB,0BAA0B;;qBAqGjD;mBACM;mBACA;mBACA;EAEjB,YAAY,uBAAuB,eAAc,QAAQ;EAKnD,SAAS,OAAO,8BAA8B,QAAQ;UAe9C;UAuBA;EAYR,MAAM,OAAO,yBAAyB,QAAQ;;EAqD9C,YAAY,OAAO,yBAAyB,QAAQ;EAoDpD,QAAQ,iBAAiB;EAQzB,cAAc;UAIN"}
@@ -1,10 +1,23 @@
1
1
  import { promises } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
+ import os from "node:os";
4
5
  import { ComponentRendererService, StoriesIndexService, createDefaultBundlerService, getAppDesignSystemConfig } from "@agimon-ai/style-system";
5
6
  //#region src/services/storyPreview/index.ts
6
7
  const STORY_FILE_PATTERN = /\.stories\.(?:js|jsx|ts|tsx)$/u;
7
8
  const STORY_EXPORT_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
9
+ const PNG_SIGNATURE = Buffer.from([
10
+ 137,
11
+ 80,
12
+ 78,
13
+ 71,
14
+ 13,
15
+ 10,
16
+ 26,
17
+ 10
18
+ ]);
19
+ const MAX_IMAGE_BYTES = 2097152;
20
+ const MAX_IMAGE_DIMENSION = 1600;
8
21
  const NON_STORY_EXPORTS = /* @__PURE__ */ new Set([
9
22
  "afterAll",
10
23
  "afterEach",
@@ -118,16 +131,67 @@ const defaultDependencies = {
118
131
  createBundler(_config) {
119
132
  return createDefaultBundlerService();
120
133
  },
134
+ createRenderer(config, appPath) {
135
+ return new ComponentRendererService(config, appPath);
136
+ },
137
+ rendererTemporaryRoot: () => path.join(os.tmpdir(), "style-system"),
121
138
  createHandle: randomUUID
122
139
  };
140
+ function pngCrc(data) {
141
+ let crc = 4294967295;
142
+ for (const byte of data) {
143
+ crc ^= byte;
144
+ for (let bit = 0; bit < 8; bit += 1) crc = crc >>> 1 ^ (crc & 1 ? 3988292384 : 0);
145
+ }
146
+ return (crc ^ 4294967295) >>> 0;
147
+ }
148
+ function pngDimensions(image) {
149
+ if (image.length < PNG_SIGNATURE.length || !image.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) throw new Error("Style-system returned an invalid PNG image");
150
+ let offset = PNG_SIGNATURE.length;
151
+ let width = 0;
152
+ let height = 0;
153
+ let chunks = 0;
154
+ let hasImageData = false;
155
+ let hasEnd = false;
156
+ while (offset < image.length) {
157
+ if (image.length - offset < 12) throw new Error("Style-system returned an invalid PNG image");
158
+ const length = image.readUInt32BE(offset);
159
+ const end = offset + 12 + length;
160
+ if (end > image.length) throw new Error("Style-system returned an invalid PNG image");
161
+ const type = image.toString("ascii", offset + 4, offset + 8);
162
+ if (!/^[A-Za-z]{4}$/u.test(type)) throw new Error("Style-system returned an invalid PNG image");
163
+ if (image.readUInt32BE(end - 4) !== pngCrc(image.subarray(offset + 4, end - 4))) throw new Error("Style-system returned an invalid PNG image");
164
+ if (chunks === 0) {
165
+ if (type !== "IHDR" || length !== 13) throw new Error("Style-system returned an invalid PNG image");
166
+ width = image.readUInt32BE(offset + 8);
167
+ height = image.readUInt32BE(offset + 12);
168
+ } else if (type === "IHDR") throw new Error("Style-system returned an invalid PNG image");
169
+ if (type === "IDAT") hasImageData = true;
170
+ if (type === "IEND") {
171
+ if (length !== 0 || end !== image.length) throw new Error("Style-system returned an invalid PNG image");
172
+ hasEnd = true;
173
+ }
174
+ chunks += 1;
175
+ offset = end;
176
+ }
177
+ if (!hasImageData || !hasEnd) throw new Error("Style-system returned an invalid PNG image");
178
+ if (width === 0 || height === 0 || width > MAX_IMAGE_DIMENSION || height > MAX_IMAGE_DIMENSION) throw new Error("Style-system returned PNG dimensions outside the supported range");
179
+ return {
180
+ width,
181
+ height
182
+ };
183
+ }
123
184
  /** Owns disposable static story-preview artifacts for one trusted workspace. */
124
185
  var StoryPreviewService = class {
125
186
  root;
126
187
  dependencies;
127
188
  artifacts = /* @__PURE__ */ new Map();
128
- constructor(workspaceRoot, dependencies = defaultDependencies) {
189
+ constructor(workspaceRoot, dependencies = {}) {
129
190
  this.root = path.resolve(workspaceRoot);
130
- this.dependencies = dependencies;
191
+ this.dependencies = {
192
+ ...defaultDependencies,
193
+ ...dependencies
194
+ };
131
195
  }
132
196
  async metadata(input) {
133
197
  const root = await promises.realpath(this.root);
@@ -231,27 +295,36 @@ var StoryPreviewService = class {
231
295
  const storyPath = await this.resolveContainedPath(root, input.storyPath, "storyPath");
232
296
  if (!STORY_FILE_PATTERN.test(storyPath)) throw new Error("storyPath must name a .stories.js, .stories.jsx, .stories.ts, or .stories.tsx file");
233
297
  const [source, config] = await Promise.all([promises.readFile(storyPath, "utf8"), this.dependencies.loadConfig(appPath)]);
234
- const renderer = new ComponentRendererService(config, appPath);
235
- let imagePath;
298
+ const renderer = this.dependencies.createRenderer(config, appPath);
299
+ let ownedImagePath;
236
300
  try {
237
301
  const component = await this.exactStory(root, storyPath, input.storyExport);
238
302
  const rendered = await renderer.renderComponent(component, {
239
303
  storyName: input.storyExport,
240
304
  darkMode: input.darkMode
241
305
  });
242
- const renderedImagePath = await promises.realpath(rendered.imagePath);
243
- if (!isContained(await promises.realpath(path.join(appPath, ".tmp")), renderedImagePath)) throw new Error("Style-system returned an image artifact outside the project temporary directory");
244
- imagePath = renderedImagePath;
306
+ const [renderedImagePath, imageRoot] = await Promise.all([promises.realpath(rendered.imagePath), promises.realpath(this.dependencies.rendererTemporaryRoot())]);
307
+ if (renderedImagePath === imageRoot || !isContained(imageRoot, renderedImagePath)) throw new Error("Style-system returned an image artifact outside its temporary directory");
308
+ const imageStat = await promises.stat(renderedImagePath);
309
+ if (!imageStat.isFile()) throw new Error("Style-system returned a non-regular PNG image");
310
+ ownedImagePath = renderedImagePath;
311
+ if (imageStat.size > MAX_IMAGE_BYTES) throw new Error("Style-system returned an oversized PNG image");
312
+ const image = await promises.readFile(renderedImagePath);
313
+ if (image.length !== imageStat.size) throw new Error("Style-system PNG changed while it was being read");
314
+ const { width, height } = pngDimensions(image);
245
315
  return {
246
- data: (await promises.readFile(imagePath)).toString("base64"),
316
+ data: image.toString("base64"),
247
317
  mimeType: "image/png",
318
+ captureId: this.dependencies.createHandle(),
319
+ width,
320
+ height,
248
321
  storyPath: relativeWorkspacePath(root, storyPath),
249
322
  storyExport: input.storyExport,
250
323
  sourceSha256: createHash("sha256").update(source).digest("hex")
251
324
  };
252
325
  } finally {
253
326
  try {
254
- if (imagePath !== void 0) await promises.rm(imagePath, { force: true });
327
+ if (ownedImagePath !== void 0) await promises.rm(ownedImagePath, { force: true });
255
328
  } finally {
256
329
  await renderer.dispose();
257
330
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["fs"],"sources":["../../../../src/services/storyPreview/index.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport {\n ComponentRendererService,\n createDefaultBundlerService,\n getAppDesignSystemConfig,\n StoriesIndexService,\n type BaseBundlerService,\n type DesignSystemConfig,\n} from '@agimon-ai/style-system';\n\nimport type { StoryPreviewExport, StoryPreviewMetadataRequest, StoryPreviewMetadataView } from '../../types/previewApi';\nimport type {\n StoryPreviewBuildInput,\n StoryPreviewBuildResult,\n StoryPreviewImageResult,\n StoryPreviewServiceDependencies,\n} from './type';\n\nconst STORY_FILE_PATTERN = /\\.stories\\.(?:js|jsx|ts|tsx)$/u;\nconst STORY_EXPORT_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\nconst NON_STORY_EXPORTS = new Set([\n 'afterAll',\n 'afterEach',\n 'argTypes',\n 'beforeAll',\n 'beforeEach',\n 'component',\n 'decorators',\n 'excludeStories',\n 'globals',\n 'includeStories',\n 'loaders',\n 'meta',\n 'parameters',\n 'render',\n 'tags',\n]);\n\ninterface OwnedArtifact {\n directory: string;\n}\n\nfunction isContained(root: string, candidate: string): boolean {\n const relative = path.relative(root, candidate);\n return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));\n}\n\nfunction stripComments(source: string): string {\n let output = '';\n let quote: \"'\" | '\"' | '`' | undefined;\n let escaped = false;\n for (let index = 0; index < source.length; index += 1) {\n const character = source[index];\n const next = source[index + 1];\n if (quote !== undefined) {\n output += character === '\\n' ? '\\n' : ' ';\n if (escaped) escaped = false;\n else if (character === '\\\\') escaped = true;\n else if (character === quote) quote = undefined;\n continue;\n }\n if (character === '\"' || character === \"'\" || character === '`') {\n quote = character;\n output += ' ';\n } else if (character === '/' && next === '/') {\n output += ' ';\n index += 2;\n while (index < source.length && source[index] !== '\\n') {\n output += ' ';\n index += 1;\n }\n if (index < source.length) output += '\\n';\n } else if (character === '/' && next === '*') {\n output += ' ';\n index += 2;\n while (index < source.length && !(source[index - 1] === '*' && source[index] === '/')) {\n output += source[index] === '\\n' ? '\\n' : ' ';\n index += 1;\n }\n } else output += character;\n }\n return output;\n}\n\nfunction storyLabel(source: string, clean: string, exportName: string): string | undefined {\n const escapedExport = exportName.replace(/[.*+?^${}()|[\\]\\\\]/gu, '\\\\$&');\n const declaration = new RegExp(`\\\\bexport\\\\s+(?:const|let|var)\\\\s+${escapedExport}\\\\b[^=]*=\\\\s*\\\\{`, 'u');\n const match = clean.match(declaration);\n if (match === null || match.index === undefined) return undefined;\n const opening = clean.indexOf('{', match.index);\n let depth = 0;\n for (let index = opening; index < clean.length; index += 1) {\n if (clean[index] === '{') depth += 1;\n else if (clean[index] === '}') {\n depth -= 1;\n if (depth === 0) {\n const label = source\n .slice(opening, index)\n .match(/\\bname\\s*:\\s*(['\"])(.*?)\\1/u)?.[2]\n ?.trim();\n return label === '' ? undefined : label;\n }\n }\n }\n return undefined;\n}\n\n/** Extracts named CSF candidates without evaluating workspace source. */\nexport function extractStoryExports(source: string): readonly StoryPreviewExport[] {\n const names: string[] = [];\n const seen = new Set<string>();\n const add = (name: string): void => {\n if (name === 'default' || NON_STORY_EXPORTS.has(name) || seen.has(name)) return;\n seen.add(name);\n names.push(name);\n };\n const clean = stripComments(source);\n const declarations = /\\bexport\\s+(?:async\\s+)?(?:const|let|var|function|class)\\s+([A-Za-z_$][A-Za-z0-9_$]*)/gu;\n for (const match of clean.matchAll(declarations)) add(match[1]!);\n const lists = /\\bexport\\s*\\{([^}]*)\\}/gu;\n for (const match of clean.matchAll(lists)) {\n for (const item of match[1]!.split(',')) {\n const parts = item.trim().split(/\\s+as\\s+/u);\n const name = (parts.at(-1) ?? '').trim();\n if (/^[A-Za-z_$][A-Za-z0-9_$]*$/u.test(name)) add(name);\n }\n }\n return names.map((exportName) => {\n const label = storyLabel(source, clean, exportName);\n return label === undefined ? { exportName } : { exportName, label };\n });\n}\n\nasync function exists(filePath: string): Promise<boolean> {\n try {\n await fs.stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction relativeWorkspacePath(root: string, candidate: string): string {\n const relative = path.relative(root, candidate).split(path.sep).join('/');\n return relative === '' ? '.' : relative;\n}\n\nconst defaultDependencies: StoryPreviewServiceDependencies = {\n loadConfig: getAppDesignSystemConfig,\n createBundler(_config: DesignSystemConfig): BaseBundlerService {\n return createDefaultBundlerService();\n },\n createHandle: randomUUID,\n};\n\n/** Owns disposable static story-preview artifacts for one trusted workspace. */\nexport class StoryPreviewService {\n private readonly root: string;\n private readonly dependencies: StoryPreviewServiceDependencies;\n private readonly artifacts = new Map<string, OwnedArtifact>();\n\n constructor(workspaceRoot: string, dependencies: StoryPreviewServiceDependencies = defaultDependencies) {\n this.root = path.resolve(workspaceRoot);\n this.dependencies = dependencies;\n }\n\n async metadata(input: StoryPreviewMetadataRequest): Promise<StoryPreviewMetadataView> {\n const root = await fs.realpath(this.root);\n const storyPath = await this.resolveContainedPath(root, input.storyPath, 'storyPath');\n if (!STORY_FILE_PATTERN.test(storyPath))\n throw new Error('storyPath must name a .stories.js, .stories.jsx, .stories.ts, or .stories.tsx file');\n const project = await this.resolveProject(root, storyPath, input.appPath);\n const source = await fs.readFile(storyPath, 'utf8');\n return {\n storyPath: relativeWorkspacePath(root, storyPath),\n appPath: relativeWorkspacePath(root, project.path),\n projectResolution: project.resolution,\n exports: extractStoryExports(source),\n };\n }\n\n private async resolveProject(\n root: string,\n storyPath: string,\n requestedPath: string | undefined,\n ): Promise<{ path: string; resolution: StoryPreviewMetadataView['projectResolution'] }> {\n if (requestedPath !== undefined && requestedPath.trim() !== '') {\n return { path: await this.resolveContainedPath(root, requestedPath, 'appPath'), resolution: 'explicit' };\n }\n let directory = path.dirname(storyPath);\n let packagePath: string | undefined;\n while (isContained(root, directory)) {\n if (await exists(path.join(directory, 'style-system.config.yaml'))) {\n return { path: directory, resolution: 'config' };\n }\n if (packagePath === undefined && (await exists(path.join(directory, 'package.json')))) packagePath = directory;\n if (directory === root) break;\n directory = path.dirname(directory);\n }\n return packagePath === undefined\n ? { path: root, resolution: 'workspace' }\n : { path: packagePath, resolution: 'package' };\n }\n\n private async exactStory(root: string, storyPath: string, storyExport: string) {\n const source = await fs.readFile(storyPath, 'utf8');\n if (!extractStoryExports(source).some((entry) => entry.exportName === storyExport)) {\n throw new Error(`Story export \"${storyExport}\" was not found in ${relativeWorkspacePath(root, storyPath)}.`);\n }\n const index = new StoriesIndexService({ workspaceRoot: root, storyFiles: [storyPath], searchRoots: [] });\n await index.initialize();\n const component = index.getAllComponents().find((entry) => path.resolve(entry.filePath) === storyPath);\n if (component === undefined) throw new Error('The requested story file could not be indexed.');\n return component;\n }\n\n async build(input: StoryPreviewBuildInput): Promise<StoryPreviewBuildResult> {\n if (!STORY_EXPORT_PATTERN.test(input.storyExport)) {\n throw new Error('storyExport must be an exact JavaScript named export');\n }\n\n const root = await fs.realpath(this.root);\n const appPath = await this.resolveContainedPath(root, input.appPath, 'appPath');\n const storyPath = await this.resolveContainedPath(root, input.storyPath, 'storyPath');\n if (!STORY_FILE_PATTERN.test(storyPath))\n throw new Error('storyPath must name a .stories.js, .stories.jsx, .stories.ts, or .stories.tsx file');\n await this.exactStory(root, storyPath, input.storyExport);\n\n const [source, config] = await Promise.all([fs.readFile(storyPath, 'utf8'), this.dependencies.loadConfig(appPath)]);\n const bundler = this.dependencies.createBundler(config);\n let artifactDirectory: string | undefined;\n try {\n const rendered = await bundler.prerenderComponent({\n componentPath: storyPath,\n storyName: input.storyExport,\n args: input.args,\n darkMode: input.darkMode,\n appPath,\n themePath: config.themeProvider,\n cssFiles: config.cssFiles,\n rootComponent: config.rootComponent,\n unistylesConfig: config.unistylesConfig,\n });\n\n const htmlPath = await fs.realpath(rendered.htmlFilePath);\n const candidateDirectory = path.dirname(htmlPath);\n const temporaryRoot = path.join(appPath, '.tmp');\n if (candidateDirectory === temporaryRoot || !isContained(temporaryRoot, candidateDirectory)) {\n throw new Error('Style-system returned a preview artifact outside the project temporary directory');\n }\n artifactDirectory = candidateDirectory;\n\n const handle = this.dependencies.createHandle();\n const html = await fs.readFile(htmlPath, 'utf8');\n this.artifacts.set(handle, { directory: artifactDirectory });\n return {\n handle,\n html,\n storyPath: relativeWorkspacePath(root, storyPath),\n storyExport: input.storyExport,\n sourceSha256: createHash('sha256').update(source).digest('hex'),\n };\n } catch (reason) {\n if (artifactDirectory !== undefined) await fs.rm(artifactDirectory, { recursive: true, force: true });\n throw reason;\n }\n }\n\n /** Renders a fresh source-backed PNG. This does not preserve transient iframe interaction state. */\n async exportImage(input: StoryPreviewBuildInput): Promise<StoryPreviewImageResult> {\n if (!STORY_EXPORT_PATTERN.test(input.storyExport)) {\n throw new Error('storyExport must be an exact JavaScript named export');\n }\n const root = await fs.realpath(this.root);\n const appPath = await this.resolveContainedPath(root, input.appPath, 'appPath');\n const storyPath = await this.resolveContainedPath(root, input.storyPath, 'storyPath');\n if (!STORY_FILE_PATTERN.test(storyPath))\n throw new Error('storyPath must name a .stories.js, .stories.jsx, .stories.ts, or .stories.tsx file');\n\n const [source, config] = await Promise.all([fs.readFile(storyPath, 'utf8'), this.dependencies.loadConfig(appPath)]);\n const renderer = new ComponentRendererService(config, appPath);\n let imagePath: string | undefined;\n try {\n const component = await this.exactStory(root, storyPath, input.storyExport);\n const rendered = await renderer.renderComponent(component, {\n storyName: input.storyExport,\n darkMode: input.darkMode,\n });\n const renderedImagePath = await fs.realpath(rendered.imagePath);\n const imageRoot = await fs.realpath(path.join(appPath, '.tmp'));\n if (!isContained(imageRoot, renderedImagePath)) {\n throw new Error('Style-system returned an image artifact outside the project temporary directory');\n }\n imagePath = renderedImagePath;\n const image = await fs.readFile(imagePath);\n return {\n data: image.toString('base64'),\n mimeType: 'image/png',\n storyPath: relativeWorkspacePath(root, storyPath),\n storyExport: input.storyExport,\n sourceSha256: createHash('sha256').update(source).digest('hex'),\n };\n } finally {\n try {\n if (imagePath !== undefined) await fs.rm(imagePath, { force: true });\n } finally {\n await renderer.dispose();\n }\n }\n }\n\n async dispose(handle: string): Promise<boolean> {\n const artifact = this.artifacts.get(handle);\n if (artifact === undefined) return false;\n this.artifacts.delete(handle);\n await fs.rm(artifact.directory, { recursive: true, force: true });\n return true;\n }\n\n async disposeAll(): Promise<void> {\n await Promise.all([...this.artifacts.keys()].map((handle) => this.dispose(handle)));\n }\n\n private async resolveContainedPath(root: string, inputPath: string, field: string): Promise<string> {\n if (inputPath.trim() === '') throw new Error(`${field} is required`);\n const candidate = path.isAbsolute(inputPath) ? inputPath : path.resolve(root, inputPath);\n const canonical = await fs.realpath(candidate);\n if (!isContained(root, canonical)) throw new Error(`${field} must remain inside the workspace root`);\n return canonical;\n }\n}\n\nexport type {\n StoryPreviewBuildInput,\n StoryPreviewBuildResult,\n StoryPreviewImageResult,\n StoryPreviewServiceDependencies,\n} from './type';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["fs"],"sources":["../../../../src/services/storyPreview/index.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport { promises as fs } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport {\n ComponentRendererService,\n createDefaultBundlerService,\n getAppDesignSystemConfig,\n StoriesIndexService,\n type BaseBundlerService,\n type DesignSystemConfig,\n} from '@agimon-ai/style-system';\n\nimport type { StoryPreviewExport, StoryPreviewMetadataRequest, StoryPreviewMetadataView } from '../../types/previewApi';\nimport type {\n StoryPreviewBuildInput,\n StoryPreviewBuildResult,\n StoryPreviewImageResult,\n StoryPreviewServiceDependencies,\n} from './type';\n\nconst STORY_FILE_PATTERN = /\\.stories\\.(?:js|jsx|ts|tsx)$/u;\nconst STORY_EXPORT_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\nconst PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);\nconst MAX_IMAGE_BYTES = 2 * 1024 * 1024;\nconst MAX_IMAGE_DIMENSION = 1600;\nconst NON_STORY_EXPORTS = new Set([\n 'afterAll',\n 'afterEach',\n 'argTypes',\n 'beforeAll',\n 'beforeEach',\n 'component',\n 'decorators',\n 'excludeStories',\n 'globals',\n 'includeStories',\n 'loaders',\n 'meta',\n 'parameters',\n 'render',\n 'tags',\n]);\n\ninterface OwnedArtifact {\n directory: string;\n}\n\nfunction isContained(root: string, candidate: string): boolean {\n const relative = path.relative(root, candidate);\n return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));\n}\n\nfunction stripComments(source: string): string {\n let output = '';\n let quote: \"'\" | '\"' | '`' | undefined;\n let escaped = false;\n for (let index = 0; index < source.length; index += 1) {\n const character = source[index];\n const next = source[index + 1];\n if (quote !== undefined) {\n output += character === '\\n' ? '\\n' : ' ';\n if (escaped) escaped = false;\n else if (character === '\\\\') escaped = true;\n else if (character === quote) quote = undefined;\n continue;\n }\n if (character === '\"' || character === \"'\" || character === '`') {\n quote = character;\n output += ' ';\n } else if (character === '/' && next === '/') {\n output += ' ';\n index += 2;\n while (index < source.length && source[index] !== '\\n') {\n output += ' ';\n index += 1;\n }\n if (index < source.length) output += '\\n';\n } else if (character === '/' && next === '*') {\n output += ' ';\n index += 2;\n while (index < source.length && !(source[index - 1] === '*' && source[index] === '/')) {\n output += source[index] === '\\n' ? '\\n' : ' ';\n index += 1;\n }\n } else output += character;\n }\n return output;\n}\n\nfunction storyLabel(source: string, clean: string, exportName: string): string | undefined {\n const escapedExport = exportName.replace(/[.*+?^${}()|[\\]\\\\]/gu, '\\\\$&');\n const declaration = new RegExp(`\\\\bexport\\\\s+(?:const|let|var)\\\\s+${escapedExport}\\\\b[^=]*=\\\\s*\\\\{`, 'u');\n const match = clean.match(declaration);\n if (match === null || match.index === undefined) return undefined;\n const opening = clean.indexOf('{', match.index);\n let depth = 0;\n for (let index = opening; index < clean.length; index += 1) {\n if (clean[index] === '{') depth += 1;\n else if (clean[index] === '}') {\n depth -= 1;\n if (depth === 0) {\n const label = source\n .slice(opening, index)\n .match(/\\bname\\s*:\\s*(['\"])(.*?)\\1/u)?.[2]\n ?.trim();\n return label === '' ? undefined : label;\n }\n }\n }\n return undefined;\n}\n\n/** Extracts named CSF candidates without evaluating workspace source. */\nexport function extractStoryExports(source: string): readonly StoryPreviewExport[] {\n const names: string[] = [];\n const seen = new Set<string>();\n const add = (name: string): void => {\n if (name === 'default' || NON_STORY_EXPORTS.has(name) || seen.has(name)) return;\n seen.add(name);\n names.push(name);\n };\n const clean = stripComments(source);\n const declarations = /\\bexport\\s+(?:async\\s+)?(?:const|let|var|function|class)\\s+([A-Za-z_$][A-Za-z0-9_$]*)/gu;\n for (const match of clean.matchAll(declarations)) add(match[1]!);\n const lists = /\\bexport\\s*\\{([^}]*)\\}/gu;\n for (const match of clean.matchAll(lists)) {\n for (const item of match[1]!.split(',')) {\n const parts = item.trim().split(/\\s+as\\s+/u);\n const name = (parts.at(-1) ?? '').trim();\n if (/^[A-Za-z_$][A-Za-z0-9_$]*$/u.test(name)) add(name);\n }\n }\n return names.map((exportName) => {\n const label = storyLabel(source, clean, exportName);\n return label === undefined ? { exportName } : { exportName, label };\n });\n}\n\nasync function exists(filePath: string): Promise<boolean> {\n try {\n await fs.stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction relativeWorkspacePath(root: string, candidate: string): string {\n const relative = path.relative(root, candidate).split(path.sep).join('/');\n return relative === '' ? '.' : relative;\n}\n\nconst defaultDependencies: StoryPreviewServiceDependencies = {\n loadConfig: getAppDesignSystemConfig,\n createBundler(_config: DesignSystemConfig): BaseBundlerService {\n return createDefaultBundlerService();\n },\n createRenderer(config, appPath) {\n return new ComponentRendererService(config, appPath);\n },\n rendererTemporaryRoot: () => path.join(os.tmpdir(), 'style-system'),\n createHandle: randomUUID,\n};\n\nfunction pngCrc(data: Buffer): number {\n let crc = 0xffffffff;\n for (const byte of data) {\n crc ^= byte;\n for (let bit = 0; bit < 8; bit += 1) crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);\n }\n return (crc ^ 0xffffffff) >>> 0;\n}\n\nfunction pngDimensions(image: Buffer): { width: number; height: number } {\n if (image.length < PNG_SIGNATURE.length || !image.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) {\n throw new Error('Style-system returned an invalid PNG image');\n }\n let offset = PNG_SIGNATURE.length;\n let width = 0;\n let height = 0;\n let chunks = 0;\n let hasImageData = false;\n let hasEnd = false;\n while (offset < image.length) {\n if (image.length - offset < 12) throw new Error('Style-system returned an invalid PNG image');\n const length = image.readUInt32BE(offset);\n const end = offset + 12 + length;\n if (end > image.length) throw new Error('Style-system returned an invalid PNG image');\n const type = image.toString('ascii', offset + 4, offset + 8);\n if (!/^[A-Za-z]{4}$/u.test(type)) throw new Error('Style-system returned an invalid PNG image');\n if (image.readUInt32BE(end - 4) !== pngCrc(image.subarray(offset + 4, end - 4))) {\n throw new Error('Style-system returned an invalid PNG image');\n }\n if (chunks === 0) {\n if (type !== 'IHDR' || length !== 13) throw new Error('Style-system returned an invalid PNG image');\n width = image.readUInt32BE(offset + 8);\n height = image.readUInt32BE(offset + 12);\n } else if (type === 'IHDR') throw new Error('Style-system returned an invalid PNG image');\n if (type === 'IDAT') hasImageData = true;\n if (type === 'IEND') {\n if (length !== 0 || end !== image.length) throw new Error('Style-system returned an invalid PNG image');\n hasEnd = true;\n }\n chunks += 1;\n offset = end;\n }\n if (!hasImageData || !hasEnd) throw new Error('Style-system returned an invalid PNG image');\n if (width === 0 || height === 0 || width > MAX_IMAGE_DIMENSION || height > MAX_IMAGE_DIMENSION) {\n throw new Error('Style-system returned PNG dimensions outside the supported range');\n }\n return { width, height };\n}\n\n/** Owns disposable static story-preview artifacts for one trusted workspace. */\nexport class StoryPreviewService {\n private readonly root: string;\n private readonly dependencies: StoryPreviewServiceDependencies;\n private readonly artifacts = new Map<string, OwnedArtifact>();\n\n constructor(workspaceRoot: string, dependencies: Partial<StoryPreviewServiceDependencies> = {}) {\n this.root = path.resolve(workspaceRoot);\n this.dependencies = { ...defaultDependencies, ...dependencies };\n }\n\n async metadata(input: StoryPreviewMetadataRequest): Promise<StoryPreviewMetadataView> {\n const root = await fs.realpath(this.root);\n const storyPath = await this.resolveContainedPath(root, input.storyPath, 'storyPath');\n if (!STORY_FILE_PATTERN.test(storyPath))\n throw new Error('storyPath must name a .stories.js, .stories.jsx, .stories.ts, or .stories.tsx file');\n const project = await this.resolveProject(root, storyPath, input.appPath);\n const source = await fs.readFile(storyPath, 'utf8');\n return {\n storyPath: relativeWorkspacePath(root, storyPath),\n appPath: relativeWorkspacePath(root, project.path),\n projectResolution: project.resolution,\n exports: extractStoryExports(source),\n };\n }\n\n private async resolveProject(\n root: string,\n storyPath: string,\n requestedPath: string | undefined,\n ): Promise<{ path: string; resolution: StoryPreviewMetadataView['projectResolution'] }> {\n if (requestedPath !== undefined && requestedPath.trim() !== '') {\n return { path: await this.resolveContainedPath(root, requestedPath, 'appPath'), resolution: 'explicit' };\n }\n let directory = path.dirname(storyPath);\n let packagePath: string | undefined;\n while (isContained(root, directory)) {\n if (await exists(path.join(directory, 'style-system.config.yaml'))) {\n return { path: directory, resolution: 'config' };\n }\n if (packagePath === undefined && (await exists(path.join(directory, 'package.json')))) packagePath = directory;\n if (directory === root) break;\n directory = path.dirname(directory);\n }\n return packagePath === undefined\n ? { path: root, resolution: 'workspace' }\n : { path: packagePath, resolution: 'package' };\n }\n\n private async exactStory(root: string, storyPath: string, storyExport: string) {\n const source = await fs.readFile(storyPath, 'utf8');\n if (!extractStoryExports(source).some((entry) => entry.exportName === storyExport)) {\n throw new Error(`Story export \"${storyExport}\" was not found in ${relativeWorkspacePath(root, storyPath)}.`);\n }\n const index = new StoriesIndexService({ workspaceRoot: root, storyFiles: [storyPath], searchRoots: [] });\n await index.initialize();\n const component = index.getAllComponents().find((entry) => path.resolve(entry.filePath) === storyPath);\n if (component === undefined) throw new Error('The requested story file could not be indexed.');\n return component;\n }\n\n async build(input: StoryPreviewBuildInput): Promise<StoryPreviewBuildResult> {\n if (!STORY_EXPORT_PATTERN.test(input.storyExport)) {\n throw new Error('storyExport must be an exact JavaScript named export');\n }\n\n const root = await fs.realpath(this.root);\n const appPath = await this.resolveContainedPath(root, input.appPath, 'appPath');\n const storyPath = await this.resolveContainedPath(root, input.storyPath, 'storyPath');\n if (!STORY_FILE_PATTERN.test(storyPath))\n throw new Error('storyPath must name a .stories.js, .stories.jsx, .stories.ts, or .stories.tsx file');\n await this.exactStory(root, storyPath, input.storyExport);\n\n const [source, config] = await Promise.all([fs.readFile(storyPath, 'utf8'), this.dependencies.loadConfig(appPath)]);\n const bundler = this.dependencies.createBundler(config);\n let artifactDirectory: string | undefined;\n try {\n const rendered = await bundler.prerenderComponent({\n componentPath: storyPath,\n storyName: input.storyExport,\n args: input.args,\n darkMode: input.darkMode,\n appPath,\n themePath: config.themeProvider,\n cssFiles: config.cssFiles,\n rootComponent: config.rootComponent,\n unistylesConfig: config.unistylesConfig,\n });\n\n const htmlPath = await fs.realpath(rendered.htmlFilePath);\n const candidateDirectory = path.dirname(htmlPath);\n const temporaryRoot = path.join(appPath, '.tmp');\n if (candidateDirectory === temporaryRoot || !isContained(temporaryRoot, candidateDirectory)) {\n throw new Error('Style-system returned a preview artifact outside the project temporary directory');\n }\n artifactDirectory = candidateDirectory;\n\n const handle = this.dependencies.createHandle();\n const html = await fs.readFile(htmlPath, 'utf8');\n this.artifacts.set(handle, { directory: artifactDirectory });\n return {\n handle,\n html,\n storyPath: relativeWorkspacePath(root, storyPath),\n storyExport: input.storyExport,\n sourceSha256: createHash('sha256').update(source).digest('hex'),\n };\n } catch (reason) {\n if (artifactDirectory !== undefined) await fs.rm(artifactDirectory, { recursive: true, force: true });\n throw reason;\n }\n }\n\n /** Renders a fresh source-backed PNG. This does not preserve transient iframe interaction state. */\n async exportImage(input: StoryPreviewBuildInput): Promise<StoryPreviewImageResult> {\n if (!STORY_EXPORT_PATTERN.test(input.storyExport)) {\n throw new Error('storyExport must be an exact JavaScript named export');\n }\n const root = await fs.realpath(this.root);\n const appPath = await this.resolveContainedPath(root, input.appPath, 'appPath');\n const storyPath = await this.resolveContainedPath(root, input.storyPath, 'storyPath');\n if (!STORY_FILE_PATTERN.test(storyPath))\n throw new Error('storyPath must name a .stories.js, .stories.jsx, .stories.ts, or .stories.tsx file');\n\n const [source, config] = await Promise.all([fs.readFile(storyPath, 'utf8'), this.dependencies.loadConfig(appPath)]);\n const renderer = this.dependencies.createRenderer(config, appPath);\n let ownedImagePath: string | undefined;\n try {\n const component = await this.exactStory(root, storyPath, input.storyExport);\n const rendered = await renderer.renderComponent(component, {\n storyName: input.storyExport,\n darkMode: input.darkMode,\n });\n const [renderedImagePath, imageRoot] = await Promise.all([\n fs.realpath(rendered.imagePath),\n fs.realpath(this.dependencies.rendererTemporaryRoot()),\n ]);\n if (renderedImagePath === imageRoot || !isContained(imageRoot, renderedImagePath)) {\n throw new Error('Style-system returned an image artifact outside its temporary directory');\n }\n const imageStat = await fs.stat(renderedImagePath);\n if (!imageStat.isFile()) throw new Error('Style-system returned a non-regular PNG image');\n ownedImagePath = renderedImagePath;\n if (imageStat.size > MAX_IMAGE_BYTES) throw new Error('Style-system returned an oversized PNG image');\n const image = await fs.readFile(renderedImagePath);\n if (image.length !== imageStat.size) throw new Error('Style-system PNG changed while it was being read');\n const { width, height } = pngDimensions(image);\n return {\n data: image.toString('base64'),\n mimeType: 'image/png',\n captureId: this.dependencies.createHandle(),\n width,\n height,\n storyPath: relativeWorkspacePath(root, storyPath),\n storyExport: input.storyExport,\n sourceSha256: createHash('sha256').update(source).digest('hex'),\n };\n } finally {\n try {\n if (ownedImagePath !== undefined) await fs.rm(ownedImagePath, { force: true });\n } finally {\n await renderer.dispose();\n }\n }\n }\n\n async dispose(handle: string): Promise<boolean> {\n const artifact = this.artifacts.get(handle);\n if (artifact === undefined) return false;\n this.artifacts.delete(handle);\n await fs.rm(artifact.directory, { recursive: true, force: true });\n return true;\n }\n\n async disposeAll(): Promise<void> {\n await Promise.all([...this.artifacts.keys()].map((handle) => this.dispose(handle)));\n }\n\n private async resolveContainedPath(root: string, inputPath: string, field: string): Promise<string> {\n if (inputPath.trim() === '') throw new Error(`${field} is required`);\n const candidate = path.isAbsolute(inputPath) ? inputPath : path.resolve(root, inputPath);\n const canonical = await fs.realpath(candidate);\n if (!isContained(root, canonical)) throw new Error(`${field} must remain inside the workspace root`);\n return canonical;\n }\n}\n\nexport type {\n StoryPreviewBuildInput,\n StoryPreviewBuildResult,\n StoryPreviewImageResult,\n StoryPreviewRenderer,\n StoryPreviewServiceDependencies,\n} from './type';\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { BaseBundlerService, DesignSystemConfig } from "@agimon-ai/style-system";
1
+ import { BaseBundlerService, ComponentRendererService, DesignSystemConfig } from "@agimon-ai/style-system";
2
2
  //#region src/services/storyPreview/type.d.ts
3
3
  export interface StoryPreviewBuildInput {
4
4
  appPath: string;
@@ -17,13 +17,19 @@ export interface StoryPreviewBuildResult {
17
17
  export interface StoryPreviewImageResult {
18
18
  data: string;
19
19
  mimeType: 'image/png';
20
+ captureId: string;
21
+ width: number;
22
+ height: number;
20
23
  storyPath: string;
21
24
  storyExport: string;
22
25
  sourceSha256: string;
23
26
  }
27
+ export type StoryPreviewRenderer = Pick<ComponentRendererService, 'renderComponent' | 'dispose'>;
24
28
  export interface StoryPreviewServiceDependencies {
25
29
  loadConfig(appPath: string): Promise<DesignSystemConfig>;
26
30
  createBundler(config: DesignSystemConfig): BaseBundlerService;
31
+ createRenderer(config: DesignSystemConfig, appPath: string): StoryPreviewRenderer;
32
+ rendererTemporaryRoot(): string;
27
33
  createHandle(): string;
28
34
  }
29
35
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"type.d.cts","names":[],"sources":["../../../../src/services/storyPreview/type.ts"],"mappings":";;iBAEiB;EACf;EACA;EACA;EACA,OAAO;EACP;;iBAGe;EACf;EACA;EACA;EACA;EACA;;iBAGe;EACf;EACA;EACA;EACA;EACA;;iBAGe;EACf,WAAW,kBAAkB,QAAQ;EACrC,cAAc,QAAQ,qBAAqB;EAC3C"}
1
+ {"version":3,"file":"type.d.cts","names":[],"sources":["../../../../src/services/storyPreview/type.ts"],"mappings":";;iBAEiB;EACf;EACA;EACA;EACA,OAAO;EACP;;iBAGe;EACf;EACA;EACA;EACA;EACA;;iBAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;YAGU,uBAAuB,KAAK;iBAEvB;EACf,WAAW,kBAAkB,QAAQ;EACrC,cAAc,QAAQ,qBAAqB;EAC3C,eAAe,QAAQ,oBAAoB,kBAAkB;EAC7D;EACA"}
@@ -1,4 +1,4 @@
1
- import { BaseBundlerService, DesignSystemConfig } from "@agimon-ai/style-system";
1
+ import { BaseBundlerService, ComponentRendererService, DesignSystemConfig } from "@agimon-ai/style-system";
2
2
  //#region src/services/storyPreview/type.d.ts
3
3
  export interface StoryPreviewBuildInput {
4
4
  appPath: string;
@@ -17,13 +17,19 @@ export interface StoryPreviewBuildResult {
17
17
  export interface StoryPreviewImageResult {
18
18
  data: string;
19
19
  mimeType: 'image/png';
20
+ captureId: string;
21
+ width: number;
22
+ height: number;
20
23
  storyPath: string;
21
24
  storyExport: string;
22
25
  sourceSha256: string;
23
26
  }
27
+ export type StoryPreviewRenderer = Pick<ComponentRendererService, 'renderComponent' | 'dispose'>;
24
28
  export interface StoryPreviewServiceDependencies {
25
29
  loadConfig(appPath: string): Promise<DesignSystemConfig>;
26
30
  createBundler(config: DesignSystemConfig): BaseBundlerService;
31
+ createRenderer(config: DesignSystemConfig, appPath: string): StoryPreviewRenderer;
32
+ rendererTemporaryRoot(): string;
27
33
  createHandle(): string;
28
34
  }
29
35
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"type.d.mts","names":[],"sources":["../../../../src/services/storyPreview/type.ts"],"mappings":";;iBAEiB;EACf;EACA;EACA;EACA,OAAO;EACP;;iBAGe;EACf;EACA;EACA;EACA;EACA;;iBAGe;EACf;EACA;EACA;EACA;EACA;;iBAGe;EACf,WAAW,kBAAkB,QAAQ;EACrC,cAAc,QAAQ,qBAAqB;EAC3C"}
1
+ {"version":3,"file":"type.d.mts","names":[],"sources":["../../../../src/services/storyPreview/type.ts"],"mappings":";;iBAEiB;EACf;EACA;EACA;EACA,OAAO;EACP;;iBAGe;EACf;EACA;EACA;EACA;EACA;;iBAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;YAGU,uBAAuB,KAAK;iBAEvB;EACf,WAAW,kBAAkB,QAAQ;EACrC,cAAc,QAAQ,qBAAqB;EAC3C,eAAe,QAAQ,oBAAoB,kBAAkB;EAC7D;EACA"}
@@ -40,6 +40,9 @@ export type ExportStoryPreviewImageRequest = BuildStoryPreviewRequest;
40
40
  export interface ExportStoryPreviewImageView {
41
41
  data: string;
42
42
  mimeType: 'image/png';
43
+ captureId: string;
44
+ width: number;
45
+ height: number;
43
46
  storyPath: string;
44
47
  storyExport: string;
45
48
  sourceSha256: string;
@@ -1 +1 @@
1
- {"version":3,"file":"previewApi.d.cts","names":[],"sources":["../../../src/types/previewApi.ts"],"mappings":";qBAAa;iBAEI;EACf;IACE;IACA;IACA;IACA;IACA;;;iBAIa;EACf;EACA;;iBAGe;EACf;EACA;;iBAGe;EACf;EACA;EACA;EACA,kBAAkB;;iBAGH;EACf;EACA;EACA;EACA;;iBAGe;EACf;EACA;EACA;EACA;EACA;;YAGU,iCAAiC;iBAE5B;EACf;EACA;EACA;EACA;EACA;;iBAGe;EACf;;iBAGe;EACf"}
1
+ {"version":3,"file":"previewApi.d.cts","names":[],"sources":["../../../src/types/previewApi.ts"],"mappings":";qBAAa;iBAEI;EACf;IACE;IACA;IACA;IACA;IACA;;;iBAIa;EACf;EACA;;iBAGe;EACf;EACA;;iBAGe;EACf;EACA;EACA;EACA,kBAAkB;;iBAGH;EACf;EACA;EACA;EACA;;iBAGe;EACf;EACA;EACA;EACA;EACA;;YAGU,iCAAiC;iBAE5B;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;iBAGe;EACf;;iBAGe;EACf"}
@@ -40,6 +40,9 @@ export type ExportStoryPreviewImageRequest = BuildStoryPreviewRequest;
40
40
  export interface ExportStoryPreviewImageView {
41
41
  data: string;
42
42
  mimeType: 'image/png';
43
+ captureId: string;
44
+ width: number;
45
+ height: number;
43
46
  storyPath: string;
44
47
  storyExport: string;
45
48
  sourceSha256: string;
@@ -1 +1 @@
1
- {"version":3,"file":"previewApi.d.mts","names":[],"sources":["../../../src/types/previewApi.ts"],"mappings":";qBAAa;iBAEI;EACf;IACE;IACA;IACA;IACA;IACA;;;iBAIa;EACf;EACA;;iBAGe;EACf;EACA;;iBAGe;EACf;EACA;EACA;EACA,kBAAkB;;iBAGH;EACf;EACA;EACA;EACA;;iBAGe;EACf;EACA;EACA;EACA;EACA;;YAGU,iCAAiC;iBAE5B;EACf;EACA;EACA;EACA;EACA;;iBAGe;EACf;;iBAGe;EACf"}
1
+ {"version":3,"file":"previewApi.d.mts","names":[],"sources":["../../../src/types/previewApi.ts"],"mappings":";qBAAa;iBAEI;EACf;IACE;IACA;IACA;IACA;IACA;;;iBAIa;EACf;EACA;;iBAGe;EACf;EACA;;iBAGe;EACf;EACA;EACA;EACA,kBAAkB;;iBAGH;EACf;EACA;EACA;EACA;;iBAGe;EACf;EACA;EACA;EACA;EACA;;YAGU,iCAAiC;iBAE5B;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;iBAGe;EACf;;iBAGe;EACf"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-style-system",
3
- "version": "0.0.1-alpha.77",
3
+ "version": "0.0.1-alpha.78",
4
4
  "description": "Optional DoomPi story preview integration for the style-system package.",
5
5
  "keywords": [
6
6
  "ai",
@@ -70,15 +70,15 @@
70
70
  "access": "public"
71
71
  },
72
72
  "dependencies": {
73
- "@agimon-ai/doompi-core": "0.0.1-alpha.76",
74
- "@agimon-ai/doompi-web-components": "0.0.1-alpha.34",
75
- "@agimon-ai/doompi-web-security": "0.0.1-alpha.36",
73
+ "@agimon-ai/doompi-core": "0.0.1-alpha.77",
74
+ "@agimon-ai/doompi-web-components": "0.0.1-alpha.35",
75
+ "@agimon-ai/doompi-web-security": "0.0.1-alpha.37",
76
76
  "@agimon-ai/style-system": "0.1.3",
77
77
  "hono": "4.13.7",
78
78
  "typebox": "1.3.30"
79
79
  },
80
80
  "devDependencies": {
81
- "@agimon-ai/doompi-build": "0.0.1-alpha.5",
81
+ "@agimon-ai/doompi-build": "0.0.1-alpha.6",
82
82
  "@earendil-works/pi-coding-agent": "0.85.1",
83
83
  "@types/node": "26.5.1",
84
84
  "@types/react": "19.3.0",