@replohq/sandbox-runtime 1.6.6 → 1.6.8
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/NextErrorReporter.js +57 -17
- package/NextErrorReporter.js.map +2 -2
- package/_vendor/replo-utils/react/use-viewport-snapshot-responder.mjs +1 -1
- package/_vendor/schemas/errors.d.ts +2 -0
- package/_vendor/schemas/generated/errors.d.ts +3 -0
- package/_vendor/schemas/iframeRpcChannel.mjs +5 -0
- package/buildMetadata.js +3 -3
- package/design-library-preview/DesignLibraryPreview.js +2 -0
- package/design-library-preview/DesignLibraryPreview.js.map +2 -2
- package/package.json +4 -4
package/NextErrorReporter.js
CHANGED
|
@@ -1,28 +1,68 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
isAllowedEditorMessage,
|
|
5
|
+
isAllowedEditorOrigin
|
|
6
|
+
} from "./annotations/bridgeGate";
|
|
7
|
+
const ERRORED_BADGE_SELECTOR = '[data-next-badge][data-error="true"]';
|
|
8
|
+
const COLLECTED_ISSUE_SELECTOR = "[data-nextjs-dialog]";
|
|
9
|
+
const CLOSED_DIALOG_OVERLAY_SELECTOR = '[data-nextjs-dialog-overlay][data-rendered="false"]';
|
|
10
|
+
const RED_CHROME_SELECTOR = `${ERRORED_BADGE_SELECTOR}, ${COLLECTED_ISSUE_SELECTOR}`;
|
|
5
11
|
const STATUS_LATCH_MS = 500;
|
|
12
|
+
function findInitialEditorOrigin() {
|
|
13
|
+
if (window.self === window.top) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const ancestorOrigin = "ancestorOrigins" in window.location ? window.location.ancestorOrigins.item(0) : null;
|
|
17
|
+
return [ancestorOrigin, getOriginOrNull(document.referrer)].find((origin) => {
|
|
18
|
+
return origin !== null && isAllowedEditorOrigin(origin);
|
|
19
|
+
}) ?? null;
|
|
20
|
+
}
|
|
21
|
+
function getOriginOrNull(url) {
|
|
22
|
+
try {
|
|
23
|
+
return new URL(url).origin;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
6
28
|
function NextErrorReporter() {
|
|
7
29
|
React.useEffect(() => {
|
|
30
|
+
let editorOrigin = findInitialEditorOrigin();
|
|
8
31
|
let lastStatus = null;
|
|
9
32
|
let shadowObserver = null;
|
|
10
33
|
let observedRoot = null;
|
|
11
34
|
let pendingPost = null;
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
35
|
+
const deliver = () => {
|
|
36
|
+
if (editorOrigin === null || lastStatus === null) {
|
|
14
37
|
return;
|
|
15
38
|
}
|
|
16
|
-
lastStatus = status;
|
|
17
39
|
window.parent.postMessage(
|
|
18
40
|
{
|
|
19
41
|
type: "replo:preview:status",
|
|
20
|
-
|
|
21
|
-
|
|
42
|
+
route: window.location.pathname,
|
|
43
|
+
status: lastStatus
|
|
22
44
|
},
|
|
23
|
-
|
|
45
|
+
editorOrigin
|
|
24
46
|
);
|
|
25
47
|
};
|
|
48
|
+
const postNow = (status) => {
|
|
49
|
+
if (lastStatus === status) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
lastStatus = status;
|
|
53
|
+
deliver();
|
|
54
|
+
};
|
|
55
|
+
const handleParentMessage = (event) => {
|
|
56
|
+
if (event.source !== window.parent || !isAllowedEditorMessage(event)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (event.origin === editorOrigin) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
editorOrigin = event.origin;
|
|
63
|
+
deliver();
|
|
64
|
+
};
|
|
65
|
+
window.addEventListener("message", handleParentMessage);
|
|
26
66
|
const post = (status) => {
|
|
27
67
|
if (pendingPost) {
|
|
28
68
|
if (pendingPost.status === status) {
|
|
@@ -31,11 +71,11 @@ function NextErrorReporter() {
|
|
|
31
71
|
window.clearTimeout(pendingPost.timeoutId);
|
|
32
72
|
pendingPost = null;
|
|
33
73
|
}
|
|
34
|
-
if (
|
|
74
|
+
if (lastStatus === status) {
|
|
35
75
|
return;
|
|
36
76
|
}
|
|
37
77
|
if (lastStatus === null && status === "ok") {
|
|
38
|
-
postNow(
|
|
78
|
+
postNow(status);
|
|
39
79
|
return;
|
|
40
80
|
}
|
|
41
81
|
const timeoutId = window.setTimeout(() => {
|
|
@@ -57,18 +97,17 @@ function NextErrorReporter() {
|
|
|
57
97
|
shadowObserver.observe(root, {
|
|
58
98
|
childList: true,
|
|
59
99
|
subtree: true,
|
|
100
|
+
characterData: true,
|
|
60
101
|
attributes: true,
|
|
61
|
-
attributeFilter: ["data-error"]
|
|
102
|
+
attributeFilter: ["data-error", "data-rendered"]
|
|
62
103
|
});
|
|
63
104
|
observedRoot = root;
|
|
64
105
|
}
|
|
65
|
-
portal.style.display =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
portal.shadowRoot?.querySelector(BLOCKING_ERROR_SELECTOR)
|
|
106
|
+
portal.style.display = root?.querySelector(RED_CHROME_SELECTOR) ? "none" : "";
|
|
107
|
+
const dialog = root?.querySelector(COLLECTED_ISSUE_SELECTOR) ?? null;
|
|
108
|
+
post(
|
|
109
|
+
dialog && !dialog.closest(CLOSED_DIALOG_OVERLAY_SELECTOR) ? "error" : "ok"
|
|
70
110
|
);
|
|
71
|
-
post(hasBlockingError ? "error" : "ok");
|
|
72
111
|
};
|
|
73
112
|
const lightObserver = new MutationObserver(syncOverlayState);
|
|
74
113
|
lightObserver.observe(document.documentElement, {
|
|
@@ -77,6 +116,7 @@ function NextErrorReporter() {
|
|
|
77
116
|
});
|
|
78
117
|
syncOverlayState();
|
|
79
118
|
return () => {
|
|
119
|
+
window.removeEventListener("message", handleParentMessage);
|
|
80
120
|
lightObserver.disconnect();
|
|
81
121
|
shadowObserver?.disconnect();
|
|
82
122
|
if (pendingPost) {
|
package/NextErrorReporter.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../NextErrorReporter.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * NOTE (Max, 2026-06-10): This component sends post messages to its embedder (website-builder miniapp)\n * so that it can render a loading / error UI if there's an error on this page.\n *\n * Ideally, we wouldn't need any update to the sandbox runtime to achieve this: the miniapp would just read the\n * iframe's content and detect whenever there's a nextjs error overlay.\n * But the preview runs cross-origin from the editor, so the editor can't read the iframe's\n * DOM (browser security).\n *\n * This component has observers that detect when the nextjs error overlay appears or\n * disappears, and sends a post message with a status:\n * - `status`: \"error\" while Next's dev overlay opens its blocking error dialog\n * (a real page break \u2014 see the selectors below for why the badge's issue\n * count is deliberately NOT used: it also counts recovered loader misses)\n * - `status`: \"ok\" otherwise. Cleared by Fast Refresh on a fix, so it's also the\n * recovery signal (agent fixed the error, so it's the error -> ok transition)\n *\n * It also hides Next's dev error overlay (`<nextjs-portal>`) at the source the\n * instant an error appears, so the red bubble never paints \u2014 the MutationObserver\n * callback runs as a microtask before paint. `data-error` stays queryable while\n * hidden, so detection is unaffected;\n *\n * The overlay lives in `<nextjs-portal>`'s shadow root, which Next can recreate,\n * so we re-attach the shadow observer whenever it hands us a new root. No polling.\n *\n * This must be mounted OUTSIDE the error boundary (see ReploProvider) so it keeps\n * reporting while the boundary is caught (and showing its neutral cover).\n *\n * I'm unsatisfied with this approach: observing for the nextjs-portal is brittle and ugly.\n * Ideally, the ClientErrorBoundary, which hooks into the render cycle, in componentDidCatch, would\n * just report a post message to the miniapp. I prototyped that, the error detection works\n * great (ok -> error transition).\n * But the error -> ok transition doesn't work for Server components (it only does for Client\n * components). Here's a slack thread with me <> Gabe on it:\n * https://replohq.slack.com/archives/C03AACVP08Y/p1781154057832689?thread_ts=1781029224.564489&cid=C03AACVP08Y.\n */\n// NOTE (Max, 2026-08-04): <nextjs-portal> surfaces two very different things,\n// and only one of them means the page is actually broken:\n// - the dev-tools badge (`[data-next-badge][data-error=\"true\"]`) lights for\n// EVERY collected issue, including a server-side loader error that React\n// already recovered from by streaming the LoaderErrorBoundary fallback. That\n// is a graceful, recoverable miss (product unpublished, deleted collection,\n// \u2026) \u2014 the page renders fine \u2014 and it must NOT be reported as a page error.\n// - the error overlay dialog (`[data-nextjs-dialog]`) only mounts when Next\n// auto-opens the overlay for a genuinely blocking error: the app shell's\n// error boundary catching an uncaught render crash, an unrecoverable SSR\n// error (missing root tags), or a build error. Next never auto-opens the\n// dialog for a recovered error. Verified against next@16.1.7 (the version\n// the platform SDK packages and most templates pin) and next@15.3.8: the sole auto-open\n// is AppDevOverlayErrorBoundary's componentDidCatch -> openErrorOverlay,\n// i.e. the shell boundary, which a nested LoaderErrorBoundary never reaches\n// (a caught loader miss only increments the badge count). The badge count\n// opens the dialog only on an explicit user click.\n// So the open dialog \u2014 not the badge's issue count \u2014 is the fatal-vs-recoverable\n// discriminator. Keying off the badge reported recoverable loader misses as\n// full-page errors, blanking a preview that had actually rendered its fallback.\n//\n// We still *hide* the whole portal whenever Next paints red chrome (the errored\n// badge OR the dialog) so no dev overlay bleeds into the preview \u2014 a recovered\n// loader miss shouldn't flash a red dot either. Only the blocking dialog is\n// reported upward as a page error.\nconst RED_CHROME_SELECTOR =\n '[data-next-badge][data-error=\"true\"], [data-nextjs-dialog]';\nconst BLOCKING_ERROR_SELECTOR = \"[data-nextjs-dialog]\";\n\n// HMR portal churn flips the overlay for milliseconds in both directions; only a state that persists this long is real.\nconst STATUS_LATCH_MS = 500;\n\ntype PreviewStatus = \"ok\" | \"error\";\n\nexport function NextErrorReporter() {\n // eslint-disable-next-line replo/no-use-effect -- Observes Next's dev error overlay (a shadow-DOM node outside React's tree) and relays its state to the cross-origin embedder. No React alternative applies.\n React.useEffect(() => {\n let lastStatus: PreviewStatus | null = null;\n let shadowObserver: MutationObserver | null = null;\n let observedRoot: ShadowRoot | null = null;\n let pendingPost: { status: PreviewStatus; timeoutId: number } | null = null;\n\n const postNow = (status: PreviewStatus) => {\n if (status === lastStatus) {\n return;\n }\n lastStatus = status;\n window.parent.postMessage(\n {\n type: \"replo:preview:status\",\n status,\n route: window.location.pathname,\n },\n \"*\",\n );\n };\n\n // Latch every transition (the opposite observation cancels a pending one) \u2014 a false error <-> ok flip makes the editor reload the preview. Only the initial \"ok\" posts immediately, as the readiness signal.\n const post = (status: PreviewStatus) => {\n if (pendingPost) {\n if (pendingPost.status === status) {\n return;\n }\n window.clearTimeout(pendingPost.timeoutId);\n pendingPost = null;\n }\n if (status === lastStatus) {\n return;\n }\n if (lastStatus === null && status === \"ok\") {\n postNow(\"ok\");\n return;\n }\n const timeoutId = window.setTimeout(() => {\n pendingPost = null;\n postNow(status);\n }, STATUS_LATCH_MS);\n pendingPost = { status, timeoutId };\n };\n\n // NOTE (Max, 2026-06-10): Reads the dev overlay's current error state and\n // propagates it \u2014 re-attach the shadow observer to the live root, hide the\n // portal while erroring, and post ok/error up to the editor. Runs on init and\n // on every observed DOM mutation.\n const syncOverlayState = () => {\n const portal = document.getElementsByTagName(\"nextjs-portal\")[0];\n if (!(portal instanceof HTMLElement)) {\n post(\"ok\");\n return;\n }\n // NOTE (Max, 2026-06-10): Re-attach whenever Next hands us a fresh shadow\n // root, else we'd keep watching a detached one after the overlay is\n // recreated.\n const root = portal.shadowRoot;\n if (root && root !== observedRoot) {\n shadowObserver?.disconnect();\n shadowObserver = new MutationObserver(syncOverlayState);\n shadowObserver.observe(root, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"data-error\"],\n });\n observedRoot = root;\n }\n // NOTE (Max, 2026-06-10): Hide the dev overlay at the source so the red\n // bubble never paints \u2014 for any red chrome, including a recovered miss's\n // errored badge.\n portal.style.display = portal.shadowRoot?.querySelector(\n RED_CHROME_SELECTOR,\n )\n ? \"none\"\n : \"\";\n // Only the blocking overlay dialog means the page actually broke; the\n // badge's issue count also covers recovered loader misses (see selectors).\n const hasBlockingError = Boolean(\n portal.shadowRoot?.querySelector(BLOCKING_ERROR_SELECTOR),\n );\n post(hasBlockingError ? \"error\" : \"ok\");\n };\n\n // NOTE (Max, 2026-06-10): Catches the portal being added/removed in the light\n // DOM; the re-attached shadow observer catches in-overlay changes.\n const lightObserver = new MutationObserver(syncOverlayState);\n lightObserver.observe(document.documentElement, {\n childList: true,\n subtree: true,\n });\n syncOverlayState();\n\n return () => {\n lightObserver.disconnect();\n shadowObserver?.disconnect();\n if (pendingPost) {\n window.clearTimeout(pendingPost.timeoutId);\n }\n };\n }, []);\n\n return null;\n}\n"],
|
|
5
|
-
"mappings": ";AAEA,YAAY,WAAW;
|
|
4
|
+
"sourcesContent": ["\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n isAllowedEditorMessage,\n isAllowedEditorOrigin,\n} from \"./annotations/bridgeGate\";\n\n/**\n * NOTE (Max, 2026-06-10): This component sends post messages to its embedder (website-builder miniapp)\n * so that it can render a loading / error UI if there's an error on this page.\n *\n * Ideally, we wouldn't need any update to the sandbox runtime to achieve this: the miniapp would just read the\n * iframe's content and detect whenever there's a nextjs error overlay.\n * But the preview runs cross-origin from the editor, so the editor can't read the iframe's\n * DOM (browser security).\n *\n * This component has observers that detect when the nextjs error overlay appears or\n * disappears, and sends a post message with a status:\n * - `status`: \"error\" while Next's dev overlay opens its blocking error dialog\n * (a real page break \u2014 see the selectors below for why the badge's issue\n * count is deliberately NOT used: it also counts recovered loader misses)\n * - `status`: \"ok\" otherwise. Cleared by Fast Refresh on a fix, so it's also the\n * recovery signal (agent fixed the error, so it's the error -> ok transition)\n *\n * It also hides Next's dev error overlay (`<nextjs-portal>`) at the source the\n * instant an error appears, so the red bubble never paints \u2014 the MutationObserver\n * callback runs as a microtask before paint. `data-error` stays queryable while\n * hidden, so detection is unaffected;\n *\n * The overlay lives in `<nextjs-portal>`'s shadow root, which Next can recreate,\n * so we re-attach the shadow observer whenever it hands us a new root. No polling.\n *\n * This must be mounted OUTSIDE the error boundary (see ReploProvider) so it keeps\n * reporting while the boundary is caught (and showing its neutral cover).\n *\n * I'm unsatisfied with this approach: observing for the nextjs-portal is brittle and ugly.\n * Ideally, the ClientErrorBoundary, which hooks into the render cycle, in componentDidCatch, would\n * just report a post message to the miniapp. I prototyped that, the error detection works\n * great (ok -> error transition).\n * But the error -> ok transition doesn't work for Server components (it only does for Client\n * components). Here's a slack thread with me <> Gabe on it:\n * https://replohq.slack.com/archives/C03AACVP08Y/p1781154057832689?thread_ts=1781029224.564489&cid=C03AACVP08Y.\n */\n// NOTE (Max, 2026-08-04; Nick, 2026-09-11): Two independent things have to be\n// read off <nextjs-portal>: *what* Next collected, and whether it considers that\n// blocking.\n// - `[data-nextjs-dialog]` holds the text of the collected issue and is\n// present for recoverable issues too, so it is the only description of the\n// problem available. It is NOT evidence the page broke.\n// - the surrounding `[data-nextjs-dialog-overlay]` says whether Next opened\n// the overlay, which it auto-does only for a genuinely blocking error: the\n// app shell's error boundary catching an uncaught render crash, an\n// unrecoverable SSR error (missing root tags), or a build error. Verified\n// against next@16.1.7 and next@15.3.8: the sole auto-open is\n// AppDevOverlayErrorBoundary's componentDidCatch -> openErrorOverlay, which\n// a nested LoaderErrorBoundary never reaches.\n// So an *open* overlay is the fatal-vs-recoverable discriminator. The badge's\n// issue count is not: it lights for every collected issue, including a\n// server-side loader error React already recovered from by streaming the\n// LoaderErrorBoundary fallback (product unpublished, deleted collection, \u2026).\n// Keying off the badge reported those as full-page errors, blanking a preview\n// that had rendered its fallback.\n//\n// Reading \"open\" is version-dependent. next@16.3 keeps the dialog mounted for\n// recoverable issues, parked inside a wrapper it marks `data-rendered=\"false\"`\n// and hides with `display: none !important`; opening flips that to `\"true\"`.\n// Earlier versions mount the dialog only when it is open, so \"has no explicitly\n// closed ancestor\" reads correctly on both.\n//\n// We still *hide* the whole portal whenever Next paints red chrome (the errored\n// badge OR the dialog) so no dev overlay bleeds into the preview \u2014 a recovered\n// loader miss shouldn't flash a red dot either.\nconst ERRORED_BADGE_SELECTOR = '[data-next-badge][data-error=\"true\"]';\nconst COLLECTED_ISSUE_SELECTOR = \"[data-nextjs-dialog]\";\nconst CLOSED_DIALOG_OVERLAY_SELECTOR =\n '[data-nextjs-dialog-overlay][data-rendered=\"false\"]';\nconst RED_CHROME_SELECTOR = `${ERRORED_BADGE_SELECTOR}, ${COLLECTED_ISSUE_SELECTOR}`;\n\n// HMR portal churn flips the overlay for milliseconds in both directions; only a state that persists this long is real.\nconst STATUS_LATCH_MS = 500;\n\ntype PreviewStatus = \"ok\" | \"error\";\n\n// The embedder's origin as the browser reports it, trusted only if it is an allowed editor. `ancestorOrigins` names the actual parent and survives referrer policies and in-frame navigations, but Firefox lacks it; the referrer covers Firefox, the same way the annotation layer announces itself.\nfunction findInitialEditorOrigin(): string | null {\n // A top-level page has no editor to report to.\n if (window.self === window.top) {\n return null;\n }\n const ancestorOrigin =\n \"ancestorOrigins\" in window.location\n ? window.location.ancestorOrigins.item(0)\n : null;\n return (\n [ancestorOrigin, getOriginOrNull(document.referrer)].find((origin) => {\n return origin !== null && isAllowedEditorOrigin(origin);\n }) ?? null\n );\n}\n\nfunction getOriginOrNull(url: string): string | null {\n try {\n return new URL(url).origin;\n } catch {\n return null;\n }\n}\n\nexport function NextErrorReporter() {\n // eslint-disable-next-line replo/no-use-effect -- Observes Next's dev error overlay (a shadow-DOM node outside React's tree) and relays its state to the cross-origin embedder. No React alternative applies.\n React.useEffect(() => {\n let editorOrigin = findInitialEditorOrigin();\n let lastStatus: PreviewStatus | null = null;\n let shadowObserver: MutationObserver | null = null;\n let observedRoot: ShadowRoot | null = null;\n let pendingPost: { status: PreviewStatus; timeoutId: number } | null = null;\n\n // Status and route are only for the editor, so they never go out with a wildcard target; without a trusted origin the status is kept and delivered once one is learned.\n const deliver = () => {\n if (editorOrigin === null || lastStatus === null) {\n return;\n }\n window.parent.postMessage(\n {\n type: \"replo:preview:status\",\n route: window.location.pathname,\n status: lastStatus,\n },\n editorOrigin,\n );\n };\n\n const postNow = (status: PreviewStatus) => {\n if (lastStatus === status) {\n return;\n }\n lastStatus = status;\n deliver();\n };\n\n // Covers a missing or stale referrer (Firefox after an in-frame navigation): a message from the parent window at an allowed origin proves where the editor lives, the same capture the annotation layer uses for its replies.\n const handleParentMessage = (event: MessageEvent) => {\n if (event.source !== window.parent || !isAllowedEditorMessage(event)) {\n return;\n }\n if (event.origin === editorOrigin) {\n return;\n }\n editorOrigin = event.origin;\n deliver();\n };\n window.addEventListener(\"message\", handleParentMessage);\n\n // Latch every transition (the opposite observation cancels a pending one) \u2014 a false error <-> ok flip makes the editor reload the preview. Only the initial \"ok\" posts immediately, as the readiness signal.\n const post = (status: PreviewStatus) => {\n if (pendingPost) {\n if (pendingPost.status === status) {\n return;\n }\n window.clearTimeout(pendingPost.timeoutId);\n pendingPost = null;\n }\n if (lastStatus === status) {\n return;\n }\n if (lastStatus === null && status === \"ok\") {\n postNow(status);\n return;\n }\n const timeoutId = window.setTimeout(() => {\n pendingPost = null;\n postNow(status);\n }, STATUS_LATCH_MS);\n pendingPost = { status, timeoutId };\n };\n\n // NOTE (Max, 2026-06-10): Reads the dev overlay's current error state and\n // propagates it \u2014 re-attach the shadow observer to the live root, hide the\n // portal while erroring, and post ok/error up to the editor. Runs on init and\n // on every observed DOM mutation.\n const syncOverlayState = () => {\n const portal = document.getElementsByTagName(\"nextjs-portal\")[0];\n if (!(portal instanceof HTMLElement)) {\n post(\"ok\");\n return;\n }\n // NOTE (Max, 2026-06-10): Re-attach whenever Next hands us a fresh shadow\n // root, else we'd keep watching a detached one after the overlay is\n // recreated.\n const root = portal.shadowRoot;\n if (root && root !== observedRoot) {\n shadowObserver?.disconnect();\n shadowObserver = new MutationObserver(syncOverlayState);\n shadowObserver.observe(root, {\n childList: true,\n subtree: true,\n characterData: true,\n attributes: true,\n attributeFilter: [\"data-error\", \"data-rendered\"],\n });\n observedRoot = root;\n }\n // NOTE (Max, 2026-06-10): Hide the dev overlay at the source so the red\n // bubble never paints \u2014 for any red chrome, including a recovered miss's\n // errored badge.\n portal.style.display = root?.querySelector(RED_CHROME_SELECTOR)\n ? \"none\"\n : \"\";\n const dialog = root?.querySelector(COLLECTED_ISSUE_SELECTOR) ?? null;\n post(\n dialog && !dialog.closest(CLOSED_DIALOG_OVERLAY_SELECTOR)\n ? \"error\"\n : \"ok\",\n );\n };\n\n // NOTE (Max, 2026-06-10): Catches the portal being added/removed in the light\n // DOM; the re-attached shadow observer catches in-overlay changes.\n const lightObserver = new MutationObserver(syncOverlayState);\n lightObserver.observe(document.documentElement, {\n childList: true,\n subtree: true,\n });\n\n syncOverlayState();\n\n return () => {\n window.removeEventListener(\"message\", handleParentMessage);\n lightObserver.disconnect();\n shadowObserver?.disconnect();\n if (pendingPost) {\n window.clearTimeout(pendingPost.timeoutId);\n }\n };\n }, []);\n\n return null;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5252,7 +5252,7 @@ function useViewportSnapshotResponder() {
|
|
|
5252
5252
|
return startViewportSnapshotResponder({
|
|
5253
5253
|
targetWindow: window,
|
|
5254
5254
|
takeSnapshot: () => {
|
|
5255
|
-
return snapshot(document, { maskAllInputs:
|
|
5255
|
+
return snapshot(document, { maskAllInputs: true });
|
|
5256
5256
|
},
|
|
5257
5257
|
measureFrameRect: measureViewportFrameRect
|
|
5258
5258
|
});
|
|
@@ -261,6 +261,7 @@ export declare const errorUserFacingDetailsMessageSchema: z.ZodObject<{
|
|
|
261
261
|
isRecoverable: z.ZodOptional<z.ZodBoolean>;
|
|
262
262
|
detail: z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString>, z.ZodString]>>;
|
|
263
263
|
key: z.ZodString;
|
|
264
|
+
details: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
264
265
|
usage: z.ZodOptional<z.ZodObject<{
|
|
265
266
|
current: z.ZodNumber;
|
|
266
267
|
maximum: z.ZodNumber;
|
|
@@ -327,6 +328,7 @@ export declare const errorUserFacingDetailsSchema: z.ZodDiscriminatedUnion<[z.Zo
|
|
|
327
328
|
isRecoverable: z.ZodOptional<z.ZodBoolean>;
|
|
328
329
|
detail: z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString>, z.ZodString]>>;
|
|
329
330
|
key: z.ZodString;
|
|
331
|
+
details: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
330
332
|
usage: z.ZodOptional<z.ZodObject<{
|
|
331
333
|
current: z.ZodNumber;
|
|
332
334
|
maximum: z.ZodNumber;
|
|
@@ -52,6 +52,11 @@ var errorUserFacingDetailsMessageSchema = z.object({
|
|
|
52
52
|
// in FullPageModal only.
|
|
53
53
|
detail: z.array(z.string()).or(z.string()).optional(),
|
|
54
54
|
key: z.string(),
|
|
55
|
+
// Structured data for API callers next to the human message, e.g. the
|
|
56
|
+
// current draft version on a project-context 409 or the offending paths on
|
|
57
|
+
// a 422. The worker error handler serializes userFacingDetails as-is, so
|
|
58
|
+
// this is the one place such data can travel without a second envelope.
|
|
59
|
+
details: z.record(z.string(), z.unknown()).optional(),
|
|
55
60
|
usage: z.object({
|
|
56
61
|
current: z.number(),
|
|
57
62
|
maximum: z.number()
|
package/buildMetadata.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const BUILD_METADATA = {
|
|
2
2
|
"packageName": "@replohq/sandbox-runtime",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.8",
|
|
4
4
|
"branch": "HEAD",
|
|
5
|
-
"commit": "
|
|
6
|
-
"builtAt": "2026-09-
|
|
5
|
+
"commit": "8ca5e66f62c62e392dfc352766793b370f4db090",
|
|
6
|
+
"builtAt": "2026-09-14T21:14:47.553Z"
|
|
7
7
|
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { useEffect, useRef } from "react";
|
|
3
3
|
import { buildGoogleFontsCssUrl } from "../_vendor/replo-utils/lib/fonts.mjs";
|
|
4
4
|
import { designLibraryPreviewMessageSchema } from "../_vendor/schemas/designLibrary.mjs";
|
|
5
|
+
import { isAllowedEditorMessage } from "../annotations/bridgeGate";
|
|
5
6
|
const FONT_LINK_DATA_KEY = "reploDesignLibraryFont";
|
|
6
7
|
function escapeCssString(value) {
|
|
7
8
|
return value.replace(/\\/gu, "\\\\").replace(/'/gu, "\\'");
|
|
@@ -13,6 +14,7 @@ function DesignLibraryPreview() {
|
|
|
13
14
|
const ownsDefaultFont = useRef(false);
|
|
14
15
|
useEffect(() => {
|
|
15
16
|
const handleMessage = (event) => {
|
|
17
|
+
if (!isAllowedEditorMessage(event)) return;
|
|
16
18
|
const parsed = designLibraryPreviewMessageSchema.safeParse(event.data);
|
|
17
19
|
if (!parsed.success) return;
|
|
18
20
|
const { cssVars, fonts, defaultFontCssVar } = parsed.data;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../design-library-preview/DesignLibraryPreview.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\n\nimport { useEffect, useRef } from \"react\";\n\nimport { buildGoogleFontsCssUrl } from \"replo-utils/lib/fonts\";\nimport { designLibraryPreviewMessageSchema } from \"schemas/designLibrary\";\n\nconst FONT_LINK_DATA_KEY = \"reploDesignLibraryFont\";\n\nfunction escapeCssString(value: string): string {\n return value.replace(/\\\\/gu, \"\\\\\\\\\").replace(/'/gu, \"\\\\'\");\n}\n\nexport function DesignLibraryPreview() {\n // NOTE (Ben, 2026-06-03): The custom-property keys we set last time, so on the\n // next message we remove only the ones we own that the new override set drops.\n const ownedKeys = useRef<Set<string> | null>(null);\n // NOTE (Ben, 2026-06-05): The Google Font <link> hrefs we injected, keyed by\n // href so we de-dupe and remove only our own when the override set changes.\n const ownedFontLinks = useRef<Map<string, HTMLLinkElement> | null>(null);\n // NOTE (Ben, 2026-06-05): The @font-face <style> nodes for uploaded custom\n // fonts, keyed by family with the URL we injected, so we de-dupe, re-inject\n // when a family's asset URL changes (re-upload), and remove only our own when\n // the override set changes.\n const ownedFontFaces = useRef<Map<\n string,\n { url: string; style: HTMLStyleElement }\n > | null>(null);\n // NOTE (Ben, 2026-06-05): Whether we set the document default font-family, so\n // we can clear just that.\n const ownsDefaultFont = useRef(false);\n\n // eslint-disable-next-line replo/no-use-effect -- Subscribes to window message events from the parent editor.\n useEffect(() => {\n const handleMessage = (event: MessageEvent) => {\n const parsed = designLibraryPreviewMessageSchema.safeParse(event.data);\n if (!parsed.success) return;\n const { cssVars, fonts, defaultFontCssVar } = parsed.data;\n const root = document.documentElement;\n\n // --- CSS variable overrides (colors + font role vars) ---\n const nextVars: Record<string, string> = { ...cssVars };\n for (const font of fonts) {\n nextVars[font.cssVar] = `'${escapeCssString(font.family)}'`;\n }\n const owned = ownedKeys.current ?? new Set();\n for (const key of owned) {\n if (!(key in nextVars)) root.style.removeProperty(key);\n }\n ownedKeys.current = new Set(Object.keys(nextVars));\n for (const [key, value] of Object.entries(nextVars)) {\n root.style.setProperty(key, value);\n }\n\n // Custom uploaded fonts carry a faceUrl; Google fonts don't.\n const customFonts = fonts.filter((font) => Boolean(font.faceUrl));\n const googleFonts = fonts.filter((font) => !font.faceUrl);\n\n // --- Google Font stylesheets (<link> to the CDN) ---\n const links = ownedFontLinks.current ?? new Map();\n const nextHrefs = new Set(\n googleFonts.map((font) => buildGoogleFontsCssUrl(font.family)),\n );\n for (const [href, link] of links) {\n if (!nextHrefs.has(href)) {\n link.remove();\n links.delete(href);\n }\n }\n for (const href of nextHrefs) {\n if (links.has(href)) continue;\n const link = document.createElement(\"link\");\n link.rel = \"stylesheet\";\n link.href = href;\n link.dataset[FONT_LINK_DATA_KEY] = \"\";\n document.head.append(link);\n links.set(href, link);\n }\n ownedFontLinks.current = links;\n\n // --- Custom uploaded fonts (@font-face from the asset URL) ---\n const faces = ownedFontFaces.current ?? new Map();\n const nextFamilies = new Set(customFonts.map((font) => font.family));\n for (const [family, entry] of faces) {\n if (!nextFamilies.has(family)) {\n entry.style.remove();\n faces.delete(family);\n }\n }\n for (const font of customFonts) {\n if (!font.faceUrl) continue;\n const existing = faces.get(font.family);\n // Re-upload reuses the family but changes the asset URL; replace the\n // stale node rather than skipping it.\n if (existing?.url === font.faceUrl) continue;\n existing?.style.remove();\n const style = document.createElement(\"style\");\n style.dataset[FONT_LINK_DATA_KEY] = \"\";\n const format = font.format\n ? ` format('${escapeCssString(font.format)}')`\n : \"\";\n style.textContent = `@font-face{font-family:'${escapeCssString(font.family)}';font-weight:400;font-style:normal;src:url('${escapeCssString(font.faceUrl)}')${format};font-display:swap;}`;\n document.head.append(style);\n faces.set(font.family, { url: font.faceUrl, style });\n }\n ownedFontFaces.current = faces;\n\n // --- Document default font ---\n if (defaultFontCssVar) {\n root.style.setProperty(\"font-family\", `var(${defaultFontCssVar})`);\n ownsDefaultFont.current = true;\n } else if (ownsDefaultFont.current) {\n root.style.removeProperty(\"font-family\");\n ownsDefaultFont.current = false;\n }\n };\n window.addEventListener(\"message\", handleMessage);\n return () => {\n window.removeEventListener(\"message\", handleMessage);\n };\n }, []);\n\n return null;\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\";\n\nimport { useEffect, useRef } from \"react\";\n\nimport { buildGoogleFontsCssUrl } from \"replo-utils/lib/fonts\";\nimport { designLibraryPreviewMessageSchema } from \"schemas/designLibrary\";\n\nimport { isAllowedEditorMessage } from \"../annotations/bridgeGate\";\n\nconst FONT_LINK_DATA_KEY = \"reploDesignLibraryFont\";\n\nfunction escapeCssString(value: string): string {\n return value.replace(/\\\\/gu, \"\\\\\\\\\").replace(/'/gu, \"\\\\'\");\n}\n\nexport function DesignLibraryPreview() {\n // NOTE (Ben, 2026-06-03): The custom-property keys we set last time, so on the\n // next message we remove only the ones we own that the new override set drops.\n const ownedKeys = useRef<Set<string> | null>(null);\n // NOTE (Ben, 2026-06-05): The Google Font <link> hrefs we injected, keyed by\n // href so we de-dupe and remove only our own when the override set changes.\n const ownedFontLinks = useRef<Map<string, HTMLLinkElement> | null>(null);\n // NOTE (Ben, 2026-06-05): The @font-face <style> nodes for uploaded custom\n // fonts, keyed by family with the URL we injected, so we de-dupe, re-inject\n // when a family's asset URL changes (re-upload), and remove only our own when\n // the override set changes.\n const ownedFontFaces = useRef<Map<\n string,\n { url: string; style: HTMLStyleElement }\n > | null>(null);\n // NOTE (Ben, 2026-06-05): Whether we set the document default font-family, so\n // we can clear just that.\n const ownsDefaultFont = useRef(false);\n\n // eslint-disable-next-line replo/no-use-effect -- Subscribes to window message events from the parent editor.\n useEffect(() => {\n const handleMessage = (event: MessageEvent) => {\n if (!isAllowedEditorMessage(event)) return;\n const parsed = designLibraryPreviewMessageSchema.safeParse(event.data);\n if (!parsed.success) return;\n const { cssVars, fonts, defaultFontCssVar } = parsed.data;\n const root = document.documentElement;\n\n // --- CSS variable overrides (colors + font role vars) ---\n const nextVars: Record<string, string> = { ...cssVars };\n for (const font of fonts) {\n nextVars[font.cssVar] = `'${escapeCssString(font.family)}'`;\n }\n const owned = ownedKeys.current ?? new Set();\n for (const key of owned) {\n if (!(key in nextVars)) root.style.removeProperty(key);\n }\n ownedKeys.current = new Set(Object.keys(nextVars));\n for (const [key, value] of Object.entries(nextVars)) {\n root.style.setProperty(key, value);\n }\n\n // Custom uploaded fonts carry a faceUrl; Google fonts don't.\n const customFonts = fonts.filter((font) => Boolean(font.faceUrl));\n const googleFonts = fonts.filter((font) => !font.faceUrl);\n\n // --- Google Font stylesheets (<link> to the CDN) ---\n const links = ownedFontLinks.current ?? new Map();\n const nextHrefs = new Set(\n googleFonts.map((font) => buildGoogleFontsCssUrl(font.family)),\n );\n for (const [href, link] of links) {\n if (!nextHrefs.has(href)) {\n link.remove();\n links.delete(href);\n }\n }\n for (const href of nextHrefs) {\n if (links.has(href)) continue;\n const link = document.createElement(\"link\");\n link.rel = \"stylesheet\";\n link.href = href;\n link.dataset[FONT_LINK_DATA_KEY] = \"\";\n document.head.append(link);\n links.set(href, link);\n }\n ownedFontLinks.current = links;\n\n // --- Custom uploaded fonts (@font-face from the asset URL) ---\n const faces = ownedFontFaces.current ?? new Map();\n const nextFamilies = new Set(customFonts.map((font) => font.family));\n for (const [family, entry] of faces) {\n if (!nextFamilies.has(family)) {\n entry.style.remove();\n faces.delete(family);\n }\n }\n for (const font of customFonts) {\n if (!font.faceUrl) continue;\n const existing = faces.get(font.family);\n // Re-upload reuses the family but changes the asset URL; replace the\n // stale node rather than skipping it.\n if (existing?.url === font.faceUrl) continue;\n existing?.style.remove();\n const style = document.createElement(\"style\");\n style.dataset[FONT_LINK_DATA_KEY] = \"\";\n const format = font.format\n ? ` format('${escapeCssString(font.format)}')`\n : \"\";\n style.textContent = `@font-face{font-family:'${escapeCssString(font.family)}';font-weight:400;font-style:normal;src:url('${escapeCssString(font.faceUrl)}')${format};font-display:swap;}`;\n document.head.append(style);\n faces.set(font.family, { url: font.faceUrl, style });\n }\n ownedFontFaces.current = faces;\n\n // --- Document default font ---\n if (defaultFontCssVar) {\n root.style.setProperty(\"font-family\", `var(${defaultFontCssVar})`);\n ownsDefaultFont.current = true;\n } else if (ownsDefaultFont.current) {\n root.style.removeProperty(\"font-family\");\n ownsDefaultFont.current = false;\n }\n };\n window.addEventListener(\"message\", handleMessage);\n return () => {\n window.removeEventListener(\"message\", handleMessage);\n };\n }, []);\n\n return null;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replohq/sandbox-runtime",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.8",
|
|
4
4
|
"reploBuild": {
|
|
5
5
|
"packageName": "@replohq/sandbox-runtime",
|
|
6
|
-
"version": "1.6.
|
|
6
|
+
"version": "1.6.8",
|
|
7
7
|
"branch": "HEAD",
|
|
8
|
-
"commit": "
|
|
9
|
-
"builtAt": "2026-09-
|
|
8
|
+
"commit": "8ca5e66f62c62e392dfc352766793b370f4db090",
|
|
9
|
+
"builtAt": "2026-09-14T21:14:47.553Z"
|
|
10
10
|
},
|
|
11
11
|
"description": "Replo sandbox runtime — editor integration for agent-built Next.js sites.",
|
|
12
12
|
"license": "SEE LICENSE IN LICENSE",
|