@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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +6 -0
- package/corpus/core/docs/content/template-content-developers.mdx +1 -1
- package/corpus/core/package.json +1 -1
- package/corpus/templates/clips/changelog/2026-07-29-meeting-notes-no-longer-transcribe-the-other-side-twice-when.md +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +37 -48
- package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +7 -5
- package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +182 -106
- package/corpus/templates/clips/desktop/src/overlays/live-transcript.tsx +43 -14
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +3 -3
- package/corpus/templates/clips/desktop/src-tauri/src/echo_guard.rs +443 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +125 -16
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -4
- package/corpus/templates/content/actions/_database-membership-lock.ts +25 -0
- package/corpus/templates/content/actions/_database-row-batch.ts +6 -2
- package/corpus/templates/content/actions/_database-source-utils.ts +289 -129
- package/corpus/templates/content/actions/_database-utils.ts +12 -3
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +39 -21
- package/corpus/templates/content/actions/attach-content-database-source.ts +4 -1
- package/corpus/templates/content/actions/change-content-database-source-role.ts +3 -5
- package/corpus/templates/content/actions/duplicate-document-property.ts +59 -46
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +77 -71
- package/corpus/templates/content/actions/materialize-builder-required-fields.ts +7 -0
- package/corpus/templates/content/actions/remove-database-items.ts +183 -0
- package/corpus/templates/content/actions/set-document-property.ts +57 -39
- package/corpus/templates/content/actions/sync-local-folder-source.ts +7 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +471 -329
- package/corpus/templates/content/app/components/editor/database/row-access.ts +45 -0
- package/corpus/templates/content/app/components/editor/database/shared.tsx +141 -38
- package/corpus/templates/content/app/hooks/use-content-database.ts +2 -2
- package/corpus/templates/content/app/i18n-data.ts +130 -0
- package/corpus/templates/content/changelog/2026-07-30-database-rows-can-now-be-removed-without-deleting-their-page.md +6 -0
- package/corpus/templates/content/parity/eval-scenarios.ts +2 -2
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +4 -5
- package/corpus/templates/content/shared/api.ts +4 -0
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +26 -11
- package/corpus/templates/design/actions/take-design-screenshot.ts +7 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +11 -3
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +4 -1
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +54 -17
- package/corpus/templates/design/app/components/design/design-canvas/local-runtime.ts +126 -0
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +2 -0
- package/corpus/templates/design/app/pages/Index.tsx +3 -1
- package/corpus/templates/design/app/pages/Present.tsx +2 -1
- package/corpus/templates/design/changelog/2026-07-30-a-broken-inline-script-in-a-generated-screen-is-now-caught-o.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-design-html-is-now-validated-with-a-spec-grade-html-parser-i.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-designs-now-load-their-tailwind-and-alpine-runtimes-from-the.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-designs-with-a-broken-alpine-expression-are-now-caught-on-sa.md +6 -0
- package/corpus/templates/design/changelog/2026-07-30-editing-a-design-no-longer-strips-its-styling-the-canvas-kep.md +6 -0
- package/corpus/templates/design/package.json +4 -0
- package/corpus/templates/design/shared/html-integrity.ts +802 -556
- package/dist/notifications/routes.d.ts +3 -3
- package/docs/content/template-content-developers.mdx +1 -1
- package/package.json +1 -1
- 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
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
|
3127
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
@@ -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:",
|