@agimon-ai/doompi-style-system 0.0.1-alpha.100

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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/dist/apiContracts.cjs +118 -0
  4. package/dist/apiContracts.cjs.map +1 -0
  5. package/dist/apiContracts.d.mts +52 -0
  6. package/dist/apiContracts.d.mts.map +1 -0
  7. package/dist/apiContracts.mjs +113 -0
  8. package/dist/apiContracts.mjs.map +1 -0
  9. package/dist/cli.cjs +242 -0
  10. package/dist/cli.cjs.map +1 -0
  11. package/dist/cli.d.mts +4 -0
  12. package/dist/cli.d.mts.map +1 -0
  13. package/dist/cli.mjs +239 -0
  14. package/dist/cli.mjs.map +1 -0
  15. package/dist/designCheck-7P9r9ijq.mjs +475 -0
  16. package/dist/designCheck-7P9r9ijq.mjs.map +1 -0
  17. package/dist/designCheck-zx4PsRvE.cjs +495 -0
  18. package/dist/designCheck-zx4PsRvE.cjs.map +1 -0
  19. package/dist/extensions/pi.cjs +94 -0
  20. package/dist/extensions/pi.cjs.map +1 -0
  21. package/dist/extensions/pi.d.mts +5 -0
  22. package/dist/extensions/pi.d.mts.map +1 -0
  23. package/dist/extensions/pi.mjs +87 -0
  24. package/dist/extensions/pi.mjs.map +1 -0
  25. package/dist/extensions/server.cjs +136 -0
  26. package/dist/extensions/server.cjs.map +1 -0
  27. package/dist/extensions/server.d.mts +5 -0
  28. package/dist/extensions/server.d.mts.map +1 -0
  29. package/dist/extensions/server.mjs +131 -0
  30. package/dist/extensions/server.mjs.map +1 -0
  31. package/dist/extensions/web.mjs +942 -0
  32. package/dist/extensions/web.mjs.map +1 -0
  33. package/dist/index.cjs +8 -0
  34. package/dist/index.d.mts +171 -0
  35. package/dist/index.d.mts.map +1 -0
  36. package/dist/index.mjs +3 -0
  37. package/dist/rolldown-runtime-VH7oDXx4.cjs +28 -0
  38. package/dist/storyPreview-D-hGhlig.cjs +371 -0
  39. package/dist/storyPreview-D-hGhlig.cjs.map +1 -0
  40. package/dist/storyPreview-Dhejqhos.mjs +357 -0
  41. package/dist/storyPreview-Dhejqhos.mjs.map +1 -0
  42. package/package.json +132 -0
  43. package/skills/design-workflow/SKILL.md +84 -0
@@ -0,0 +1,942 @@
1
+ import { apiResponse, createApiClient, defineApiRoutes, defineFill, defineWebPlugin, sessionApiAddress } from "@agimon-ai/doompi-core/web";
2
+ import { Button } from "@agimon-ai/doompi-web-components";
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
+ import { sealedTransport } from "@agimon-ai/doompi-web-security/browser";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/types/apiRoutes.ts
7
+ var apiRoutes_default = defineApiRoutes({
8
+ metadata: {
9
+ method: "POST",
10
+ path: "/metadata",
11
+ response: apiResponse()
12
+ },
13
+ build: {
14
+ method: "POST",
15
+ path: "/build",
16
+ response: apiResponse()
17
+ },
18
+ image: {
19
+ method: "POST",
20
+ path: "/image",
21
+ response: apiResponse()
22
+ },
23
+ dispose: {
24
+ method: "DELETE",
25
+ path: "/artifact",
26
+ response: apiResponse()
27
+ }
28
+ });
29
+ const api = createApiClient(apiRoutes_default, {
30
+ scopes: ["session"],
31
+ basePath: "style-system-preview",
32
+ transport: (input, init) => sealedTransport.fetch(input, init),
33
+ sessionAddress: sessionApiAddress
34
+ });
35
+ //#endregion
36
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/previewApi.ts
37
+ function errorMessage(result) {
38
+ if (result.status === 0) return "The preview service is unreachable.";
39
+ return result.error === "" ? `The preview service answered ${String(result.status)}.` : result.error;
40
+ }
41
+ async function storyMetadata(sessionId, request) {
42
+ const result = await api.session(sessionId).metadata({ body: request });
43
+ return result.ok ? {
44
+ ok: true,
45
+ metadata: result.data
46
+ } : {
47
+ ok: false,
48
+ error: errorMessage(result)
49
+ };
50
+ }
51
+ async function buildPreview(sessionId, request) {
52
+ const result = await api.session(sessionId).build({ body: request });
53
+ return result.ok ? {
54
+ ok: true,
55
+ preview: result.data
56
+ } : {
57
+ ok: false,
58
+ error: errorMessage(result)
59
+ };
60
+ }
61
+ async function exportPreviewImage(sessionId, request) {
62
+ const result = await api.session(sessionId).image({ body: request });
63
+ return result.ok ? {
64
+ ok: true,
65
+ image: result.data
66
+ } : {
67
+ ok: false,
68
+ error: errorMessage(result)
69
+ };
70
+ }
71
+ async function disposePreview(sessionId, handle) {
72
+ await api.session(sessionId).dispose({ body: { handle } });
73
+ }
74
+ //#endregion
75
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/previewFrame.ts
76
+ const PREVIEW_CSP = "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'";
77
+ function findHeadInsertion(html) {
78
+ const lower = html.toLowerCase();
79
+ let index = 0;
80
+ while (index < html.length) {
81
+ if (lower.startsWith("<!--", index)) {
82
+ const end = lower.indexOf("-->", index + 4);
83
+ index = end < 0 ? html.length : end + 3;
84
+ continue;
85
+ }
86
+ if (html[index] !== "<") {
87
+ index += 1;
88
+ continue;
89
+ }
90
+ const tag = html.slice(index).match(/^<([a-z][a-z0-9:-]*)(?:\s[^>]*)?>/i);
91
+ if (tag === null) {
92
+ index += 1;
93
+ continue;
94
+ }
95
+ const name = tag[1].toLowerCase();
96
+ const end = index + tag[0].length;
97
+ if (name === "head") return end;
98
+ if (name === "script" || name === "style" || name === "template" || name === "textarea" || name === "title") {
99
+ const closing = lower.indexOf(`</${name}`, end);
100
+ index = closing < 0 ? html.length : closing;
101
+ continue;
102
+ }
103
+ index = end;
104
+ }
105
+ }
106
+ function isolatedPreviewHtml(html) {
107
+ const policy = `<meta http-equiv="Content-Security-Policy" content="${PREVIEW_CSP}">`;
108
+ const headInsertion = findHeadInsertion(html);
109
+ if (headInsertion !== void 0) return `${html.slice(0, headInsertion)}${policy}${html.slice(headInsertion)}`;
110
+ const doctype = html.match(/^\s*<!doctype[^>]*>/i);
111
+ if (doctype !== null) return `${doctype[0]}<head>${policy}</head>${html.slice(doctype[0].length)}`;
112
+ return `<head>${policy}</head>${html}`;
113
+ }
114
+ function normalizedAnnotationRect(start, end) {
115
+ return {
116
+ x: Math.min(start.x, end.x),
117
+ y: Math.min(start.y, end.y),
118
+ width: Math.abs(end.x - start.x),
119
+ height: Math.abs(end.y - start.y)
120
+ };
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
+ }
134
+ //#endregion
135
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/StoryPreviewPanel.tsx
136
+ function imageDownload(data, storyExport) {
137
+ const link = document.createElement("a");
138
+ link.href = `data:image/png;base64,${data}`;
139
+ link.download = `${storyExport.replace(/[^A-Za-z0-9_-]/g, "-")}.png`;
140
+ link.click();
141
+ }
142
+ function point(event) {
143
+ const bounds = event.currentTarget.getBoundingClientRect();
144
+ return {
145
+ x: Math.min(1, Math.max(0, (event.clientX - bounds.left) / bounds.width)),
146
+ y: Math.min(1, Math.max(0, (event.clientY - bounds.top) / bounds.height))
147
+ };
148
+ }
149
+ function preferredExport(exports) {
150
+ return exports.find((entry) => entry.exportName === "Playground")?.exportName ?? exports[0]?.exportName ?? "";
151
+ }
152
+ function errorText(reason) {
153
+ return reason instanceof Error ? reason.message : String(reason);
154
+ }
155
+ function StoryPreviewPanel({ sessionId, submitCapture, seed, source, activeTool = "select", displayedAnnotations = [], pendingCandidate = false, onAnnotationCandidate }) {
156
+ const contextualSource = source ?? seed?.source;
157
+ const sourcePath = contextualSource?.path ?? "";
158
+ const sourceDirty = contextualSource?.hasUnsavedChanges === true;
159
+ const sourceRevision = contextualSource?.revision ?? 0;
160
+ const sourceSha256 = contextualSource?.sourceSha256 ?? "";
161
+ const contextual = contextualSource !== void 0;
162
+ const [appPath, setAppPath] = useState(contextual ? "" : ".");
163
+ const [storyPath, setStoryPath] = useState(sourcePath);
164
+ const [storyExport, setStoryExport] = useState(contextual ? "" : "Playground");
165
+ const [storyExports, setStoryExports] = useState();
166
+ const [darkMode, setDarkMode] = useState(false);
167
+ const [preview, setPreview] = useState(void 0);
168
+ const [builtRequest, setBuiltRequest] = useState(void 0);
169
+ const [annotationImage, setAnnotationImage] = useState(void 0);
170
+ const [annotationStart, setAnnotationStart] = useState(void 0);
171
+ const [annotationRect, setAnnotationRect] = useState(void 0);
172
+ const [feedback, setFeedback] = useState("");
173
+ const [status, setStatus] = useState(contextual ? sourceDirty ? "Loading story metadata. Preview uses saved source." : "Loading story metadata…" : "Choose an exact story file and export.");
174
+ const [working, setWorking] = useState(false);
175
+ const generationRef = useRef(0);
176
+ const operationRef = useRef(0);
177
+ const previewRef = useRef(void 0);
178
+ const builtRequestRef = useRef(void 0);
179
+ const workingRef = useRef(false);
180
+ const dirtyRef = useRef(sourceDirty);
181
+ const annotationExportRef = useRef("");
182
+ const [stroke, setStroke] = useState([]);
183
+ const [zoom, setZoom] = useState(1);
184
+ const [gesture, setGesture] = useState();
185
+ const pointers = useRef(/* @__PURE__ */ new Map());
186
+ const pinch = useRef(void 0);
187
+ const pan = useRef(void 0);
188
+ const imageScroll = useRef(null);
189
+ dirtyRef.current = sourceDirty;
190
+ const request = useMemo(() => ({
191
+ appPath,
192
+ storyPath,
193
+ storyExport,
194
+ darkMode
195
+ }), [
196
+ appPath,
197
+ storyPath,
198
+ storyExport,
199
+ darkMode
200
+ ]);
201
+ useEffect(() => {
202
+ operationRef.current += 1;
203
+ if (!workingRef.current) return;
204
+ workingRef.current = false;
205
+ setWorking(false);
206
+ }, [request]);
207
+ useEffect(() => {
208
+ const generation = generationRef.current + 1;
209
+ generationRef.current = generation;
210
+ operationRef.current += 1;
211
+ workingRef.current = false;
212
+ setWorking(false);
213
+ setStoryExports(void 0);
214
+ setAppPath(contextual ? "" : ".");
215
+ setStoryPath(sourcePath);
216
+ setStoryExport(contextual ? "" : "Playground");
217
+ builtRequestRef.current = void 0;
218
+ setBuiltRequest(void 0);
219
+ setAnnotationImage(void 0);
220
+ setAnnotationStart(void 0);
221
+ setAnnotationRect(void 0);
222
+ setStroke([]);
223
+ setZoom(1);
224
+ pointers.current.clear();
225
+ pinch.current = void 0;
226
+ if (sessionId === null || sourcePath === "") {
227
+ setPreview(void 0);
228
+ previewRef.current = void 0;
229
+ return () => {
230
+ if (generationRef.current === generation) generationRef.current += 1;
231
+ };
232
+ }
233
+ const previous = previewRef.current;
234
+ previewRef.current = void 0;
235
+ setPreview(void 0);
236
+ if (previous !== void 0) disposePreview(sessionId, previous.handle);
237
+ let current = true;
238
+ setStatus(sourceDirty ? "Loading story metadata. Preview uses saved source." : "Loading story metadata…");
239
+ storyMetadata(sessionId, { storyPath: sourcePath }).then((result) => {
240
+ if (!current || generationRef.current !== generation) return;
241
+ if (!result.ok) {
242
+ setStoryExports(void 0);
243
+ setStatus(result.error);
244
+ return;
245
+ }
246
+ const exports = result.metadata.exports;
247
+ setAppPath(result.metadata.appPath);
248
+ setStoryPath(result.metadata.storyPath);
249
+ setStoryExports(exports);
250
+ setStoryExport(preferredExport(exports));
251
+ if (exports.length === 0) setStatus("No story exports were found in this file.");
252
+ else if (dirtyRef.current) setStatus("Save changes to preview. Preview uses saved source.");
253
+ else if (exports.length === 1) setStatus("Story found. Building preview…");
254
+ else setStatus("Story found. Building the Playground export…");
255
+ }).catch((reason) => {
256
+ if (current && generationRef.current === generation) setStatus(errorText(reason));
257
+ });
258
+ return () => {
259
+ current = false;
260
+ if (generationRef.current === generation) generationRef.current += 1;
261
+ operationRef.current += 1;
262
+ const ownedPreview = previewRef.current;
263
+ previewRef.current = void 0;
264
+ if (ownedPreview !== void 0) disposePreview(sessionId, ownedPreview.handle);
265
+ };
266
+ }, [
267
+ contextual,
268
+ sessionId,
269
+ sourcePath,
270
+ sourceRevision,
271
+ sourceSha256
272
+ ]);
273
+ useEffect(() => {
274
+ if (!contextual || !sourceDirty) return;
275
+ setStatus("Save changes to preview. Preview uses saved source.");
276
+ }, [contextual, sourceDirty]);
277
+ const rebuild = useCallback(async () => {
278
+ if (sessionId === null || workingRef.current || request.appPath.trim() === "" || request.storyPath.trim() === "" || request.storyExport.trim() === "") return false;
279
+ if (dirtyRef.current) {
280
+ setStatus("Save changes to preview. Preview uses saved source.");
281
+ return false;
282
+ }
283
+ const generation = generationRef.current;
284
+ const operation = operationRef.current + 1;
285
+ operationRef.current = operation;
286
+ const requestSessionId = sessionId;
287
+ workingRef.current = true;
288
+ setWorking(true);
289
+ setStatus("Building preview…");
290
+ try {
291
+ const result = await buildPreview(requestSessionId, request);
292
+ if (generationRef.current !== generation || operationRef.current !== operation) {
293
+ if (result.ok) disposePreview(requestSessionId, result.preview.handle);
294
+ return false;
295
+ }
296
+ if (!result.ok) {
297
+ setStatus(result.error);
298
+ return false;
299
+ }
300
+ const previous = previewRef.current;
301
+ previewRef.current = result.preview;
302
+ builtRequestRef.current = request;
303
+ setPreview(result.preview);
304
+ setBuiltRequest(request);
305
+ setAnnotationImage(void 0);
306
+ setAnnotationStart(void 0);
307
+ setAnnotationRect(void 0);
308
+ if (previous !== void 0 && previous.handle !== result.preview.handle) disposePreview(requestSessionId, previous.handle);
309
+ setStatus(dirtyRef.current ? "Preview ready from saved source. Save changes to refresh." : "Preview ready. Refresh after source edits.");
310
+ return true;
311
+ } catch (reason) {
312
+ if (generationRef.current === generation && operationRef.current === operation) setStatus(errorText(reason));
313
+ return false;
314
+ } finally {
315
+ if (generationRef.current === generation && operationRef.current === operation) {
316
+ workingRef.current = false;
317
+ setWorking(false);
318
+ }
319
+ }
320
+ }, [request, sessionId]);
321
+ useEffect(() => {
322
+ if (!contextual || sessionId === null || sourceDirty || storyExports === void 0 || storyExport.trim() === "" || appPath.trim() === "" || storyPath.trim() === "") return;
323
+ rebuild();
324
+ }, [
325
+ appPath,
326
+ contextual,
327
+ rebuild,
328
+ sessionId,
329
+ sourceDirty,
330
+ storyExport,
331
+ storyExports,
332
+ storyPath
333
+ ]);
334
+ const image = useCallback(async () => {
335
+ const currentRequest = builtRequestRef.current;
336
+ if (sessionId === null || workingRef.current || currentRequest === void 0) return void 0;
337
+ const generation = generationRef.current;
338
+ const operation = operationRef.current + 1;
339
+ operationRef.current = operation;
340
+ const requestSessionId = sessionId;
341
+ workingRef.current = true;
342
+ setWorking(true);
343
+ setStatus("Rendering a fresh source-backed image…");
344
+ try {
345
+ const result = await exportPreviewImage(requestSessionId, currentRequest);
346
+ if (generationRef.current !== generation || operationRef.current !== operation) return void 0;
347
+ if (!result.ok) {
348
+ setStatus(result.error);
349
+ return;
350
+ }
351
+ setStatus("Image rendered from the current source. Transient iframe interactions are not included.");
352
+ return result.image;
353
+ } catch (reason) {
354
+ if (generationRef.current === generation && operationRef.current === operation) setStatus(errorText(reason));
355
+ return;
356
+ } finally {
357
+ if (generationRef.current === generation && operationRef.current === operation) {
358
+ workingRef.current = false;
359
+ setWorking(false);
360
+ }
361
+ }
362
+ }, [sessionId]);
363
+ async function prepareAnnotation() {
364
+ const generation = generationRef.current;
365
+ const result = await image();
366
+ if (result === void 0 || generationRef.current !== generation) return;
367
+ setAnnotationImage(result);
368
+ setAnnotationRect(void 0);
369
+ setStatus("Annotation image ready. Drag a region or comment on the whole preview.");
370
+ }
371
+ useEffect(() => {
372
+ if (onAnnotationCandidate === void 0) return;
373
+ if (activeTool === "select") {
374
+ annotationExportRef.current = "";
375
+ setAnnotationImage(void 0);
376
+ setAnnotationStart(void 0);
377
+ setAnnotationRect(void 0);
378
+ return;
379
+ }
380
+ if (preview === void 0 || annotationImage !== void 0 || working || annotationExportRef.current === preview.handle) return;
381
+ const generation = generationRef.current;
382
+ image().then((result) => {
383
+ if (result === void 0 || generationRef.current !== generation) return;
384
+ annotationExportRef.current = preview.handle;
385
+ setAnnotationImage(result);
386
+ setStatus("Frozen source-backed image ready for Author annotations.");
387
+ });
388
+ }, [
389
+ activeTool,
390
+ annotationImage,
391
+ image,
392
+ onAnnotationCandidate,
393
+ preview,
394
+ working
395
+ ]);
396
+ function updateAnnotation(event) {
397
+ if (annotationStart === void 0) return;
398
+ const end = point(event);
399
+ setAnnotationRect(normalizedAnnotationRect(annotationStart, end));
400
+ }
401
+ function authorCandidate(location, rect, points) {
402
+ if (annotationImage === void 0 || onAnnotationCandidate === void 0 || builtRequest === void 0) return;
403
+ const binary = atob(annotationImage.data);
404
+ const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
405
+ const evidence = new Blob([bytes], { type: annotationImage.mimeType });
406
+ const thumbnailUrl = URL.createObjectURL(evidence);
407
+ try {
408
+ onAnnotationCandidate({
409
+ mode: rect === void 0 ? "point" : "region",
410
+ point: location,
411
+ ...rect === void 0 ? {} : { rect },
412
+ ...points === void 0 ? {} : { stroke: points.map((point) => ({ ...point })) },
413
+ preview: {
414
+ version: 1,
415
+ provider: "style-system",
416
+ projectPath: builtRequest.appPath,
417
+ storyPath: annotationImage.storyPath,
418
+ storyExport: annotationImage.storyExport,
419
+ buildRevision: annotationImage.sourceSha256,
420
+ viewport: {
421
+ width: annotationImage.width,
422
+ height: annotationImage.height
423
+ },
424
+ sources: [{
425
+ path: annotationImage.storyPath,
426
+ sha256: annotationImage.sourceSha256
427
+ }]
428
+ },
429
+ evidence,
430
+ thumbnailUrl
431
+ });
432
+ } catch (reason) {
433
+ URL.revokeObjectURL(thumbnailUrl);
434
+ setStatus(errorText(reason));
435
+ }
436
+ }
437
+ const multipleExports = storyExports !== void 0 && storyExports.length > 1;
438
+ const previewVisible = preview !== void 0;
439
+ const authorAnnotationActive = onAnnotationCandidate !== void 0 && activeTool !== "select";
440
+ useEffect(() => {
441
+ setStroke([]);
442
+ setAnnotationStart(void 0);
443
+ pointers.current.clear();
444
+ pinch.current = void 0;
445
+ pan.current = void 0;
446
+ }, [activeTool, annotationImage?.sourceSha256]);
447
+ return /* @__PURE__ */ jsxs("section", {
448
+ className: "flex h-full min-h-0 min-w-0 flex-1 flex-col gap-3 p-3",
449
+ "data-testid": "style-system-preview-panel",
450
+ children: [
451
+ contextual ? /* @__PURE__ */ jsxs("header", {
452
+ className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-doom-dim",
453
+ children: [
454
+ /* @__PURE__ */ jsxs("span", { children: ["Story: ", /* @__PURE__ */ jsx("strong", {
455
+ className: "text-doom-text",
456
+ children: storyPath || sourcePath
457
+ })] }),
458
+ /* @__PURE__ */ jsxs("span", { children: ["Project: ", /* @__PURE__ */ jsx("strong", {
459
+ className: "text-doom-text",
460
+ children: appPath || "resolving…"
461
+ })] }),
462
+ multipleExports ? /* @__PURE__ */ jsxs("label", {
463
+ className: "flex items-center gap-1",
464
+ children: ["Export", /* @__PURE__ */ jsx("select", {
465
+ className: "rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text",
466
+ value: storyExport,
467
+ "aria-label": "story export",
468
+ onChange: (event) => {
469
+ setStoryExport(event.target.value);
470
+ builtRequestRef.current = void 0;
471
+ setBuiltRequest(void 0);
472
+ setAnnotationImage(void 0);
473
+ setAnnotationRect(void 0);
474
+ },
475
+ children: storyExports.map((entry) => /* @__PURE__ */ jsx("option", {
476
+ value: entry.exportName,
477
+ children: entry.label ?? entry.exportName
478
+ }, entry.exportName))
479
+ })]
480
+ }) : storyExport !== "" ? /* @__PURE__ */ jsxs("span", { children: ["Export: ", /* @__PURE__ */ jsx("strong", {
481
+ className: "text-doom-text",
482
+ children: storyExport
483
+ })] }) : null
484
+ ]
485
+ }) : /* @__PURE__ */ jsxs("header", {
486
+ className: "grid gap-2 md:grid-cols-3",
487
+ children: [
488
+ /* @__PURE__ */ jsxs("label", {
489
+ className: "grid gap-1 text-xs text-doom-dim",
490
+ children: ["Project path", /* @__PURE__ */ jsx("input", {
491
+ className: "rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text",
492
+ value: appPath,
493
+ onChange: (event) => setAppPath(event.target.value)
494
+ })]
495
+ }),
496
+ /* @__PURE__ */ jsxs("label", {
497
+ className: "grid gap-1 text-xs text-doom-dim",
498
+ children: ["Story file", /* @__PURE__ */ jsx("input", {
499
+ className: "rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text",
500
+ placeholder: "packages/ui/Button.stories.tsx",
501
+ value: storyPath,
502
+ onChange: (event) => {
503
+ setStoryPath(event.target.value);
504
+ setStoryExports(void 0);
505
+ }
506
+ })]
507
+ }),
508
+ /* @__PURE__ */ jsxs("label", {
509
+ className: "grid gap-1 text-xs text-doom-dim",
510
+ children: ["Story export", storyExports === void 0 ? /* @__PURE__ */ jsx("input", {
511
+ className: "rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text",
512
+ value: storyExport,
513
+ onChange: (event) => setStoryExport(event.target.value)
514
+ }) : /* @__PURE__ */ jsxs("select", {
515
+ className: "rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text",
516
+ value: storyExport,
517
+ onChange: (event) => setStoryExport(event.target.value),
518
+ children: [/* @__PURE__ */ jsx("option", {
519
+ value: "",
520
+ children: "Choose an export"
521
+ }), storyExports.map((entry) => /* @__PURE__ */ jsx("option", {
522
+ value: entry.exportName,
523
+ children: entry.label ?? entry.exportName
524
+ }, entry.exportName))]
525
+ })]
526
+ })
527
+ ]
528
+ }),
529
+ /* @__PURE__ */ jsxs("div", {
530
+ className: "flex flex-wrap items-center gap-2",
531
+ children: [
532
+ /* @__PURE__ */ jsx(Button, {
533
+ disabled: sessionId === null || working || sourceDirty || request.storyPath.trim() === "" || request.storyExport.trim() === "",
534
+ onClick: () => void rebuild(),
535
+ children: previewVisible ? "Refresh preview" : "Build preview"
536
+ }),
537
+ /* @__PURE__ */ jsx(Button, {
538
+ variant: "outline",
539
+ disabled: preview === void 0 || working,
540
+ onClick: () => void image().then((result) => result && imageDownload(result.data, result.storyExport)),
541
+ children: "Export PNG"
542
+ }),
543
+ onAnnotationCandidate === void 0 ? /* @__PURE__ */ jsx(Button, {
544
+ variant: "outline",
545
+ disabled: preview === void 0 || working,
546
+ onClick: () => void prepareAnnotation(),
547
+ children: "Prepare annotation"
548
+ }) : null,
549
+ /* @__PURE__ */ jsxs("label", {
550
+ className: "flex items-center gap-1 text-xs text-doom-dim",
551
+ children: [/* @__PURE__ */ jsx("input", {
552
+ type: "checkbox",
553
+ checked: darkMode,
554
+ onChange: (event) => setDarkMode(event.target.checked)
555
+ }), " Dark mode"]
556
+ }),
557
+ /* @__PURE__ */ jsx("output", {
558
+ className: "text-xs text-doom-dim",
559
+ "aria-live": "polite",
560
+ children: status
561
+ })
562
+ ]
563
+ }),
564
+ sourceDirty ? /* @__PURE__ */ jsx("p", {
565
+ "data-testid": "style-system-preview-dirty",
566
+ className: "text-xs text-doom-yellow",
567
+ children: "Save changes to preview. Preview uses saved source."
568
+ }) : null,
569
+ preview === void 0 ? /* @__PURE__ */ jsx("div", {
570
+ className: "grid min-h-48 place-items-center rounded border border-dashed border-doom-border text-sm text-doom-faint",
571
+ children: "No preview built."
572
+ }) : authorAnnotationActive && annotationImage !== void 0 ? /* @__PURE__ */ jsxs("div", {
573
+ className: "flex min-h-0 flex-1 flex-col overflow-hidden rounded border border-doom-border bg-doom-deep",
574
+ children: [/* @__PURE__ */ jsxs("div", {
575
+ className: "flex items-center gap-2 p-2",
576
+ role: "group",
577
+ "aria-label": "Preview zoom",
578
+ children: [
579
+ /* @__PURE__ */ jsx(Button, {
580
+ variant: "outline",
581
+ className: "min-h-11 min-w-11",
582
+ "aria-label": "Zoom out",
583
+ onClick: () => setZoom((value) => Math.max(1, value / 1.5)),
584
+ children: "−"
585
+ }),
586
+ /* @__PURE__ */ jsxs("output", {
587
+ className: "text-xs text-doom-dim",
588
+ children: [Math.round(zoom * 100), "%"]
589
+ }),
590
+ /* @__PURE__ */ jsx(Button, {
591
+ variant: "outline",
592
+ className: "min-h-11 min-w-11",
593
+ "aria-label": "Zoom in",
594
+ onClick: () => setZoom((value) => Math.min(8, value * 1.5)),
595
+ children: "+"
596
+ }),
597
+ /* @__PURE__ */ jsx(Button, {
598
+ variant: "outline",
599
+ className: "min-h-11",
600
+ "aria-label": "Fit preview",
601
+ onClick: () => setZoom(1),
602
+ children: "Fit"
603
+ })
604
+ ]
605
+ }), /* @__PURE__ */ jsx("div", {
606
+ ref: imageScroll,
607
+ className: "min-h-0 flex-1 overflow-auto",
608
+ children: /* @__PURE__ */ jsxs("div", {
609
+ className: "relative w-fit max-w-full overflow-hidden",
610
+ style: {
611
+ zoom,
612
+ touchAction: "none"
613
+ },
614
+ "aria-label": activeTool === "comment" ? "Click to place an annotation point" : activeTool === "draw" ? "Draw a feedback location mark" : activeTool === "pan" ? "Drag to pan preview" : "Drag to mark an annotation region",
615
+ "data-testid": "style-system-author-annotation-overlay",
616
+ onPointerDown: (event) => {
617
+ if (pendingCandidate) return;
618
+ pointers.current.set(event.pointerId, {
619
+ x: event.clientX,
620
+ y: event.clientY
621
+ });
622
+ event.currentTarget.setPointerCapture(event.pointerId);
623
+ if (pointers.current.size === 2) {
624
+ setStroke([]);
625
+ setAnnotationStart(void 0);
626
+ const [a, b] = [...pointers.current.values()];
627
+ pinch.current = {
628
+ distance: Math.hypot(a.x - b.x, a.y - b.y),
629
+ zoom
630
+ };
631
+ return;
632
+ }
633
+ if (activeTool === "pan") {
634
+ const container = imageScroll.current;
635
+ if (container) pan.current = {
636
+ x: event.clientX,
637
+ y: event.clientY,
638
+ left: container.scrollLeft,
639
+ top: container.scrollTop
640
+ };
641
+ return;
642
+ }
643
+ if (activeTool !== "mark" && activeTool !== "comment" && activeTool !== "draw") return;
644
+ setGesture(event.pointerId);
645
+ if (activeTool === "draw") setStroke([point(event)]);
646
+ const start = point(event);
647
+ setAnnotationStart(start);
648
+ setAnnotationRect(activeTool === "mark" ? {
649
+ ...start,
650
+ width: 0,
651
+ height: 0
652
+ } : void 0);
653
+ },
654
+ onPointerMove: (event) => {
655
+ if (pointers.current.has(event.pointerId)) pointers.current.set(event.pointerId, {
656
+ x: event.clientX,
657
+ y: event.clientY
658
+ });
659
+ if (pinch.current !== void 0 && pointers.current.size >= 2) {
660
+ const [a, b] = [...pointers.current.values()];
661
+ if (pinch.current.distance > 0) setZoom(Math.max(1, Math.min(8, pinch.current.zoom * Math.hypot(a.x - b.x, a.y - b.y) / pinch.current.distance)));
662
+ return;
663
+ }
664
+ if (pan.current !== void 0 && activeTool === "pan") {
665
+ const container = imageScroll.current;
666
+ if (container) {
667
+ container.scrollLeft = pan.current.left + pan.current.x - event.clientX;
668
+ container.scrollTop = pan.current.top + pan.current.y - event.clientY;
669
+ }
670
+ return;
671
+ }
672
+ if (gesture !== event.pointerId) return;
673
+ if (activeTool === "mark") updateAnnotation(event);
674
+ if (activeTool === "draw") setStroke((points) => points.length >= 128 ? points : [...points, point(event)]);
675
+ },
676
+ onPointerUp: (event) => {
677
+ pointers.current.delete(event.pointerId);
678
+ if (pinch.current !== void 0 || pan.current !== void 0) {
679
+ if (pointers.current.size < 2) pinch.current = void 0;
680
+ pan.current = void 0;
681
+ return;
682
+ }
683
+ const start = annotationStart;
684
+ if (start === void 0 || gesture !== event.pointerId) return;
685
+ if (activeTool === "draw") {
686
+ const points = stroke.length >= 128 ? stroke : [...stroke, point(event)];
687
+ setStroke([]);
688
+ setAnnotationStart(void 0);
689
+ if (points.length < 2 || !points.some((item) => item.x !== points[0]?.x || item.y !== points[0]?.y)) return;
690
+ const x = Math.min(...points.map((item) => item.x));
691
+ const y = Math.min(...points.map((item) => item.y));
692
+ const rect = {
693
+ x: Math.min(x, .999),
694
+ y: Math.min(y, .999),
695
+ width: Math.max(.001, Math.max(...points.map((item) => item.x)) - x),
696
+ height: Math.max(.001, Math.max(...points.map((item) => item.y)) - y)
697
+ };
698
+ authorCandidate(points[0], rect, points);
699
+ return;
700
+ }
701
+ const end = point(event);
702
+ setAnnotationStart(void 0);
703
+ const location = previewAnnotationLocation(activeTool === "comment" ? "comment" : "mark", start, end);
704
+ if (location === null) return;
705
+ setAnnotationRect(location.mode === "region" ? location.rect : void 0);
706
+ authorCandidate(location.point, location.mode === "region" ? location.rect : void 0);
707
+ },
708
+ onPointerCancel: () => {
709
+ setAnnotationStart(void 0);
710
+ setAnnotationRect(void 0);
711
+ setStroke([]);
712
+ pointers.current.clear();
713
+ pinch.current = void 0;
714
+ pan.current = void 0;
715
+ },
716
+ children: [
717
+ /* @__PURE__ */ jsx("img", {
718
+ className: "block max-h-full max-w-full select-none",
719
+ src: `data:${annotationImage.mimeType};base64,${annotationImage.data}`,
720
+ alt: "Frozen source-backed story render for Author annotation",
721
+ draggable: false
722
+ }),
723
+ displayedAnnotations.map((annotation) => annotation.stroke !== void 0 ? /* @__PURE__ */ jsxs("div", {
724
+ "data-author-stroke": annotation.ordinal,
725
+ className: "pointer-events-none absolute inset-0",
726
+ children: [/* @__PURE__ */ jsx("svg", {
727
+ "aria-hidden": "true",
728
+ className: "absolute inset-0 h-full w-full text-doom-yellow",
729
+ viewBox: "0 0 1000 1000",
730
+ preserveAspectRatio: "none",
731
+ children: /* @__PURE__ */ jsx("polyline", {
732
+ points: annotation.stroke.map((point) => `${String(point.x * 1e3)},${String(point.y * 1e3)}`).join(" "),
733
+ fill: "none",
734
+ stroke: "currentColor",
735
+ strokeWidth: "5",
736
+ strokeLinecap: "round",
737
+ strokeLinejoin: "round"
738
+ })
739
+ }), /* @__PURE__ */ jsx("span", {
740
+ className: "absolute flex h-5 min-w-5 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-doom-yellow px-1 text-2xs font-bold text-doom-deep",
741
+ style: {
742
+ left: `${String(annotation.stroke[0].x * 100)}%`,
743
+ top: `${String(annotation.stroke[0].y * 100)}%`
744
+ },
745
+ children: annotation.ordinal
746
+ })]
747
+ }, annotation.ordinal) : annotation.mode === "point" || annotation.rect === void 0 ? /* @__PURE__ */ jsx("span", {
748
+ "data-author-point": annotation.ordinal,
749
+ 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",
750
+ style: {
751
+ left: `${String(annotation.point.x * 100)}%`,
752
+ top: `${String(annotation.point.y * 100)}%`
753
+ },
754
+ children: annotation.ordinal
755
+ }, annotation.ordinal) : /* @__PURE__ */ jsx("div", {
756
+ "data-author-region": annotation.ordinal,
757
+ className: "pointer-events-none absolute border border-doom-yellow bg-doom-yellow/10",
758
+ style: {
759
+ left: `${String(annotation.rect.x * 100)}%`,
760
+ top: `${String(annotation.rect.y * 100)}%`,
761
+ width: `${String(annotation.rect.width * 100)}%`,
762
+ height: `${String(annotation.rect.height * 100)}%`
763
+ },
764
+ children: /* @__PURE__ */ jsx("span", {
765
+ className: "bg-doom-yellow text-doom-deep",
766
+ children: annotation.ordinal
767
+ })
768
+ }, annotation.ordinal)),
769
+ stroke.length < 2 ? null : /* @__PURE__ */ jsx("svg", {
770
+ "aria-hidden": "true",
771
+ "data-testid": "style-system-author-stroke-selection",
772
+ className: "pointer-events-none absolute inset-0 h-full w-full text-doom-red",
773
+ viewBox: "0 0 1000 1000",
774
+ preserveAspectRatio: "none",
775
+ children: /* @__PURE__ */ jsx("polyline", {
776
+ points: stroke.map((point) => `${String(point.x * 1e3)},${String(point.y * 1e3)}`).join(" "),
777
+ fill: "none",
778
+ stroke: "currentColor",
779
+ strokeWidth: "5",
780
+ strokeLinecap: "round",
781
+ strokeLinejoin: "round"
782
+ })
783
+ }),
784
+ annotationRect === void 0 ? null : /* @__PURE__ */ jsx("div", {
785
+ className: "pointer-events-none absolute border-2 border-red-500 bg-red-500/10",
786
+ style: {
787
+ left: `${String(annotationRect.x * 100)}%`,
788
+ top: `${String(annotationRect.y * 100)}%`,
789
+ width: `${String(annotationRect.width * 100)}%`,
790
+ height: `${String(annotationRect.height * 100)}%`
791
+ }
792
+ })
793
+ ]
794
+ })
795
+ })]
796
+ }) : /* @__PURE__ */ jsx("iframe", {
797
+ title: `${preview.storyExport} story preview`,
798
+ sandbox: "allow-scripts",
799
+ referrerPolicy: "no-referrer",
800
+ srcDoc: isolatedPreviewHtml(preview.html),
801
+ className: "min-h-0 flex-1 rounded border border-doom-border bg-white"
802
+ }, preview.handle),
803
+ annotationImage === void 0 || onAnnotationCandidate !== void 0 ? null : /* @__PURE__ */ jsxs("div", {
804
+ className: "grid gap-2",
805
+ children: [/* @__PURE__ */ jsxs("div", {
806
+ className: "relative max-h-80 w-fit max-w-full touch-none overflow-hidden rounded border border-doom-border",
807
+ "aria-label": "Drag to mark an annotation region",
808
+ onPointerDown: (event) => {
809
+ event.currentTarget.setPointerCapture(event.pointerId);
810
+ const start = point(event);
811
+ setAnnotationStart(start);
812
+ setAnnotationRect({
813
+ ...start,
814
+ width: 0,
815
+ height: 0
816
+ });
817
+ },
818
+ onPointerMove: updateAnnotation,
819
+ onPointerUp: (event) => {
820
+ updateAnnotation(event);
821
+ setAnnotationStart(void 0);
822
+ },
823
+ children: [/* @__PURE__ */ jsx("img", {
824
+ className: "block max-h-80 max-w-full select-none",
825
+ src: `data:${annotationImage.mimeType};base64,${annotationImage.data}`,
826
+ alt: "Fresh source-backed story render for annotation",
827
+ draggable: false
828
+ }), annotationRect === void 0 ? null : /* @__PURE__ */ jsx("div", {
829
+ className: "pointer-events-none absolute border-2 border-red-500 bg-red-500/10",
830
+ style: {
831
+ left: `${String(annotationRect.x * 100)}%`,
832
+ top: `${String(annotationRect.y * 100)}%`,
833
+ width: `${String(annotationRect.width * 100)}%`,
834
+ height: `${String(annotationRect.height * 100)}%`
835
+ }
836
+ })]
837
+ }), /* @__PURE__ */ jsx("p", {
838
+ className: "text-xs text-doom-faint",
839
+ children: "This image is rebuilt from source and does not preserve transient iframe interactions."
840
+ })]
841
+ }),
842
+ preview === void 0 || onAnnotationCandidate !== void 0 ? null : /* @__PURE__ */ jsxs("div", {
843
+ className: "flex gap-2",
844
+ children: [/* @__PURE__ */ jsxs("label", {
845
+ className: "grid min-w-0 flex-1 gap-1 text-xs text-doom-dim",
846
+ children: ["AI design feedback", /* @__PURE__ */ jsx("textarea", {
847
+ className: "min-h-20 rounded border border-doom-border bg-doom-bg px-2 py-1 text-doom-text",
848
+ value: feedback,
849
+ onChange: (event) => setFeedback(event.target.value)
850
+ })]
851
+ }), /* @__PURE__ */ jsx(Button, {
852
+ className: "self-end",
853
+ variant: "outline",
854
+ disabled: working || submitCapture === void 0 || feedback.trim() === "" || annotationImage === void 0,
855
+ onClick: () => {
856
+ if (submitCapture === void 0 || annotationImage === void 0) return;
857
+ const location = annotationRect === void 0 ? "Whole preview" : `Region: x=${annotationRect.x.toFixed(3)}, y=${annotationRect.y.toFixed(3)}, width=${annotationRect.width.toFixed(3)}, height=${annotationRect.height.toFixed(3)}`;
858
+ const generation = generationRef.current;
859
+ submitCapture({
860
+ ...annotationImage,
861
+ context: {
862
+ kind: "author-capture",
863
+ source: "style-system",
864
+ id: crypto.randomUUID(),
865
+ label: `${annotationImage.storyPath}#${annotationImage.storyExport}`,
866
+ content: [
867
+ "Edit the underlying story or component source, never the generated preview HTML.",
868
+ `Project: ${builtRequest?.appPath ?? appPath}`,
869
+ `Story: ${annotationImage.storyPath}#${annotationImage.storyExport}`,
870
+ `Story source sha256: ${annotationImage.sourceSha256}`,
871
+ `Source-backed image revision: ${annotationImage.sourceSha256}`,
872
+ location,
873
+ `Feedback: ${feedback.trim()}`,
874
+ "After editing, rebuild this preview before treating it as visually verified."
875
+ ].join("\n")
876
+ }
877
+ }).then(() => {
878
+ if (generationRef.current !== generation) return;
879
+ setStatus("Feedback and the frozen source-backed annotation image were submitted to the agent.");
880
+ setFeedback("");
881
+ }).catch((reason) => {
882
+ if (generationRef.current === generation) setStatus(errorText(reason));
883
+ });
884
+ },
885
+ children: "Send to AI"
886
+ })]
887
+ })
888
+ ]
889
+ });
890
+ }
891
+ //#endregion
892
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/StoryPreviewLauncher.tsx
893
+ function sourceKey(path) {
894
+ let hash = 2166136261;
895
+ for (const character of path) hash = Math.imul(hash ^ character.codePointAt(0), 16777619);
896
+ return (hash >>> 0).toString(36);
897
+ }
898
+ function storyPreviewTab(seed) {
899
+ const sourcePath = seed?.source?.path;
900
+ return {
901
+ id: sourcePath === void 0 ? "style-system-story-preview" : `style-system-story-preview-${sourceKey(sourcePath)}`,
902
+ label: "Story preview",
903
+ panel: (props) => /* @__PURE__ */ jsx(StoryPreviewPanel, {
904
+ ...props,
905
+ seed
906
+ }),
907
+ retainComposer: true
908
+ };
909
+ }
910
+ //#endregion
911
+ //#region src/extensions/workspaces/sessions/(frontend)/fill/StyleSystem.author.preview-action.web.tsx
912
+ var StyleSystem_author_preview_action_web_default = defineFill({
913
+ slot: "author.preview-action",
914
+ id: "style-system",
915
+ data: {
916
+ version: 1,
917
+ label: "Preview",
918
+ detail: "Build, inspect, refresh, and send source-targeted visual feedback.",
919
+ supportsSource: (source) => source.kind === "story-preview",
920
+ embeddedPanel: StoryPreviewPanel,
921
+ createTab: ({ source }) => storyPreviewTab(source === void 0 ? void 0 : { source })
922
+ }
923
+ });
924
+ //#endregion
925
+ //#region generated/web.ts
926
+ const via = (identity, value) => value === void 0 ? void 0 : {
927
+ ...identity,
928
+ ...value
929
+ };
930
+ const webPlugin = defineWebPlugin({
931
+ id: "style-system",
932
+ session: { get fills() {
933
+ return [via({
934
+ slot: "author.preview-action",
935
+ id: "style-system"
936
+ }, StyleSystem_author_preview_action_web_default)];
937
+ } }
938
+ });
939
+ //#endregion
940
+ export { webPlugin };
941
+
942
+ //# sourceMappingURL=web.mjs.map