@agent-native/core 0.134.0 → 0.134.1

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 (57) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/docs/content/template-content-developers.mdx +1 -1
  4. package/corpus/core/package.json +1 -1
  5. package/corpus/templates/clips/changelog/2026-07-29-meeting-notes-no-longer-transcribe-the-other-side-twice-when.md +6 -0
  6. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +37 -48
  7. package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +7 -5
  8. package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +182 -106
  9. package/corpus/templates/clips/desktop/src/overlays/live-transcript.tsx +43 -14
  10. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +3 -3
  11. package/corpus/templates/clips/desktop/src-tauri/src/echo_guard.rs +443 -0
  12. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
  13. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +125 -16
  14. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -4
  15. package/corpus/templates/content/actions/_database-membership-lock.ts +25 -0
  16. package/corpus/templates/content/actions/_database-row-batch.ts +6 -2
  17. package/corpus/templates/content/actions/_database-source-utils.ts +289 -129
  18. package/corpus/templates/content/actions/_database-utils.ts +12 -3
  19. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +39 -21
  20. package/corpus/templates/content/actions/attach-content-database-source.ts +4 -1
  21. package/corpus/templates/content/actions/change-content-database-source-role.ts +3 -5
  22. package/corpus/templates/content/actions/duplicate-document-property.ts +59 -46
  23. package/corpus/templates/content/actions/execute-builder-source-execution.ts +77 -71
  24. package/corpus/templates/content/actions/materialize-builder-required-fields.ts +7 -0
  25. package/corpus/templates/content/actions/remove-database-items.ts +183 -0
  26. package/corpus/templates/content/actions/set-document-property.ts +57 -39
  27. package/corpus/templates/content/actions/sync-local-folder-source.ts +7 -0
  28. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +471 -329
  29. package/corpus/templates/content/app/components/editor/database/row-access.ts +45 -0
  30. package/corpus/templates/content/app/components/editor/database/shared.tsx +141 -38
  31. package/corpus/templates/content/app/hooks/use-content-database.ts +2 -2
  32. package/corpus/templates/content/app/i18n-data.ts +130 -0
  33. package/corpus/templates/content/changelog/2026-07-30-database-rows-can-now-be-removed-without-deleting-their-page.md +6 -0
  34. package/corpus/templates/content/parity/eval-scenarios.ts +2 -2
  35. package/corpus/templates/content/parity/matrix.md +1 -1
  36. package/corpus/templates/content/parity/matrix.ts +4 -5
  37. package/corpus/templates/content/shared/api.ts +4 -0
  38. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +26 -11
  39. package/corpus/templates/design/actions/take-design-screenshot.ts +7 -0
  40. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +11 -3
  41. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +4 -1
  42. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +54 -17
  43. package/corpus/templates/design/app/components/design/design-canvas/local-runtime.ts +126 -0
  44. package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +2 -0
  45. package/corpus/templates/design/app/pages/Index.tsx +3 -1
  46. package/corpus/templates/design/app/pages/Present.tsx +2 -1
  47. package/corpus/templates/design/changelog/2026-07-30-a-broken-inline-script-in-a-generated-screen-is-now-caught-o.md +6 -0
  48. package/corpus/templates/design/changelog/2026-07-30-design-html-is-now-validated-with-a-spec-grade-html-parser-i.md +6 -0
  49. package/corpus/templates/design/changelog/2026-07-30-designs-now-load-their-tailwind-and-alpine-runtimes-from-the.md +6 -0
  50. package/corpus/templates/design/changelog/2026-07-30-designs-with-a-broken-alpine-expression-are-now-caught-on-sa.md +6 -0
  51. package/corpus/templates/design/changelog/2026-07-30-editing-a-design-no-longer-strips-its-styling-the-canvas-kep.md +6 -0
  52. package/corpus/templates/design/package.json +4 -0
  53. package/corpus/templates/design/shared/html-integrity.ts +802 -556
  54. package/dist/notifications/routes.d.ts +3 -3
  55. package/docs/content/template-content-developers.mdx +1 -1
  56. package/package.json +1 -1
  57. package/corpus/templates/content/actions/delete-database-items.ts +0 -89
@@ -560,17 +560,28 @@ export const editorChromeBridgeScript: string = `"use strict";
560
560
  }
561
561
  renderRuntimeInteractionStatePreviews();
562
562
  }
563
- function runtimeHeadHtmlWithoutEditorChrome() {
564
- if (!document.head) return "";
565
- var clone = document.head.cloneNode(true);
566
- Array.prototype.slice.call(
567
- clone.querySelectorAll(
568
- "[data-agent-native-editor-chrome-style], [data-agent-native-editing-safety-style]"
569
- )
570
- ).forEach(function(node) {
563
+ var lastSourceHeadHtml = null;
564
+ function replaceSourceHeadNodes(previousSourceHtml, nextSourceHtml) {
565
+ if (!document.head) return;
566
+ var stale = document.createElement("head");
567
+ stale.innerHTML = previousSourceHtml || "";
568
+ var staleCounts = {};
569
+ Array.prototype.forEach.call(stale.children, function(node) {
570
+ var key = node.outerHTML;
571
+ staleCounts[key] = (staleCounts[key] || 0) + 1;
572
+ });
573
+ Array.prototype.slice.call(document.head.children).forEach(function(node) {
574
+ var key = node.outerHTML;
575
+ if (!staleCounts[key]) return;
576
+ staleCounts[key] -= 1;
571
577
  if (node.parentNode) node.parentNode.removeChild(node);
572
578
  });
573
- return clone.innerHTML;
579
+ var next = document.createElement("head");
580
+ next.innerHTML = nextSourceHtml || "";
581
+ var anchor = document.head.firstChild;
582
+ Array.prototype.slice.call(next.children).forEach(function(node) {
583
+ document.head.insertBefore(document.importNode(node, true), anchor);
584
+ });
574
585
  }
575
586
  function chromeScaleX() {
576
587
  return 1 / Math.max(0.05, editorChromeScaleX);
@@ -2536,7 +2547,10 @@ export const editorChromeBridgeScript: string = `"use strict";
2536
2547
  }
2537
2548
  var nextHeadHtml = nextDoc.head ? nextDoc.head.innerHTML : "";
2538
2549
  ensureEditorChromeStyle();
2539
- var currentHeadHtml = runtimeHeadHtmlWithoutEditorChrome();
2550
+ if (lastSourceHeadHtml === null) {
2551
+ lastSourceHeadHtml = nextHeadHtml;
2552
+ }
2553
+ var currentHeadHtml = lastSourceHeadHtml;
2540
2554
  if (nextHeadHtml === currentHeadHtml && activeCandidates.length > 0) {
2541
2555
  var currentMatch = null;
2542
2556
  var nextMatch = null;
@@ -2598,8 +2612,9 @@ export const editorChromeBridgeScript: string = `"use strict";
2598
2612
  }
2599
2613
  }
2600
2614
  if (currentHeadHtml !== nextHeadHtml) {
2601
- document.head.innerHTML = nextHeadHtml;
2615
+ replaceSourceHeadNodes(currentHeadHtml, nextHeadHtml);
2602
2616
  ensureEditorChromeStyle();
2617
+ lastSourceHeadHtml = nextHeadHtml;
2603
2618
  }
2604
2619
  Array.prototype.slice.call(document.body.attributes).forEach(function(attribute) {
2605
2620
  document.body.removeAttribute(attribute.name);
@@ -588,8 +588,15 @@ export default defineAction({
588
588
  const consoleErrors: string[] = [];
589
589
  const fontLoadFailures: string[] = [];
590
590
  page.on("console", (msg) => {
591
+ // Alpine reports failed expressions at warn level, and that warning
592
+ // is the only place the expression and element appear — its paired
593
+ // `pageerror` is a bare "Invalid or unexpected token".
591
594
  if (msg.type() === "error") {
592
595
  consoleErrors.push(msg.text().slice(0, 300));
596
+ return;
597
+ }
598
+ if (msg.type() === "warning" && /Alpine/.test(msg.text())) {
599
+ consoleErrors.push(msg.text().slice(0, 300));
593
600
  }
594
601
  });
595
602
  page.on("pageerror", (err) => {
@@ -106,6 +106,7 @@ import {
106
106
  forwardEmbeddedCanvasPanMessage,
107
107
  type EmbeddedCanvasPanSession,
108
108
  } from "./design-canvas/iframe-pan";
109
+ import { withLocalRuntimes } from "./design-canvas/local-runtime";
109
110
  import type { MotionTrackWire } from "./design-canvas/motion-types";
110
111
  import {
111
112
  PENDING_TEXT_EDIT_TIMEOUT_MS,
@@ -1018,7 +1019,14 @@ function runtimeDocumentNeedsReload(
1018
1019
  Array.from(html.matchAll(SCRIPT_ELEMENT_RE), (match) => match[0]).join(
1019
1020
  "\n",
1020
1021
  );
1021
- return scriptSignature(previousContent) !== scriptSignature(nextContent);
1022
+ // A changed <head> is replaced wholesale, which drops whatever the page's own
1023
+ // runtime injected there and cannot re-run the scripts that produced it.
1024
+ const headSignature = (html: string) =>
1025
+ /<head\b[^>]*>([\s\S]*?)<\/head\s*>/i.exec(html)?.[1] ?? "";
1026
+ return (
1027
+ scriptSignature(previousContent) !== scriptSignature(nextContent) ||
1028
+ headSignature(previousContent) !== headSignature(nextContent)
1029
+ );
1022
1030
  }
1023
1031
 
1024
1032
  /**
@@ -2261,7 +2269,7 @@ export function DesignCanvas({
2261
2269
  editorChromeBridge +
2262
2270
  imageDiagBridge;
2263
2271
  const frameContent = getEmbeddedFrameDocumentContent({
2264
- content: iframeRenderContent,
2272
+ content: withLocalRuntimes(iframeRenderContent),
2265
2273
  embeddedFrameBackground,
2266
2274
  transparentBackground,
2267
2275
  contentOffsetX: embeddedFrame?.contentOffsetX ?? 0,
@@ -2282,7 +2290,7 @@ export function DesignCanvas({
2282
2290
  embeddedFrame?.contentOffsetY ?? 0,
2283
2291
  ),
2284
2292
  ].join("");
2285
- frameDocument = `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">${frameStyle}</head><body>${iframeRenderContent}${bridgeToInject}</body></html>`;
2293
+ frameDocument = `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">${frameStyle}</head><body>${withLocalRuntimes(iframeRenderContent)}${bridgeToInject}</body></html>`;
2286
2294
  }
2287
2295
  // Overview frames report their own content height so the canvas can
2288
2296
  // content-fit them (Framer-style). Embedded (overview) frames only — a
@@ -88,6 +88,7 @@ import {
88
88
  type ContentSizeSample,
89
89
  } from "./design-canvas/content-size-report";
90
90
  import { appendHitTestResponder } from "./design-canvas/hit-test";
91
+ import { withLocalRuntimes } from "./design-canvas/local-runtime";
91
92
  import { DesignCanvas } from "./DesignCanvas";
92
93
  import { dndHostLog } from "./dnd-debug";
93
94
  import {
@@ -9499,7 +9500,9 @@ const Screen = memo(function Screen({
9499
9500
  // editable DesignCanvas is supplied) still report their own height and
9500
9501
  // content-fit. The editable DesignCanvas path injects its own reporter.
9501
9502
  return injectSessionReplayIframeBootstrap(
9502
- appendContentSizeReporter(appendHitTestResponder(screen.content)),
9503
+ appendContentSizeReporter(
9504
+ appendHitTestResponder(withLocalRuntimes(screen.content)),
9505
+ ),
9503
9506
  );
9504
9507
  // eslint-disable-next-line react-hooks/exhaustive-deps
9505
9508
  }, [screen.content]);
@@ -315,19 +315,49 @@ declare var __SELECTED_LAYER_DRAG_PRIORITY__: boolean;
315
315
  renderRuntimeInteractionStatePreviews();
316
316
  }
317
317
 
318
- function runtimeHeadHtmlWithoutEditorChrome(): string {
319
- if (!document.head) return "";
320
- var clone = document.head.cloneNode(true) as HTMLElement;
321
- Array.prototype.slice
322
- .call(
323
- clone.querySelectorAll(
324
- "[data-agent-native-editor-chrome-style], [data-agent-native-editing-safety-style]",
325
- ),
326
- )
327
- .forEach(function (node) {
328
- if (node.parentNode) node.parentNode.removeChild(node);
329
- });
330
- return clone.innerHTML;
318
+ /**
319
+ * The last source <head> this bridge applied. Compared against instead of the
320
+ * live head, which also carries nodes the page's own runtime injected —
321
+ * @tailwindcss/browser's compiled sheet above all. Against the live head the
322
+ * comparison always differs, the head gets wiped, the compiled CSS goes with
323
+ * it, and the re-inserted `<script src>` cannot rebuild it because innerHTML
324
+ * never executes scripts. The screen then renders unstyled for good.
325
+ */
326
+ var lastSourceHeadHtml: string | null = null;
327
+
328
+ /**
329
+ * Swaps only the nodes the previous source head contributed. Assigning
330
+ * `document.head.innerHTML` instead destroys whatever the page's own runtime
331
+ * injected — @tailwindcss/browser's compiled sheet above all — and the
332
+ * `<script src>` re-inserted in its place can never rebuild it, because
333
+ * innerHTML does not execute scripts.
334
+ */
335
+ function replaceSourceHeadNodes(
336
+ previousSourceHtml: string | null,
337
+ nextSourceHtml: string,
338
+ ): void {
339
+ if (!document.head) return;
340
+ var stale = document.createElement("head");
341
+ stale.innerHTML = previousSourceHtml || "";
342
+ var staleCounts: Record<string, number> = {};
343
+ Array.prototype.forEach.call(stale.children, function (node: Element) {
344
+ var key = node.outerHTML;
345
+ staleCounts[key] = (staleCounts[key] || 0) + 1;
346
+ });
347
+ Array.prototype.slice.call(document.head.children).forEach(function (
348
+ node: Element,
349
+ ) {
350
+ var key = node.outerHTML;
351
+ if (!staleCounts[key]) return;
352
+ staleCounts[key] -= 1;
353
+ if (node.parentNode) node.parentNode.removeChild(node);
354
+ });
355
+ var next = document.createElement("head");
356
+ next.innerHTML = nextSourceHtml || "";
357
+ var anchor = document.head.firstChild;
358
+ Array.prototype.slice.call(next.children).forEach(function (node: Element) {
359
+ document.head.insertBefore(document.importNode(node, true), anchor);
360
+ });
331
361
  }
332
362
 
333
363
  function chromeScaleX(): number {
@@ -3123,8 +3153,8 @@ declare var __SELECTED_LAYER_DRAG_PRIORITY__: boolean;
3123
3153
  // Both layer states are painted here, from the one `layer-states` message,
3124
3154
  // so every call site that re-runs after a runtime document swap restores
3125
3155
  // hide AND lock together. Locked paints an attribute only; the hairline
3126
- // treatment lives in the editor chrome stylesheet so it is stripped from
3127
- // runtimeHeadHtmlWithoutEditorChrome and never reaches source or export.
3156
+ // treatment lives in the editor chrome stylesheet, which is never part of
3157
+ // the source head and so never reaches source or export.
3128
3158
  function applyLayerStateSelectors(): void {
3129
3159
  document
3130
3160
  .querySelectorAll("[data-agent-native-runtime-hidden]")
@@ -3248,7 +3278,13 @@ declare var __SELECTED_LAYER_DRAG_PRIORITY__: boolean;
3248
3278
 
3249
3279
  var nextHeadHtml = nextDoc.head ? nextDoc.head.innerHTML : "";
3250
3280
  ensureEditorChromeStyle();
3251
- var currentHeadHtml = runtimeHeadHtmlWithoutEditorChrome();
3281
+ if (lastSourceHeadHtml === null) {
3282
+ // First patch after a srcdoc build, so the document already carries this
3283
+ // source head. Forced replacements adopt too: treating the live head as
3284
+ // the baseline is what wipes the runtime's own nodes.
3285
+ lastSourceHeadHtml = nextHeadHtml;
3286
+ }
3287
+ var currentHeadHtml = lastSourceHeadHtml;
3252
3288
  if (nextHeadHtml === currentHeadHtml && activeCandidates.length > 0) {
3253
3289
  var currentMatch = null;
3254
3290
  var nextMatch = null;
@@ -3326,8 +3362,9 @@ declare var __SELECTED_LAYER_DRAG_PRIORITY__: boolean;
3326
3362
  }
3327
3363
  }
3328
3364
  if (currentHeadHtml !== nextHeadHtml) {
3329
- document.head.innerHTML = nextHeadHtml;
3365
+ replaceSourceHeadNodes(currentHeadHtml, nextHeadHtml);
3330
3366
  ensureEditorChromeStyle();
3367
+ lastSourceHeadHtml = nextHeadHtml;
3331
3368
  }
3332
3369
  Array.prototype.slice.call(document.body.attributes).forEach(function (
3333
3370
  attribute: Attr,
@@ -0,0 +1,126 @@
1
+ /**
2
+ * A blocked CDN request leaves a screen rendering as unstyled default HTML with
3
+ * nothing in its own markup to explain it. Rewriting at render time rather than
4
+ * at generation time also repairs every design already stored.
5
+ *
6
+ * Export keeps the CDN on purpose: a downloaded file runs outside this app.
7
+ */
8
+
9
+ import tailwindRuntimeUrl from "@tailwindcss/browser?url";
10
+ import alpineRuntimeUrl from "alpinejs/dist/cdn.min.js?url";
11
+ import { parse } from "parse5";
12
+
13
+ /**
14
+ * Only the major version this app vendors may be substituted. Swapping v3's Play
15
+ * CDN for the v4 runtime looks equivalent and is not: v4 resolves spacing and
16
+ * radius through theme variables a v3 document never defines, so `px-8` computes
17
+ * to 0 and `rounded-full` to garbage.
18
+ */
19
+ function substitutable(src: string, pinned: RegExp, vendored: RegExp): boolean {
20
+ const version = pinned.exec(src);
21
+ return !version || vendored.test(version[1] ?? "");
22
+ }
23
+
24
+ function tailwindReplacement(src: string): boolean {
25
+ if (!/tailwindcss\/browser/i.test(src)) return false;
26
+ return substitutable(src, /@tailwindcss\/browser@(\d+)/i, /^4$/);
27
+ }
28
+
29
+ /**
30
+ * Core Alpine only. `@alpinejs/persist`, `/focus`, `/mask` and friends all carry
31
+ * "alpinejs" in their URL, and swapping one for the core bundle drops the plugin
32
+ * entirely while loading Alpine a second time.
33
+ */
34
+ function alpineReplacement(src: string): boolean {
35
+ if (/@alpinejs\//i.test(src)) return false;
36
+ if (!/(^|[/@])alpinejs(@|\/|$)/i.test(src)) return false;
37
+ return substitutable(src, /alpinejs@(\d+)/i, /^3$/);
38
+ }
39
+
40
+ /**
41
+ * Absolute, because a srcdoc document resolves relative URLs against the parent
42
+ * document rather than against the app root it was composed for.
43
+ */
44
+ function absolute(url: string): string {
45
+ if (/^[a-z]+:\/\//i.test(url)) return url;
46
+ if (typeof window === "undefined") return url;
47
+ return new URL(url, window.location.origin).href;
48
+ }
49
+
50
+ export function localRuntimeUrls(): { tailwind: string; alpine: string } {
51
+ return {
52
+ tailwind: absolute(tailwindRuntimeUrl),
53
+ alpine: absolute(alpineRuntimeUrl),
54
+ };
55
+ }
56
+
57
+ interface SrcSpan {
58
+ start: number;
59
+ end: number;
60
+ replacement: string;
61
+ }
62
+
63
+ /**
64
+ * Spans come from the parser, never from a source-wide regex: a runtime URL
65
+ * written inside a `<script>` body, an HTML comment, or another element's
66
+ * attribute is user content, and rewriting it makes the preview diverge from
67
+ * the stored and exported document.
68
+ */
69
+ function runtimeSrcSpans(
70
+ html: string,
71
+ urls: { tailwind: string; alpine: string },
72
+ ): SrcSpan[] {
73
+ const spans: SrcSpan[] = [];
74
+
75
+ const visit = (node: unknown) => {
76
+ const element = node as {
77
+ tagName?: string;
78
+ attrs?: Array<{ name: string; value: string }>;
79
+ childNodes?: unknown[];
80
+ content?: { childNodes?: unknown[] };
81
+ sourceCodeLocation?: {
82
+ attrs?: Record<string, { startOffset: number; endOffset: number }>;
83
+ } | null;
84
+ };
85
+ if (element.tagName === "script") {
86
+ const src = element.attrs?.find((attr) => attr.name === "src")?.value;
87
+ const at = element.sourceCodeLocation?.attrs?.src;
88
+ if (src && at) {
89
+ const replacement = tailwindReplacement(src)
90
+ ? urls.tailwind
91
+ : alpineReplacement(src)
92
+ ? urls.alpine
93
+ : null;
94
+ if (replacement) {
95
+ spans.push({ start: at.startOffset, end: at.endOffset, replacement });
96
+ }
97
+ }
98
+ }
99
+ for (const child of [
100
+ ...(element.childNodes ?? []),
101
+ ...(element.content?.childNodes ?? []),
102
+ ]) {
103
+ visit(child);
104
+ }
105
+ };
106
+ visit(parse(html, { sourceCodeLocationInfo: true }));
107
+
108
+ return spans.sort((left, right) => left.start - right.start);
109
+ }
110
+
111
+ export function withLocalRuntimes(
112
+ html: string,
113
+ urls: { tailwind: string; alpine: string } = localRuntimeUrls(),
114
+ ): string {
115
+ if (!html || !/<script/i.test(html)) return html;
116
+ const spans = runtimeSrcSpans(html, urls);
117
+ if (spans.length === 0) return html;
118
+
119
+ let out = "";
120
+ let cursor = 0;
121
+ for (const span of spans) {
122
+ out += `${html.slice(cursor, span.start)}src="${span.replacement}"`;
123
+ cursor = span.end;
124
+ }
125
+ return out + html.slice(cursor);
126
+ }
@@ -63,6 +63,8 @@ export function TemplatePreview({
63
63
  className,
64
64
  )}
65
65
  >
66
+ {/* The empty sandbox blocks scripts, so the CDN runtimes stay inert here;
67
+ a styled preview needs precompiled CSS, not a runtime swap. */}
66
68
  <iframe
67
69
  title={`${title} preview`}
68
70
  srcDoc={html}
@@ -71,6 +71,8 @@ import {
71
71
  writePendingGeneration,
72
72
  } from "@/lib/pending-generation";
73
73
 
74
+ import { withLocalRuntimes } from "../components/design/design-canvas/local-runtime";
75
+
74
76
  type ProjectType = "prototype" | "other";
75
77
  interface Design {
76
78
  id: string;
@@ -1176,7 +1178,7 @@ function DesignThumbnail({ html }: { html: string | null }) {
1176
1178
  >
1177
1179
  <iframe
1178
1180
  {...{ [SESSION_REPLAY_IFRAME_ATTRIBUTE]: "" }}
1179
- srcDoc={injectSessionReplayIframeBootstrap(html)}
1181
+ srcDoc={injectSessionReplayIframeBootstrap(withLocalRuntimes(html))}
1180
1182
  sandbox="allow-scripts"
1181
1183
  loading="lazy"
1182
1184
  tabIndex={-1}
@@ -28,6 +28,7 @@ import {
28
28
  import { Skeleton } from "@/components/ui/skeleton";
29
29
  import { ReviewCanvasPins } from "@/components/visual-editor/ReviewCanvasPins";
30
30
 
31
+ import { withLocalRuntimes } from "../components/design/design-canvas/local-runtime";
31
32
  import {
32
33
  resolvePresentEscapeAction,
33
34
  shouldBlockPresentPageNavigation,
@@ -181,7 +182,7 @@ export default function Present() {
181
182
  <div className="present-review-canvas h-full w-full">
182
183
  <iframe
183
184
  {...{ [SESSION_REPLAY_IFRAME_ATTRIBUTE]: "" }}
184
- srcDoc={reviewableContent}
185
+ srcDoc={withLocalRuntimes(reviewableContent)}
185
186
  sandbox="allow-scripts"
186
187
  data-design-preview-iframe
187
188
  className="h-full w-full border-0"
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-30
4
+ ---
5
+
6
+ A broken inline script in a generated screen is now caught on save instead of silently never running.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-30
4
+ ---
5
+
6
+ Design HTML is now validated with a spec-grade HTML parser instead of hand-rolled scanning, so markup inside attributes and scripts is no longer mistaken for real tags.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-30
4
+ ---
5
+
6
+ Designs now load their Tailwind and Alpine runtimes from the app itself instead of a public CDN, so an ad blocker, a restricted network, or being offline cannot leave every screen unstyled.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-30
4
+ ---
5
+
6
+ Designs with a broken Alpine expression are now caught on save instead of rendering with every interaction silently dead.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-30
4
+ ---
5
+
6
+ Editing a design no longer strips its styling: the canvas now keeps the page's compiled CSS instead of discarding it on every update.
@@ -25,6 +25,9 @@
25
25
  "@libsql/client": "^0.15.8",
26
26
  "@paper-design/shaders-react": "0.0.76",
27
27
  "@tabler/icons-react": "catalog:",
28
+ "@tailwindcss/browser": "^4.3.3",
29
+ "acorn": "^8.18.0",
30
+ "alpinejs": "^3.15.12",
28
31
  "drizzle-orm": "^0.45.2",
29
32
  "entities": "8.0.0",
30
33
  "fzstd": "^0.1.1",
@@ -37,6 +40,7 @@
37
40
  "monaco-editor": "^0.55.1",
38
41
  "nanoid": "^5.1.9",
39
42
  "pako": "^3.0.0",
43
+ "parse5": "^8.0.1",
40
44
  "pdf-parse": "^2.4.5",
41
45
  "prettier": "^3.9.4",
42
46
  "yjs": "catalog:",