@replohq/sandbox-runtime 1.7.4 → 1.7.6
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/buildMetadata.js +3 -3
- package/next/component-preview-client.js +40 -20
- package/package.json +4 -4
- package/select-mode/EditorSelection.js +30 -10
- package/select-mode/fast-refresh-relay.js +4 -0
- package/select-mode/hooks/useSelectionInteractions.js +3 -1
- package/select-mode/previewRendered.d.ts +1 -0
- package/select-mode/previewRendered.js +4 -0
- package/select-mode/symbolicate.d.ts +1 -0
- package/select-mode/symbolicate.js +41 -1
package/buildMetadata.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const BUILD_METADATA = {
|
|
2
2
|
"packageName": "@replohq/sandbox-runtime",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.6",
|
|
4
4
|
"branch": "HEAD",
|
|
5
|
-
"commit": "
|
|
6
|
-
"builtAt": "2026-09-
|
|
5
|
+
"commit": "eebf20f7ddeeb977a292b14a7abe974b07255b14",
|
|
6
|
+
"builtAt": "2026-09-18T20:52:15.073Z"
|
|
7
7
|
};
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
createIframeRpcChannel,
|
|
15
15
|
createIframeRpcRemote
|
|
16
16
|
} from "../_vendor/schemas/iframeRpcChannel.mjs";
|
|
17
|
+
import { PREVIEW_RENDERED_EVENT } from "../select-mode/previewRendered";
|
|
17
18
|
import { getComponentPreviewHref } from "./component-preview-href";
|
|
18
19
|
import {
|
|
19
20
|
mergeClientProps,
|
|
@@ -38,6 +39,7 @@ function getCurrentHref() {
|
|
|
38
39
|
function useComponentPreviewChannel(onSetProps) {
|
|
39
40
|
const onSetPropsRef = useRef(onSetProps);
|
|
40
41
|
onSetPropsRef.current = onSetProps;
|
|
42
|
+
const pendingUpdates = useRef(Promise.resolve());
|
|
41
43
|
useIframeRpcChannel(
|
|
42
44
|
useMemo(() => {
|
|
43
45
|
if (typeof window === "undefined") {
|
|
@@ -61,7 +63,20 @@ function useComponentPreviewChannel(onSetProps) {
|
|
|
61
63
|
outgoingMethods: componentPreviewToWebsiteBuilderMethodConfig,
|
|
62
64
|
incomingMethods: websiteBuilderToComponentPreviewMethodConfig,
|
|
63
65
|
handlers: {
|
|
64
|
-
setProps: (input) =>
|
|
66
|
+
setProps: (input) => {
|
|
67
|
+
const result = pendingUpdates.current.then(async () => {
|
|
68
|
+
const result2 = await onSetPropsRef.current(input);
|
|
69
|
+
window.dispatchEvent(new Event(PREVIEW_RENDERED_EVENT));
|
|
70
|
+
return result2;
|
|
71
|
+
});
|
|
72
|
+
pendingUpdates.current = result.then(
|
|
73
|
+
() => {
|
|
74
|
+
},
|
|
75
|
+
() => {
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
65
80
|
}
|
|
66
81
|
});
|
|
67
82
|
return {
|
|
@@ -109,30 +124,28 @@ function useComponentPreviewChannel(onSetProps) {
|
|
|
109
124
|
}, [])
|
|
110
125
|
);
|
|
111
126
|
}
|
|
112
|
-
function
|
|
113
|
-
const
|
|
114
|
-
const [isNavigationPending, startNavigation] = useTransition();
|
|
127
|
+
function useCommitPreviewUpdate() {
|
|
128
|
+
const [isUpdatePending, startUpdate] = useTransition();
|
|
115
129
|
const settleResolvers = useRef([]);
|
|
116
130
|
useEffect(() => {
|
|
117
|
-
if (!
|
|
131
|
+
if (!isUpdatePending) {
|
|
118
132
|
const resolvers = settleResolvers.current;
|
|
119
133
|
settleResolvers.current = [];
|
|
120
134
|
for (const resolve of resolvers) {
|
|
121
135
|
resolve();
|
|
122
136
|
}
|
|
123
137
|
}
|
|
124
|
-
}, [
|
|
125
|
-
return (
|
|
138
|
+
}, [isUpdatePending]);
|
|
139
|
+
return (update) => {
|
|
126
140
|
return new Promise((resolve) => {
|
|
127
141
|
settleResolvers.current.push(resolve);
|
|
128
|
-
|
|
129
|
-
router.replace(href, { scroll: false });
|
|
130
|
-
});
|
|
142
|
+
startUpdate(update);
|
|
131
143
|
});
|
|
132
144
|
};
|
|
133
145
|
}
|
|
134
146
|
function ComponentPreviewNavigation() {
|
|
135
|
-
const
|
|
147
|
+
const router = useRouter();
|
|
148
|
+
const commitPreviewUpdate = useCommitPreviewUpdate();
|
|
136
149
|
useComponentPreviewChannel(
|
|
137
150
|
async ({ props, sourceRevision }) => {
|
|
138
151
|
const nextHref = getComponentPreviewHref({
|
|
@@ -143,7 +156,9 @@ function ComponentPreviewNavigation() {
|
|
|
143
156
|
if (nextHref === getCurrentHref()) {
|
|
144
157
|
return { mode: "unchanged" };
|
|
145
158
|
}
|
|
146
|
-
await
|
|
159
|
+
await commitPreviewUpdate(() => {
|
|
160
|
+
return router.replace(nextHref, { scroll: false });
|
|
161
|
+
});
|
|
147
162
|
return { mode: "navigation" };
|
|
148
163
|
}
|
|
149
164
|
);
|
|
@@ -165,7 +180,8 @@ function ComponentPreviewStage({
|
|
|
165
180
|
propsRef.current = props;
|
|
166
181
|
const lastRawPropsRef = useRef(initialRawProps);
|
|
167
182
|
const lastSourceRevisionRef = useRef(initialSourceRevision);
|
|
168
|
-
const
|
|
183
|
+
const router = useRouter();
|
|
184
|
+
const commitPreviewUpdate = useCommitPreviewUpdate();
|
|
169
185
|
useComponentPreviewChannel(
|
|
170
186
|
async ({
|
|
171
187
|
props: nextRawProps,
|
|
@@ -183,17 +199,21 @@ function ComponentPreviewStage({
|
|
|
183
199
|
previous: lastRawPropsRef.current,
|
|
184
200
|
next: nextRawProps
|
|
185
201
|
})) {
|
|
186
|
-
await
|
|
202
|
+
await commitPreviewUpdate(() => {
|
|
203
|
+
return router.replace(nextHref, { scroll: false });
|
|
204
|
+
});
|
|
187
205
|
lastRawPropsRef.current = nextRawProps;
|
|
188
206
|
lastSourceRevisionRef.current = sourceRevision;
|
|
189
207
|
return { mode: "navigation" };
|
|
190
208
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
209
|
+
await commitPreviewUpdate(() => {
|
|
210
|
+
return setProps(
|
|
211
|
+
mergeClientProps({
|
|
212
|
+
currentResolved: propsRef.current,
|
|
213
|
+
nextRaw: nextRawProps
|
|
214
|
+
})
|
|
215
|
+
);
|
|
216
|
+
});
|
|
197
217
|
lastRawPropsRef.current = nextRawProps;
|
|
198
218
|
window.history.replaceState(null, "", nextHref);
|
|
199
219
|
return { mode: "client" };
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replohq/sandbox-runtime",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.6",
|
|
4
4
|
"reploBuild": {
|
|
5
5
|
"packageName": "@replohq/sandbox-runtime",
|
|
6
|
-
"version": "1.7.
|
|
6
|
+
"version": "1.7.6",
|
|
7
7
|
"branch": "HEAD",
|
|
8
|
-
"commit": "
|
|
9
|
-
"builtAt": "2026-09-
|
|
8
|
+
"commit": "eebf20f7ddeeb977a292b14a7abe974b07255b14",
|
|
9
|
+
"builtAt": "2026-09-18T20:52:15.073Z"
|
|
10
10
|
},
|
|
11
11
|
"description": "Replo sandbox runtime — editor integration for agent-built Next.js sites.",
|
|
12
12
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -15,7 +15,10 @@ import { useSelectionScrollSync } from "./hooks/useSelectionScrollSync";
|
|
|
15
15
|
import { useSelectModeController } from "./hooks/useSelectModeController";
|
|
16
16
|
import { getRepetitionSiblingElements } from "./hooks/useSiblingResolution";
|
|
17
17
|
import { getWebsiteBuilderRpc } from "./iframeRpcChannel";
|
|
18
|
-
import {
|
|
18
|
+
import { getDocumentScrollOffset } from "./interactions/selectionGeometry";
|
|
19
|
+
import { PREVIEW_RENDERED_EVENT } from "./previewRendered";
|
|
20
|
+
import { showElementSelectionFrames } from "./selectionFrame";
|
|
21
|
+
import { getConnectedSelectedElement } from "./selectModeSession";
|
|
19
22
|
import { symbolicateSelectedElement } from "./symbolicate";
|
|
20
23
|
function EditorSelection() {
|
|
21
24
|
const {
|
|
@@ -40,9 +43,13 @@ function EditorSelection() {
|
|
|
40
43
|
if (session.type !== "element") {
|
|
41
44
|
return;
|
|
42
45
|
}
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
const element = getConnectedSelectedElement(session);
|
|
47
|
+
if (!element) {
|
|
48
|
+
setSelectionType({ selectionType: "none" });
|
|
49
|
+
getWebsiteBuilderRpc()?.areaSelected({
|
|
50
|
+
rect: session.data.rect,
|
|
51
|
+
items: []
|
|
52
|
+
});
|
|
46
53
|
return;
|
|
47
54
|
}
|
|
48
55
|
const siblingElements = getRepetitionSiblingElements({ element });
|
|
@@ -54,22 +61,24 @@ function EditorSelection() {
|
|
|
54
61
|
if (descriptor.kind !== "react-element") {
|
|
55
62
|
return;
|
|
56
63
|
}
|
|
57
|
-
|
|
64
|
+
const refreshedSession = {
|
|
58
65
|
...session,
|
|
59
66
|
element,
|
|
60
67
|
siblingElements,
|
|
61
68
|
data: descriptor
|
|
62
69
|
};
|
|
70
|
+
interactionSessionRef.current = refreshedSession;
|
|
71
|
+
showElementSelectionFrames({
|
|
72
|
+
elements: [element, ...siblingElements],
|
|
73
|
+
scrollOffset: getDocumentScrollOffset()
|
|
74
|
+
});
|
|
63
75
|
void symbolicateSelectedElement(descriptor).then((symbolicated) => {
|
|
64
|
-
if (
|
|
65
|
-
session: interactionSessionRef.current,
|
|
66
|
-
selection: { type: "element", element }
|
|
67
|
-
})) {
|
|
76
|
+
if (interactionSessionRef.current !== refreshedSession) {
|
|
68
77
|
return;
|
|
69
78
|
}
|
|
70
79
|
getWebsiteBuilderRpc()?.elementSelected({ data: symbolicated });
|
|
71
80
|
});
|
|
72
|
-
}, [interactionSessionRef]);
|
|
81
|
+
}, [interactionSessionRef, setSelectionType]);
|
|
73
82
|
useEffect(() => {
|
|
74
83
|
installFastRefreshRelay({
|
|
75
84
|
onComplete: () => {
|
|
@@ -77,6 +86,17 @@ function EditorSelection() {
|
|
|
77
86
|
postSelectedElementSnapshot();
|
|
78
87
|
}
|
|
79
88
|
});
|
|
89
|
+
window.addEventListener(
|
|
90
|
+
PREVIEW_RENDERED_EVENT,
|
|
91
|
+
postSelectedElementSnapshot
|
|
92
|
+
);
|
|
93
|
+
return () => {
|
|
94
|
+
window.removeEventListener(
|
|
95
|
+
PREVIEW_RENDERED_EVENT,
|
|
96
|
+
postSelectedElementSnapshot
|
|
97
|
+
);
|
|
98
|
+
installFastRefreshRelay();
|
|
99
|
+
};
|
|
80
100
|
}, [editingElementRef, postSelectedElementSnapshot, reapplyOverrides]);
|
|
81
101
|
useParentMessaging({
|
|
82
102
|
applyOptimisticUpdate,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getWebsiteBuilderRpc } from "./iframeRpcChannel";
|
|
2
|
+
import { invalidateSymbolicationCache } from "./symbolicate";
|
|
2
3
|
let hasInstalledFastRefreshRelay = false;
|
|
3
4
|
let onFastRefreshComplete = null;
|
|
4
5
|
function installFastRefreshRelay({
|
|
@@ -13,6 +14,9 @@ function installFastRefreshRelay({
|
|
|
13
14
|
const originalInfo = console.info;
|
|
14
15
|
const relayIfFastRefreshComplete = (...args) => {
|
|
15
16
|
const message = args.join(" ");
|
|
17
|
+
if (message.includes("[Fast Refresh] rebuilding") || message.includes("[Fast Refresh] done")) {
|
|
18
|
+
invalidateSymbolicationCache();
|
|
19
|
+
}
|
|
16
20
|
if (message.includes("[Fast Refresh] done")) {
|
|
17
21
|
getWebsiteBuilderRpc()?.fastRefreshComplete(void 0);
|
|
18
22
|
onFastRefreshComplete?.();
|
|
@@ -308,7 +308,9 @@ function useSelectionInteractions({
|
|
|
308
308
|
data
|
|
309
309
|
};
|
|
310
310
|
void maybeSymbolicate(data).then((symbolicated) => {
|
|
311
|
-
|
|
311
|
+
const session = interactionSessionRef.current;
|
|
312
|
+
const isPendingClickOnSelection = session.type === "drag" && !session.hasExceededThreshold && committedSelectionAtPointerDown.length === 1 && committedSelectionAtPointerDown[0] === selectionElement;
|
|
313
|
+
if (!isPendingClickOnSelection && !isSelectionStillCurrent({
|
|
312
314
|
session: interactionSessionRef.current,
|
|
313
315
|
selection: { type: "element", element: selectionElement }
|
|
314
316
|
})) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PREVIEW_RENDERED_EVENT = "replo:preview-rendered";
|
|
@@ -66,7 +66,46 @@ function extractResolvedFrame(entry) {
|
|
|
66
66
|
}
|
|
67
67
|
return entry.value?.originalStackFrame ?? null;
|
|
68
68
|
}
|
|
69
|
-
|
|
69
|
+
const MAX_SYMBOLICATION_CACHE_ENTRIES = 128;
|
|
70
|
+
const symbolications = /* @__PURE__ */ new Map();
|
|
71
|
+
let symbolicationRevision = 0;
|
|
72
|
+
function invalidateSymbolicationCache() {
|
|
73
|
+
symbolications.clear();
|
|
74
|
+
symbolicationRevision += 1;
|
|
75
|
+
}
|
|
76
|
+
async function postFrames(request) {
|
|
77
|
+
if (request.frames.length === 0) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
const key = JSON.stringify(request);
|
|
81
|
+
const cached = symbolications.get(key);
|
|
82
|
+
if (cached) {
|
|
83
|
+
return cached;
|
|
84
|
+
}
|
|
85
|
+
const revision = symbolicationRevision;
|
|
86
|
+
const promise = fetchFrames(request).then((results) => {
|
|
87
|
+
if (revision !== symbolicationRevision) {
|
|
88
|
+
return request.frames.map(() => null);
|
|
89
|
+
}
|
|
90
|
+
const hasUnresolvedFrames = results.length !== request.frames.length || results.some((result, index) => {
|
|
91
|
+
const frame = extractResolvedFrame(result);
|
|
92
|
+
return !frame?.file || frame.file === request.frames[index]?.file;
|
|
93
|
+
});
|
|
94
|
+
if (hasUnresolvedFrames && symbolications.get(key) === promise) {
|
|
95
|
+
symbolications.delete(key);
|
|
96
|
+
}
|
|
97
|
+
return results;
|
|
98
|
+
});
|
|
99
|
+
if (symbolications.size >= MAX_SYMBOLICATION_CACHE_ENTRIES) {
|
|
100
|
+
const oldestKey = symbolications.keys().next().value;
|
|
101
|
+
if (oldestKey !== void 0) {
|
|
102
|
+
symbolications.delete(oldestKey);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
symbolications.set(key, promise);
|
|
106
|
+
return promise;
|
|
107
|
+
}
|
|
108
|
+
async function fetchFrames({
|
|
70
109
|
frames,
|
|
71
110
|
isServer
|
|
72
111
|
}) {
|
|
@@ -195,5 +234,6 @@ async function symbolicateSelectedElement(selectedElement) {
|
|
|
195
234
|
};
|
|
196
235
|
}
|
|
197
236
|
export {
|
|
237
|
+
invalidateSymbolicationCache,
|
|
198
238
|
symbolicateSelectedElement
|
|
199
239
|
};
|