@replohq/sandbox-runtime 1.17.0 → 1.18.0

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.
@@ -0,0 +1,541 @@
1
+ import { randomUUID } from './chunk-LIPH4O4F.mjs';
2
+ import { isNullish } from './chunk-PH3S6LJI.mjs';
3
+ import * as z from 'zod';
4
+
5
+ var defaultLogger = {
6
+ info: console.log,
7
+ debug: console.debug,
8
+ error: console.error
9
+ };
10
+
11
+ var ReploError = class _ReploError extends Error {
12
+ config;
13
+ id;
14
+ constructor(config) {
15
+ super(config.message);
16
+ this.message = config.message ?? this.constructor.name;
17
+ this.config = config;
18
+ this.id = randomUUID();
19
+ const cause = config.cause;
20
+ if (cause) {
21
+ this.cause = cause;
22
+ if (cause instanceof _ReploError) {
23
+ this.config.additionalData = {
24
+ ...this.config.additionalData,
25
+ ...cause.config.additionalData
26
+ };
27
+ }
28
+ }
29
+ }
30
+ };
31
+
32
+ var errorUserFacingDetailsMessageSchema = z.object({
33
+ type: z.enum([
34
+ "toast",
35
+ "fullPageModal",
36
+ "shopifyStorePassword",
37
+ "billing.addPublishedElements",
38
+ "billing.addShopifyThemeProject",
39
+ "billing.addReploSite",
40
+ "agentBilling.shopifyThemeBuilder.publishedElements.exceeded",
41
+ "agentBilling.shopifyThemeBuilder.projects.exceeded",
42
+ "agentBilling.sites.exceeded",
43
+ "agentBilling.credits.exceeded",
44
+ "agentBilling.upgradeRequired"
45
+ ]),
46
+ message: z.string(),
47
+ isRecoverable: z.boolean().optional(),
48
+ detail: z.array(z.string()).or(z.string()).optional(),
49
+ key: z.string(),
50
+ details: z.record(z.string(), z.unknown()).optional(),
51
+ usage: z.object({
52
+ current: z.number(),
53
+ maximum: z.number()
54
+ }).optional(),
55
+ plan: z.string().optional(),
56
+ callToAction: z.discriminatedUnion("type", [
57
+ z.object({ type: z.literal("reload") }),
58
+ z.object({ type: z.literal("installApp") }),
59
+ z.object({ type: z.literal("goToHomepage") }),
60
+ z.object({ type: z.literal("navigateToProjectEditor") }),
61
+ z.object({ type: z.literal("closeModal") }),
62
+ z.object({
63
+ type: z.literal("navigateToLink"),
64
+ link: z.discriminatedUnion("type", [
65
+ z.object({ type: z.literal("errors.wrongJsonExtension") }),
66
+ z.object({ type: z.literal("errors.chunkingError") }),
67
+ z.object({ type: z.literal("errors.indexBackupError") }),
68
+ z.object({
69
+ type: z.literal("errors.shopifyIntegrationAlreadyAssigned")
70
+ }),
71
+ z.object({
72
+ type: z.literal("errors.shopifyIntegrationMissingAccessScopes"),
73
+ url: z.string()
74
+ }),
75
+ z.object({
76
+ type: z.literal("customLink"),
77
+ url: z.string()
78
+ }),
79
+ z.object({
80
+ type: z.literal(
81
+ "errors.projectConnectedToBillingIntegrationForShopifyWorkspace"
82
+ )
83
+ }),
84
+ z.object({ type: z.literal("errors.themeAssetSizeLimitExceeded") }),
85
+ z.object({ type: z.literal("errors.themeTotalSizeLimitExceeded") }),
86
+ z.object({
87
+ type: z.literal("errors.pageMetafieldDefinitionsChanged")
88
+ }),
89
+ z.object({ type: z.literal("settings.billing") })
90
+ ])
91
+ })
92
+ ]).optional()
93
+ }).describe("ErrorUserFacingDetailsWithMessage");
94
+ z.discriminatedUnion("type", [
95
+ errorUserFacingDetailsMessageSchema,
96
+ z.object({
97
+ type: z.literal("uncommon_none"),
98
+ key: z.string(),
99
+ usage: z.object({
100
+ current: z.number(),
101
+ maximum: z.number()
102
+ }).optional(),
103
+ message: z.string().optional(),
104
+ isRecoverable: z.boolean().optional(),
105
+ detail: z.array(z.string()).or(z.string()).optional()
106
+ })
107
+ ]).describe("ErrorUserFacingDetails");
108
+ var IframeRpcChannelError = class extends ReploError {
109
+ };
110
+ var IframeRpcChannelTimeoutError = class extends IframeRpcChannelError {
111
+ constructor({ method, timeoutMs }) {
112
+ super({
113
+ message: `RPC method '${method}' timed out after ${timeoutMs}ms`,
114
+ additionalData: { method, timeoutMs }
115
+ });
116
+ }
117
+ };
118
+ var IframeRpcChannelTargetUnavailableError = class extends IframeRpcChannelError {
119
+ constructor({
120
+ callType,
121
+ method
122
+ }) {
123
+ super({
124
+ message: method ? `RPC target window was unavailable for '${method}'` : "RPC target window was unavailable for a response",
125
+ additionalData: { callType, method }
126
+ });
127
+ }
128
+ };
129
+ var IframeRpcChannelRemoteError = class extends IframeRpcChannelError {
130
+ constructor({ method, message }) {
131
+ super({
132
+ message: `RPC method '${method}' failed: ${message}`,
133
+ additionalData: { method }
134
+ });
135
+ }
136
+ };
137
+ var IframeRpcChannelUnsubscribedError = class extends IframeRpcChannelError {
138
+ constructor({ method }) {
139
+ super({
140
+ message: `RPC was unsubscribed while '${method}' was pending`,
141
+ additionalData: { method }
142
+ });
143
+ }
144
+ };
145
+ var IframeRpcChannelUnknownMethodError = class extends IframeRpcChannelError {
146
+ constructor({ method }) {
147
+ super({
148
+ message: `Unknown RPC method '${method}'`,
149
+ additionalData: { method }
150
+ });
151
+ }
152
+ };
153
+ var IframeRpcChannelConfigurationError = class extends IframeRpcChannelError {
154
+ constructor({ message }) {
155
+ super({ message });
156
+ }
157
+ };
158
+ var IframeRpcChannelInvalidInputError = class extends IframeRpcChannelError {
159
+ constructor({ method, cause }) {
160
+ super({
161
+ message: `Invalid RPC input for '${method}': ${cause.message}`,
162
+ cause,
163
+ additionalData: { method }
164
+ });
165
+ }
166
+ };
167
+
168
+ var iframeRpcCallResultSchema = z.discriminatedUnion("type", [
169
+ z.object({
170
+ type: z.literal("success"),
171
+ data: z.unknown()
172
+ }),
173
+ z.object({
174
+ type: z.literal("error"),
175
+ errorMessage: z.string()
176
+ })
177
+ ]);
178
+ var iframeRpcCallSchema = z.discriminatedUnion("type", [
179
+ z.object({
180
+ type: z.literal("notify"),
181
+ method: z.string(),
182
+ data: z.unknown().optional()
183
+ }),
184
+ z.object({
185
+ type: z.literal("request"),
186
+ id: z.uuid(),
187
+ method: z.string(),
188
+ data: z.unknown().optional()
189
+ }),
190
+ z.object({
191
+ type: z.literal("response"),
192
+ id: z.uuid(),
193
+ result: iframeRpcCallResultSchema
194
+ })
195
+ ]);
196
+ function isFireAndForgetOutputSchema(outputSchema) {
197
+ return outputSchema.type === "void" || outputSchema instanceof z.ZodVoid;
198
+ }
199
+ function isUndefinedInputSchema(inputSchema) {
200
+ return inputSchema.type === "undefined" || inputSchema instanceof z.ZodUndefined;
201
+ }
202
+ function coerceEmptyRpcInput({
203
+ input,
204
+ inputSchema
205
+ }) {
206
+ if (isNullish(input) && isUndefinedInputSchema(inputSchema)) {
207
+ return void 0;
208
+ }
209
+ return input;
210
+ }
211
+ function createIframeRpcRemote(channelOrGetChannel, methods) {
212
+ const getChannel = typeof channelOrGetChannel === "function" ? channelOrGetChannel : () => channelOrGetChannel;
213
+ const outgoingMethods = methods ?? getChannel()?.outgoingMethods ?? null;
214
+ if (!outgoingMethods) {
215
+ throw new IframeRpcChannelConfigurationError({
216
+ message: "createIframeRpcRemote requires methods when the channel may be null"
217
+ });
218
+ }
219
+ return Object.fromEntries(
220
+ Object.keys(outgoingMethods).map(
221
+ (method) => {
222
+ return [
223
+ method,
224
+ (input) => {
225
+ const definition = outgoingMethods[method];
226
+ const channel = getChannel();
227
+ if (!channel) {
228
+ if (definition && isFireAndForgetOutputSchema(definition.outputSchema)) {
229
+ return void 0;
230
+ }
231
+ return Promise.reject(
232
+ new IframeRpcChannelUnsubscribedError({ method })
233
+ );
234
+ }
235
+ return channel.call({
236
+ method,
237
+ input
238
+ });
239
+ }
240
+ ];
241
+ }
242
+ )
243
+ );
244
+ }
245
+ function createIframeRpcChannel(options) {
246
+ const {
247
+ subscriberWindow,
248
+ channelIdentifier,
249
+ version,
250
+ getTargetWindow,
251
+ targetOrigin,
252
+ isExpectedMessage,
253
+ outgoingMethods,
254
+ incomingMethods,
255
+ handlers
256
+ } = options;
257
+ const pendingRequests = /* @__PURE__ */ new Map();
258
+ let subscriberCount = 0;
259
+ let handlerMeta;
260
+ const postMessage = (call2, targetWindow = getTargetWindow()) => {
261
+ targetWindow?.postMessage(
262
+ {
263
+ channelIdentifier,
264
+ version,
265
+ ...call2
266
+ },
267
+ targetOrigin
268
+ );
269
+ };
270
+ const respondWithError = ({
271
+ id,
272
+ errorMessage
273
+ }) => {
274
+ postMessage({
275
+ type: "response",
276
+ id,
277
+ result: { type: "error", errorMessage }
278
+ });
279
+ };
280
+ const handleRequest = async (message) => {
281
+ const definition = incomingMethods[message.method];
282
+ if (!definition || isFireAndForgetOutputSchema(definition.outputSchema)) {
283
+ respondWithError({
284
+ id: message.id,
285
+ errorMessage: `Unknown RPC method '${message.method}'`
286
+ });
287
+ return;
288
+ }
289
+ const parsedInput = definition.inputSchema.safeParse(
290
+ coerceEmptyRpcInput({
291
+ input: message.data,
292
+ inputSchema: definition.inputSchema
293
+ })
294
+ );
295
+ if (!parsedInput.success) {
296
+ respondWithError({
297
+ id: message.id,
298
+ errorMessage: `Invalid request payload for '${message.method}': ${parsedInput.error.message}`
299
+ });
300
+ return;
301
+ }
302
+ try {
303
+ const handler = handlers[
304
+ message.method
305
+ ];
306
+ const result = await handler(parsedInput.data, handlerMeta);
307
+ const parsedOutput = definition.outputSchema.safeParse(result);
308
+ if (!parsedOutput.success) {
309
+ respondWithError({
310
+ id: message.id,
311
+ errorMessage: `Invalid response payload for '${message.method}': ${parsedOutput.error.message}`
312
+ });
313
+ return;
314
+ }
315
+ postMessage({
316
+ type: "response",
317
+ id: message.id,
318
+ result: { type: "success", data: parsedOutput.data }
319
+ });
320
+ } catch (error) {
321
+ respondWithError({
322
+ id: message.id,
323
+ errorMessage: error instanceof Error ? error.message : String(error)
324
+ });
325
+ }
326
+ };
327
+ const handleNotification = (message) => {
328
+ const definition = incomingMethods[message.method];
329
+ if (!definition || !isFireAndForgetOutputSchema(definition.outputSchema)) {
330
+ return;
331
+ }
332
+ const parsedInput = definition.inputSchema.safeParse(
333
+ coerceEmptyRpcInput({
334
+ input: message.data,
335
+ inputSchema: definition.inputSchema
336
+ })
337
+ );
338
+ if (!parsedInput.success) {
339
+ defaultLogger.error(
340
+ `[iframeRpcChannel] Dropping invalid notify for '${message.method}': ${parsedInput.error.message}`
341
+ );
342
+ return;
343
+ }
344
+ const handler = handlers[
345
+ message.method
346
+ ];
347
+ handler(parsedInput.data, handlerMeta);
348
+ };
349
+ const handleResponse = (message) => {
350
+ const pendingRequest = pendingRequests.get(message.id);
351
+ if (!pendingRequest) {
352
+ return;
353
+ }
354
+ clearTimeout(pendingRequest.timeoutId);
355
+ pendingRequests.delete(message.id);
356
+ if (message.result.type === "error") {
357
+ pendingRequest.reject(
358
+ new IframeRpcChannelRemoteError({
359
+ method: pendingRequest.method,
360
+ message: message.result.errorMessage
361
+ })
362
+ );
363
+ return;
364
+ }
365
+ const definition = outgoingMethods[pendingRequest.method];
366
+ const parsedOutput = definition?.outputSchema.safeParse(
367
+ message.result.data
368
+ );
369
+ if (!parsedOutput?.success) {
370
+ pendingRequest.reject(
371
+ new IframeRpcChannelRemoteError({
372
+ method: pendingRequest.method,
373
+ message: `Invalid response payload: ${parsedOutput?.error.message ?? "unknown method"}`
374
+ })
375
+ );
376
+ return;
377
+ }
378
+ pendingRequest.resolve(parsedOutput.data);
379
+ };
380
+ const handleMessage = (event) => {
381
+ const parsedMetadata = z.object({
382
+ channelIdentifier: z.literal(channelIdentifier),
383
+ version: z.literal(version)
384
+ }).safeParse(event.data);
385
+ if (!parsedMetadata.success) {
386
+ return;
387
+ }
388
+ if (!isExpectedMessage(event)) {
389
+ const isSiblingFrame = typeof document !== "undefined" && Array.from(document.querySelectorAll("iframe")).some((frame) => {
390
+ return event.source !== null && frame.contentWindow === event.source;
391
+ });
392
+ if (isSiblingFrame) {
393
+ return;
394
+ }
395
+ defaultLogger.error("[iframeRpcChannel] Dropping unexpected message", {
396
+ channelIdentifier,
397
+ version,
398
+ origin: event.origin,
399
+ hasSource: Boolean(event.source)
400
+ });
401
+ return;
402
+ }
403
+ const parsedCall = iframeRpcCallSchema.safeParse(event.data);
404
+ if (!parsedCall.success) {
405
+ return;
406
+ }
407
+ const message = parsedCall.data;
408
+ if (message.type === "notify") {
409
+ handleNotification(message);
410
+ return;
411
+ }
412
+ if (message.type === "request") {
413
+ void handleRequest(message);
414
+ return;
415
+ }
416
+ handleResponse(message);
417
+ };
418
+ const rejectPendingRequests = () => {
419
+ for (const pendingRequest of pendingRequests.values()) {
420
+ clearTimeout(pendingRequest.timeoutId);
421
+ pendingRequest.reject(
422
+ new IframeRpcChannelUnsubscribedError({
423
+ method: pendingRequest.method
424
+ })
425
+ );
426
+ }
427
+ pendingRequests.clear();
428
+ };
429
+ const subscribe = () => {
430
+ subscriberCount += 1;
431
+ if (subscriberCount === 1) {
432
+ subscriberWindow.addEventListener("message", handleMessage);
433
+ }
434
+ let isActive = true;
435
+ return () => {
436
+ if (!isActive) {
437
+ return;
438
+ }
439
+ isActive = false;
440
+ subscriberCount -= 1;
441
+ if (subscriberCount === 0) {
442
+ subscriberWindow.removeEventListener("message", handleMessage);
443
+ rejectPendingRequests();
444
+ }
445
+ };
446
+ };
447
+ const call = ({
448
+ method,
449
+ input
450
+ }) => {
451
+ const definition = outgoingMethods[method];
452
+ if (!definition) {
453
+ return Promise.reject(
454
+ new IframeRpcChannelUnknownMethodError({ method })
455
+ );
456
+ }
457
+ const isFireAndForget = isFireAndForgetOutputSchema(
458
+ definition.outputSchema
459
+ );
460
+ const parsedInput = definition.inputSchema.safeParse(
461
+ coerceEmptyRpcInput({
462
+ input,
463
+ inputSchema: definition.inputSchema
464
+ })
465
+ );
466
+ if (!parsedInput.success) {
467
+ const error = new IframeRpcChannelInvalidInputError({
468
+ method,
469
+ cause: parsedInput.error
470
+ });
471
+ if (isFireAndForget) {
472
+ throw error;
473
+ }
474
+ return Promise.reject(error);
475
+ }
476
+ if (isFireAndForget) {
477
+ postMessage({
478
+ type: "notify",
479
+ method,
480
+ data: parsedInput.data
481
+ });
482
+ return void 0;
483
+ }
484
+ if (subscriberCount === 0) {
485
+ return Promise.reject(
486
+ new IframeRpcChannelUnsubscribedError({ method })
487
+ );
488
+ }
489
+ const targetWindow = getTargetWindow();
490
+ if (!targetWindow) {
491
+ return Promise.reject(
492
+ new IframeRpcChannelTargetUnavailableError({
493
+ callType: "request",
494
+ method
495
+ })
496
+ );
497
+ }
498
+ const id = randomUUID();
499
+ const timeoutMs = definition.timeoutMs ?? 3e4;
500
+ return new Promise(
501
+ (resolve, reject) => {
502
+ const timeoutId = setTimeout(() => {
503
+ pendingRequests.delete(id);
504
+ reject(new IframeRpcChannelTimeoutError({ method, timeoutMs }));
505
+ }, timeoutMs);
506
+ pendingRequests.set(id, {
507
+ method,
508
+ resolve: (value) => {
509
+ resolve(value);
510
+ },
511
+ reject,
512
+ timeoutId
513
+ });
514
+ try {
515
+ postMessage(
516
+ {
517
+ type: "request",
518
+ id,
519
+ method,
520
+ data: parsedInput.data
521
+ },
522
+ targetWindow
523
+ );
524
+ } catch (error) {
525
+ clearTimeout(timeoutId);
526
+ pendingRequests.delete(id);
527
+ reject(error);
528
+ }
529
+ }
530
+ );
531
+ };
532
+ const channel = {
533
+ subscribe,
534
+ call,
535
+ outgoingMethods
536
+ };
537
+ handlerMeta = { channel: createIframeRpcRemote(channel) };
538
+ return channel;
539
+ }
540
+
541
+ export { createIframeRpcChannel, createIframeRpcRemote, iframeRpcCallSchema };
@@ -0,0 +1,30 @@
1
+ export type NextErrorFeedEvent = {
2
+ kind: "rootCaught";
3
+ error: unknown;
4
+ } | {
5
+ kind: "error";
6
+ origin: "runtime" | "recoverable" | "console";
7
+ error: Error;
8
+ } | {
9
+ kind: "rejection";
10
+ error: Error;
11
+ };
12
+ type NextErrorFeedListener = (event: NextErrorFeedEvent) => void;
13
+ type NextErrorFeedState = {
14
+ listener: NextErrorFeedListener | null;
15
+ dispose: () => void;
16
+ };
17
+ declare global {
18
+ interface Window {
19
+ __reploNextErrorFeed?: NextErrorFeedState;
20
+ }
21
+ }
22
+ /**
23
+ * Routes Next's error feed to `listener`, replacing any previous listener.
24
+ * The subscriber lives in its own detached React root so it outlives a root
25
+ * failure of the page tree and never unmounts: unmounting `useErrorHandler`
26
+ * clears Next's shared error queues.
27
+ */
28
+ export declare function installNextErrorFeed(listener: NextErrorFeedListener): void;
29
+ export declare function resetNextErrorFeedForTests(): void;
30
+ export {};
@@ -0,0 +1,64 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { isRecoverableError } from "next/dist/client/react-client-callbacks/on-recoverable-error";
4
+ import { isConsoleError } from "next/dist/next-devtools/shared/console-error";
5
+ import { AppDevOverlayErrorBoundary } from "next/dist/next-devtools/userspace/app/app-dev-overlay-error-boundary";
6
+ import { useErrorHandler } from "next/dist/next-devtools/userspace/app/errors/use-error-handler";
7
+ import { flushSync } from "react-dom";
8
+ import { createRoot } from "react-dom/client";
9
+ const ROOT_BOUNDARY_PATCHED = Symbol.for(
10
+ "replo.previewHealth.rootBoundaryPatched"
11
+ );
12
+ function emit(event) {
13
+ window.__reploNextErrorFeed?.listener?.(event);
14
+ }
15
+ function classifyError(error) {
16
+ if (isRecoverableError(error)) {
17
+ return "recoverable";
18
+ }
19
+ return isConsoleError(error) ? "console" : "runtime";
20
+ }
21
+ function handleError(error) {
22
+ emit({ kind: "error", origin: classifyError(error), error });
23
+ }
24
+ function handleRejection(error) {
25
+ emit({ kind: "rejection", error });
26
+ }
27
+ function ErrorFeedSubscriber() {
28
+ useErrorHandler(handleError, handleRejection);
29
+ return null;
30
+ }
31
+ function patchRootBoundary() {
32
+ const prototype = AppDevOverlayErrorBoundary.prototype;
33
+ if (Reflect.get(prototype, ROOT_BOUNDARY_PATCHED)) {
34
+ return;
35
+ }
36
+ const original = prototype.componentDidCatch;
37
+ prototype.componentDidCatch = function componentDidCatch(error) {
38
+ emit({ kind: "rootCaught", error });
39
+ original.call(this, error);
40
+ };
41
+ Reflect.set(prototype, ROOT_BOUNDARY_PATCHED, true);
42
+ }
43
+ function installNextErrorFeed(listener) {
44
+ if (window.__reploNextErrorFeed) {
45
+ window.__reploNextErrorFeed.listener = listener;
46
+ return;
47
+ }
48
+ patchRootBoundary();
49
+ const feedDocument = document.implementation.createHTMLDocument();
50
+ const root = createRoot(feedDocument.createElement("div"));
51
+ window.__reploNextErrorFeed = {
52
+ listener,
53
+ dispose: () => root.unmount()
54
+ };
55
+ flushSync(() => root.render(/* @__PURE__ */ jsx(ErrorFeedSubscriber, {})));
56
+ }
57
+ function resetNextErrorFeedForTests() {
58
+ window.__reploNextErrorFeed?.dispose();
59
+ delete window.__reploNextErrorFeed;
60
+ }
61
+ export {
62
+ installNextErrorFeed,
63
+ resetNextErrorFeedForTests
64
+ };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@replohq/sandbox-runtime",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "reploBuild": {
5
5
  "packageName": "@replohq/sandbox-runtime",
6
- "version": "1.17.0",
6
+ "version": "1.18.0",
7
7
  "branch": "HEAD",
8
- "commit": "7d16c9a29182a37625a958569422b94a08b9a627",
9
- "builtAt": "2026-10-20T04:21:24.844Z"
8
+ "commit": "371ae98105e9f99b04918b3dffe40867380cd9ef",
9
+ "builtAt": "2026-10-21T22:33:13.604Z"
10
10
  },
11
11
  "description": "Replo sandbox runtime — editor integration for agent-built Next.js sites.",
12
12
  "license": "SEE LICENSE IN LICENSE",
@@ -17,14 +17,6 @@
17
17
  "types": "./sandbox-runtime.d.ts",
18
18
  "default": "./sandbox-runtime.js"
19
19
  },
20
- "./next/component-preview": {
21
- "types": "./next/component-preview.d.ts",
22
- "default": "./next/component-preview.js"
23
- },
24
- "./next/component-preview-client": {
25
- "types": "./next/component-preview-client.d.ts",
26
- "default": "./next/component-preview-client.js"
27
- },
28
20
  "./next/config": {
29
21
  "types": "./next/config.d.ts",
30
22
  "default": "./next/config.js"
@@ -42,6 +34,7 @@
42
34
  "@tiptap/extension-underline": "^2.25.0",
43
35
  "@tiptap/pm": "^2.25.0",
44
36
  "@zumer/snapdom": "^3.0.0",
37
+ "async-retry": "^1.3.3",
45
38
  "uuid": "^11.1.0",
46
39
  "zod": "^4.3.6",
47
40
  "zustand": "^4.5.7"
@@ -0,0 +1,25 @@
1
+ type CaughtError = {
2
+ error: unknown;
3
+ componentStack: string | null;
4
+ };
5
+ export type PreviewHealthActions = {
6
+ /** The boundary committed the page: a mount, or a recovery after a reset. */
7
+ boundaryRendered: (boundaryId: string) => void;
8
+ boundaryCaughtError: (boundaryId: string, caught: CaughtError) => void;
9
+ boundaryUnmounted: (boundaryId: string) => void;
10
+ rootCaughtError: (error: unknown) => void;
11
+ buildErrorsChanged: (messages: string[]) => void;
12
+ };
13
+ type PreviewHealthMonitor = {
14
+ actions: PreviewHealthActions;
15
+ dispose: () => void;
16
+ };
17
+ declare global {
18
+ interface Window {
19
+ __reploPreviewHealthMonitor?: PreviewHealthMonitor;
20
+ }
21
+ }
22
+ /** The document's preview health actions; null outside an embedded preview. */
23
+ export declare function getPreviewHealthActions(): PreviewHealthActions | null;
24
+ export declare function resetPreviewHealthMonitorForTests(): void;
25
+ export {};