@replohq/sandbox-runtime 0.3.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/_vendor/schemas/generated/selectModeIframeRpc.d.ts +8 -0
  2. package/_vendor/schemas/selectModeIframeRpc.d.ts +21 -1
  3. package/_vendor/schemas/selectModeIframeRpc.mjs +11 -3
  4. package/buildMetadata.d.ts +9 -0
  5. package/buildMetadata.js +7 -0
  6. package/buildMetadata.js.map +7 -0
  7. package/package.json +17 -5
  8. package/select-mode/EditorSelection.js +8 -2
  9. package/select-mode/EditorSelection.js.map +2 -2
  10. package/select-mode/fiber.d.ts +12 -17
  11. package/select-mode/fiber.js +228 -64
  12. package/select-mode/fiber.js.map +2 -2
  13. package/select-mode/hooks/useEditModeStateMachine.js +2 -0
  14. package/select-mode/hooks/useEditModeStateMachine.js.map +2 -2
  15. package/select-mode/hooks/useOptimisticOverrides.js +2 -2
  16. package/select-mode/hooks/useOptimisticOverrides.js.map +2 -2
  17. package/select-mode/hooks/useParentMessaging.js +21 -1
  18. package/select-mode/hooks/useParentMessaging.js.map +2 -2
  19. package/select-mode/hooks/useSelectModeController.js +2 -0
  20. package/select-mode/hooks/useSelectModeController.js.map +2 -2
  21. package/select-mode/hooks/useSelectionHover.d.ts +4 -5
  22. package/select-mode/hooks/useSelectionHover.js +21 -3
  23. package/select-mode/hooks/useSelectionHover.js.map +2 -2
  24. package/select-mode/hooks/useSelectionInteractions.js +12 -11
  25. package/select-mode/hooks/useSelectionInteractions.js.map +2 -2
  26. package/select-mode/hooks/useSelectionScrollSync.d.ts +3 -4
  27. package/select-mode/hooks/useSelectionScrollSync.js +4 -9
  28. package/select-mode/hooks/useSelectionScrollSync.js.map +2 -2
  29. package/select-mode/hooks/useSiblingResolution.d.ts +2 -2
  30. package/select-mode/hooks/useSiblingResolution.js +10 -47
  31. package/select-mode/hooks/useSiblingResolution.js.map +2 -2
  32. package/select-mode/iframeRpcChannel.d.ts +7 -1
  33. package/select-mode/selectModeSession.d.ts +1 -0
  34. package/select-mode/selectModeSession.js.map +2 -2
  35. package/select-mode/selectionFrame.d.ts +13 -17
  36. package/select-mode/selectionFrame.js +18 -5
  37. package/select-mode/selectionFrame.js.map +2 -2
@@ -1,4 +1,11 @@
1
1
  import type { DisplayMode, SelectModeSelectionType, SelectModeOptimisticUpdatePayload, SelectModeFormatCommand, SelectableData, SelectModeBoundingRect, SelectModeClickPosition, SelectModeSelectionRect, SelectModeTiptapActiveMarks } from "./selectMode";
2
+ export type IframeRuntimeBuildMetadata = {
3
+ packageName: string;
4
+ version: string;
5
+ branch: string | null;
6
+ commit: string | null;
7
+ builtAt: string | null;
8
+ };
2
9
  export type SelectModeParentMessage = {
3
10
  method: "setDisplayMode";
4
11
  data: {
@@ -46,6 +53,7 @@ export type SelectModeParentMessage = {
46
53
  };
47
54
  export type SelectModeIframeMessage = {
48
55
  method: "iframeReady";
56
+ data?: IframeRuntimeBuildMetadata;
49
57
  } | {
50
58
  method: "elementSelected";
51
59
  data: {
@@ -2,6 +2,13 @@ import type { SelectModeIframeMessage, SelectModeParentMessage } from "./generat
2
2
  import { z } from "zod";
3
3
  export declare const SELECT_MODE_IFRAME_RPC_CHANNEL_ID: "replo:select-mode-iframe-rpc";
4
4
  export declare const SELECT_MODE_IFRAME_RPC_CHANNEL_VERSION: 1;
5
+ export declare const iframeRuntimeBuildMetadataSchema: z.ZodObject<{
6
+ packageName: z.ZodString;
7
+ version: z.ZodString;
8
+ branch: z.ZodNullable<z.ZodString>;
9
+ commit: z.ZodNullable<z.ZodString>;
10
+ builtAt: z.ZodNullable<z.ZodString>;
11
+ }, z.core.$strip>;
5
12
  export declare const websiteBuilderToCanopyMethodConfig: {
6
13
  symbolicateSelectedElement: {
7
14
  inputSchema: z.ZodObject<{
@@ -280,7 +287,13 @@ export declare const canopyToWebsiteBuilderMethodConfig: {
280
287
  timeoutMs: number;
281
288
  };
282
289
  iframeReady: {
283
- inputSchema: z.ZodUndefined;
290
+ inputSchema: z.ZodOptional<z.ZodObject<{
291
+ packageName: z.ZodString;
292
+ version: z.ZodString;
293
+ branch: z.ZodNullable<z.ZodString>;
294
+ commit: z.ZodNullable<z.ZodString>;
295
+ builtAt: z.ZodNullable<z.ZodString>;
296
+ }, z.core.$strip>>;
284
297
  outputSchema: z.ZodVoid;
285
298
  };
286
299
  elementSelected: {
@@ -629,6 +642,13 @@ export declare const selectModeParentMessageSchema: z.ZodDiscriminatedUnion<[z.Z
629
642
  }, z.core.$strip>], "method">;
630
643
  export declare const selectModeIframeMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
631
644
  method: z.ZodLiteral<"iframeReady">;
645
+ data: z.ZodOptional<z.ZodObject<{
646
+ packageName: z.ZodString;
647
+ version: z.ZodString;
648
+ branch: z.ZodNullable<z.ZodString>;
649
+ commit: z.ZodNullable<z.ZodString>;
650
+ builtAt: z.ZodNullable<z.ZodString>;
651
+ }, z.core.$strip>>;
632
652
  }, z.core.$strip>, z.ZodObject<{
633
653
  method: z.ZodLiteral<"elementSelected">;
634
654
  data: z.ZodObject<{
@@ -5,6 +5,13 @@ import { z } from 'zod';
5
5
 
6
6
  var SELECT_MODE_IFRAME_RPC_CHANNEL_ID = "replo:select-mode-iframe-rpc";
7
7
  var SELECT_MODE_IFRAME_RPC_CHANNEL_VERSION = 1;
8
+ var iframeRuntimeBuildMetadataSchema = z.object({
9
+ packageName: z.string(),
10
+ version: z.string(),
11
+ branch: z.string().nullable(),
12
+ commit: z.string().nullable(),
13
+ builtAt: z.string().nullable()
14
+ }).describe("IframeRuntimeBuildMetadata");
8
15
  var websiteBuilderToCanopyMethodConfig = {
9
16
  symbolicateSelectedElement: {
10
17
  inputSchema: selectedElementSchema,
@@ -78,7 +85,7 @@ var canopyToWebsiteBuilderMethodConfig = {
78
85
  timeoutMs: 5e3
79
86
  },
80
87
  iframeReady: {
81
- inputSchema: z.undefined(),
88
+ inputSchema: iframeRuntimeBuildMetadataSchema.optional(),
82
89
  outputSchema: z.void()
83
90
  },
84
91
  elementSelected: {
@@ -181,7 +188,8 @@ var selectModeParentMessageSchema = z.discriminatedUnion("method", [
181
188
  ]).describe("SelectModeParentMessage");
182
189
  var selectModeIframeMessageSchema = z.discriminatedUnion("method", [
183
190
  z.object({
184
- method: z.literal("iframeReady")
191
+ method: z.literal("iframeReady"),
192
+ data: iframeRuntimeBuildMetadataSchema.optional()
185
193
  }),
186
194
  z.object({
187
195
  method: z.literal("elementSelected"),
@@ -220,4 +228,4 @@ var selectModeIframeMessageSchema = z.discriminatedUnion("method", [
220
228
  })
221
229
  ]).describe("SelectModeIframeMessage");
222
230
 
223
- export { SELECT_MODE_IFRAME_RPC_CHANNEL_ID, SELECT_MODE_IFRAME_RPC_CHANNEL_VERSION, canopyToWebsiteBuilderMethodConfig, selectModeIframeMessageSchema, selectModeParentMessageSchema, websiteBuilderToCanopyMethodConfig };
231
+ export { SELECT_MODE_IFRAME_RPC_CHANNEL_ID, SELECT_MODE_IFRAME_RPC_CHANNEL_VERSION, canopyToWebsiteBuilderMethodConfig, iframeRuntimeBuildMetadataSchema, selectModeIframeMessageSchema, selectModeParentMessageSchema, websiteBuilderToCanopyMethodConfig };
@@ -0,0 +1,9 @@
1
+ type BuildMetadata = {
2
+ packageName: string;
3
+ version: string;
4
+ branch: string | null;
5
+ commit: string | null;
6
+ builtAt: string | null;
7
+ };
8
+ export declare const BUILD_METADATA: BuildMetadata;
9
+ export {};
@@ -0,0 +1,7 @@
1
+ export const BUILD_METADATA = {
2
+ "packageName": "@replohq/sandbox-runtime",
3
+ "version": "1.0.1",
4
+ "branch": "HEAD",
5
+ "commit": "9fc57a3ef1d06689e70fe0d90c25c03717e37d03",
6
+ "builtAt": "2026-08-19T22:39:33.469Z"
7
+ };
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../buildMetadata.ts"],
4
+ "sourcesContent": ["type BuildMetadata = {\n packageName: string;\n version: string;\n branch: string | null;\n commit: string | null;\n builtAt: string | null;\n};\n\nexport const BUILD_METADATA: BuildMetadata = {\n packageName: \"@replohq/sandbox-runtime\",\n version: \"0.0.0-dev\",\n branch: null,\n commit: null,\n builtAt: null,\n};\n"],
5
+ "mappings": "AAQO,MAAM,iBAAgC;AAAA,EAC3C,aAAa;AAAA,EACb,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AACX;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,13 @@
1
1
  {
2
2
  "name": "@replohq/sandbox-runtime",
3
- "version": "0.3.0",
3
+ "version": "1.0.1",
4
+ "reploBuild": {
5
+ "packageName": "@replohq/sandbox-runtime",
6
+ "version": "1.0.1",
7
+ "branch": "HEAD",
8
+ "commit": "9fc57a3ef1d06689e70fe0d90c25c03717e37d03",
9
+ "builtAt": "2026-08-19T22:39:33.469Z"
10
+ },
4
11
  "description": "Replo sandbox runtime — editor integration for agent-built Next.js sites.",
5
12
  "license": "SEE LICENSE IN LICENSE",
6
13
  "type": "module",
@@ -33,11 +40,16 @@
33
40
  "zod": "^4.3.6"
34
41
  },
35
42
  "peerDependencies": {
36
- "@types/react": ">=18.2.0",
37
- "@types/react-dom": ">=18.2.0",
43
+ "@types/react": ">=19.0.0",
44
+ "@types/react-dom": ">=19.0.0",
38
45
  "next": ">=15.0.0",
39
- "react": ">=18.2.0",
40
- "react-dom": ">=18.2.0"
46
+ "react": ">=19.0.0",
47
+ "react-dom": ">=19.0.0"
48
+ },
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/replohq/andytown.git",
52
+ "directory": "packages/sandbox-runtime"
41
53
  },
42
54
  "publishConfig": {
43
55
  "registry": "https://registry.npmjs.org/",
@@ -1,6 +1,9 @@
1
1
  "use client";
2
2
  import { useCallback, useEffect } from "react";
3
- import { buildElementDescriptor } from "./descriptor/elementDescriptor";
3
+ import {
4
+ buildElementDescriptor,
5
+ buildSelector
6
+ } from "./descriptor/elementDescriptor";
4
7
  import { installFastRefreshRelay } from "./fast-refresh-relay";
5
8
  import { useCrosshairCursor } from "./hooks/useCrosshairCursor";
6
9
  import { useEditModeStateMachine } from "./hooks/useEditModeStateMachine";
@@ -10,6 +13,7 @@ import { useSelectionHover } from "./hooks/useSelectionHover";
10
13
  import { useSelectionInteractions } from "./hooks/useSelectionInteractions";
11
14
  import { useSelectionScrollSync } from "./hooks/useSelectionScrollSync";
12
15
  import { useSelectModeController } from "./hooks/useSelectModeController";
16
+ import { getRepetitionSiblingElements } from "./hooks/useSiblingResolution";
13
17
  import { getWebsiteBuilderRpc } from "./iframeRpcChannel";
14
18
  import { symbolicateSelectedElement } from "./symbolicate";
15
19
  function EditorSelection() {
@@ -40,10 +44,11 @@ function EditorSelection() {
40
44
  if (!(element instanceof Element) || !element.isConnected) {
41
45
  return;
42
46
  }
47
+ const siblingElements = getRepetitionSiblingElements({ element });
43
48
  const descriptor = buildElementDescriptor({
44
49
  element,
45
50
  clickPosition: session.clickPosition ?? (session.data.kind === "react-element" ? session.data.clickPosition : { x: 0, y: 0 }),
46
- siblingSelectors: session.data.kind === "react-element" ? session.data.siblingSelectors : []
51
+ siblingSelectors: siblingElements.map(buildSelector)
47
52
  });
48
53
  if (descriptor.kind !== "react-element") {
49
54
  return;
@@ -51,6 +56,7 @@ function EditorSelection() {
51
56
  interactionSessionRef.current = {
52
57
  ...session,
53
58
  element,
59
+ siblingElements,
54
60
  data: descriptor
55
61
  };
56
62
  void symbolicateSelectedElement(descriptor).then((symbolicated) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../select-mode/EditorSelection.tsx"],
4
- "sourcesContent": ["\"use client\";\n\nimport { useCallback, useEffect } from \"react\";\n\nimport { buildElementDescriptor } from \"./descriptor/elementDescriptor\";\nimport { installFastRefreshRelay } from \"./fast-refresh-relay\";\nimport { useCrosshairCursor } from \"./hooks/useCrosshairCursor\";\nimport { useEditModeStateMachine } from \"./hooks/useEditModeStateMachine\";\nimport { useOptimisticOverrides } from \"./hooks/useOptimisticOverrides\";\nimport { useParentMessaging } from \"./hooks/useParentMessaging\";\nimport { useSelectionHover } from \"./hooks/useSelectionHover\";\nimport { useSelectionInteractions } from \"./hooks/useSelectionInteractions\";\nimport { useSelectionScrollSync } from \"./hooks/useSelectionScrollSync\";\nimport { useSelectModeController } from \"./hooks/useSelectModeController\";\nimport { getWebsiteBuilderRpc } from \"./iframeRpcChannel\";\nimport { symbolicateSelectedElement } from \"./symbolicate\";\n\n/**\n * Mounts the iframe-side selectMode controller. This is a render-less\n * component \u2014 every responsibility is delegated to a dedicated hook so each\n * concern (parent messaging, cursor styling, pointer interactions, hover\n * tracking, scroll syncing) can be read in isolation:\n *\n * - `useParentMessaging` \u2014 inbound messages from the parent (set mode,\n * set selection type) + outbound `IFRAME_READY` handshake.\n * - `useCrosshairCursor` \u2014 crosshair cursor + disable text/drag selection\n * while in selection mode.\n * - `useSelectionInteractions` \u2014 pointer-down / move / up state machine\n * that produces `ELEMENT_SELECTED`, `AREA_SELECTED`, and live\n * `DRAG_RECT_UPDATED` messages.\n * - `useSelectionHover` \u2014 hover-element rect updates.\n * - `useSelectionScrollSync` \u2014 keeps the parent overlay aligned with the\n * selected element/area when the iframe scrolls.\n *\n * The `interactionSessionRef` is the single source of truth for the\n * in-progress drag/selection \u2014 every hook reads/writes it via ref so we\n * don't trigger React re-renders during interaction-heavy operations.\n *\n * @author Kevin O'Connell 2026-04-17\n */\nexport function EditorSelection() {\n const {\n displayMode,\n interactionSessionRef,\n setDisplayMode,\n setSelectionType,\n } = useSelectModeController();\n const { beginEditing, editingElementRef, formatCommand, revert } =\n useEditModeStateMachine({\n displayMode,\n interactionSessionRef,\n });\n const {\n applyOptimisticUpdate,\n markCommitted,\n reapplyOverrides,\n resolveSessionOverrides,\n revertOverride,\n } = useOptimisticOverrides();\n\n const postSelectedElementSnapshot = useCallback(() => {\n const session = interactionSessionRef.current;\n if (session.type !== \"element\") {\n return;\n }\n\n const selectedElement =\n session.data.kind === \"react-element\"\n ? document.querySelector(session.data.selector)\n : null;\n const element = selectedElement ?? session.element;\n if (!(element instanceof Element) || !element.isConnected) {\n return;\n }\n\n const descriptor = buildElementDescriptor({\n element,\n clickPosition:\n session.clickPosition ??\n (session.data.kind === \"react-element\"\n ? session.data.clickPosition\n : { x: 0, y: 0 }),\n siblingSelectors:\n session.data.kind === \"react-element\"\n ? session.data.siblingSelectors\n : [],\n });\n if (descriptor.kind !== \"react-element\") {\n return;\n }\n\n interactionSessionRef.current = {\n ...session,\n element,\n data: descriptor,\n };\n\n void symbolicateSelectedElement(descriptor).then((symbolicated) => {\n getWebsiteBuilderRpc()?.elementSelected({ data: symbolicated });\n });\n }, [interactionSessionRef]);\n\n // eslint-disable-next-line replo/no-use-effect -- Installs a window-level console bridge for the preview iframe.\n useEffect(() => {\n installFastRefreshRelay({\n onComplete: () => {\n // Re-apply before re-symbolicating so the snapshot reads the\n // optimistic content, not the source the refresh just repainted.\n reapplyOverrides({ skipElement: editingElementRef.current });\n postSelectedElementSnapshot();\n },\n });\n }, [editingElementRef, postSelectedElementSnapshot, reapplyOverrides]);\n\n useParentMessaging({\n applyOptimisticUpdate,\n markCommitted,\n onExitEditMode: revert,\n onFormatCommand: formatCommand,\n resolveSessionOverrides,\n revertOverride,\n setDisplayMode,\n setSelectionType,\n });\n useCrosshairCursor({ displayMode });\n useSelectionInteractions({\n displayMode,\n editingElementRef,\n interactionSessionRef,\n onBeginEditing: beginEditing,\n });\n useSelectionHover({ displayMode, interactionSessionRef });\n useSelectionScrollSync({\n displayMode,\n interactionSessionRef,\n });\n\n return null;\n}\n"],
5
- "mappings": ";AAEA,SAAS,aAAa,iBAAiB;AAEvC,SAAS,8BAA8B;AACvC,SAAS,+BAA+B;AACxC,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,yBAAyB;AAClC,SAAS,gCAAgC;AACzC,SAAS,8BAA8B;AACvC,SAAS,+BAA+B;AACxC,SAAS,4BAA4B;AACrC,SAAS,kCAAkC;AAyBpC,SAAS,kBAAkB;AAChC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,wBAAwB;AAC5B,QAAM,EAAE,cAAc,mBAAmB,eAAe,OAAO,IAC7D,wBAAwB;AAAA,IACtB;AAAA,IACA;AAAA,EACF,CAAC;AACH,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,uBAAuB;AAE3B,QAAM,8BAA8B,YAAY,MAAM;AACpD,UAAM,UAAU,sBAAsB;AACtC,QAAI,QAAQ,SAAS,WAAW;AAC9B;AAAA,IACF;AAEA,UAAM,kBACJ,QAAQ,KAAK,SAAS,kBAClB,SAAS,cAAc,QAAQ,KAAK,QAAQ,IAC5C;AACN,UAAM,UAAU,mBAAmB,QAAQ;AAC3C,QAAI,EAAE,mBAAmB,YAAY,CAAC,QAAQ,aAAa;AACzD;AAAA,IACF;AAEA,UAAM,aAAa,uBAAuB;AAAA,MACxC;AAAA,MACA,eACE,QAAQ,kBACP,QAAQ,KAAK,SAAS,kBACnB,QAAQ,KAAK,gBACb,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,MACnB,kBACE,QAAQ,KAAK,SAAS,kBAClB,QAAQ,KAAK,mBACb,CAAC;AAAA,IACT,CAAC;AACD,QAAI,WAAW,SAAS,iBAAiB;AACvC;AAAA,IACF;AAEA,0BAAsB,UAAU;AAAA,MAC9B,GAAG;AAAA,MACH;AAAA,MACA,MAAM;AAAA,IACR;AAEA,SAAK,2BAA2B,UAAU,EAAE,KAAK,CAAC,iBAAiB;AACjE,2BAAqB,GAAG,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAAA,IAChE,CAAC;AAAA,EACH,GAAG,CAAC,qBAAqB,CAAC;AAG1B,YAAU,MAAM;AACd,4BAAwB;AAAA,MACtB,YAAY,MAAM;AAGhB,yBAAiB,EAAE,aAAa,kBAAkB,QAAQ,CAAC;AAC3D,oCAA4B;AAAA,MAC9B;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,mBAAmB,6BAA6B,gBAAgB,CAAC;AAErE,qBAAmB;AAAA,IACjB;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,qBAAmB,EAAE,YAAY,CAAC;AAClC,2BAAyB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,EAClB,CAAC;AACD,oBAAkB,EAAE,aAAa,sBAAsB,CAAC;AACxD,yBAAuB;AAAA,IACrB;AAAA,IACA;AAAA,EACF,CAAC;AAED,SAAO;AACT;",
4
+ "sourcesContent": ["\"use client\";\n\nimport { useCallback, useEffect } from \"react\";\n\nimport {\n buildElementDescriptor,\n buildSelector,\n} from \"./descriptor/elementDescriptor\";\nimport { installFastRefreshRelay } from \"./fast-refresh-relay\";\nimport { useCrosshairCursor } from \"./hooks/useCrosshairCursor\";\nimport { useEditModeStateMachine } from \"./hooks/useEditModeStateMachine\";\nimport { useOptimisticOverrides } from \"./hooks/useOptimisticOverrides\";\nimport { useParentMessaging } from \"./hooks/useParentMessaging\";\nimport { useSelectionHover } from \"./hooks/useSelectionHover\";\nimport { useSelectionInteractions } from \"./hooks/useSelectionInteractions\";\nimport { useSelectionScrollSync } from \"./hooks/useSelectionScrollSync\";\nimport { useSelectModeController } from \"./hooks/useSelectModeController\";\nimport { getRepetitionSiblingElements } from \"./hooks/useSiblingResolution\";\nimport { getWebsiteBuilderRpc } from \"./iframeRpcChannel\";\nimport { symbolicateSelectedElement } from \"./symbolicate\";\n\n/**\n * Mounts the iframe-side selectMode controller. This is a render-less\n * component \u2014 every responsibility is delegated to a dedicated hook so each\n * concern (parent messaging, cursor styling, pointer interactions, hover\n * tracking, scroll syncing) can be read in isolation:\n *\n * - `useParentMessaging` \u2014 inbound messages from the parent (set mode,\n * set selection type) + outbound `IFRAME_READY` handshake.\n * - `useCrosshairCursor` \u2014 crosshair cursor + disable text/drag selection\n * while in selection mode.\n * - `useSelectionInteractions` \u2014 pointer-down / move / up state machine\n * that produces `ELEMENT_SELECTED`, `AREA_SELECTED`, and live\n * `DRAG_RECT_UPDATED` messages.\n * - `useSelectionHover` \u2014 hover-element rect updates.\n * - `useSelectionScrollSync` \u2014 keeps the parent overlay aligned with the\n * selected element/area when the iframe scrolls.\n *\n * The `interactionSessionRef` is the single source of truth for the\n * in-progress drag/selection \u2014 every hook reads/writes it via ref so we\n * don't trigger React re-renders during interaction-heavy operations.\n *\n * @author Kevin O'Connell 2026-04-17\n */\nexport function EditorSelection() {\n const {\n displayMode,\n interactionSessionRef,\n setDisplayMode,\n setSelectionType,\n } = useSelectModeController();\n const { beginEditing, editingElementRef, formatCommand, revert } =\n useEditModeStateMachine({\n displayMode,\n interactionSessionRef,\n });\n const {\n applyOptimisticUpdate,\n markCommitted,\n reapplyOverrides,\n resolveSessionOverrides,\n revertOverride,\n } = useOptimisticOverrides();\n\n const postSelectedElementSnapshot = useCallback(() => {\n const session = interactionSessionRef.current;\n if (session.type !== \"element\") {\n return;\n }\n\n const selectedElement =\n session.data.kind === \"react-element\"\n ? document.querySelector(session.data.selector)\n : null;\n const element = selectedElement ?? session.element;\n if (!(element instanceof Element) || !element.isConnected) {\n return;\n }\n\n // A fast refresh can replace the repeated DOM nodes, so the group is\n // re-resolved from the (possibly re-created) element rather than carried\n // over from the stale session.\n const siblingElements = getRepetitionSiblingElements({ element });\n const descriptor = buildElementDescriptor({\n element,\n clickPosition:\n session.clickPosition ??\n (session.data.kind === \"react-element\"\n ? session.data.clickPosition\n : { x: 0, y: 0 }),\n siblingSelectors: siblingElements.map(buildSelector),\n });\n if (descriptor.kind !== \"react-element\") {\n return;\n }\n\n interactionSessionRef.current = {\n ...session,\n element,\n siblingElements,\n data: descriptor,\n };\n\n void symbolicateSelectedElement(descriptor).then((symbolicated) => {\n getWebsiteBuilderRpc()?.elementSelected({ data: symbolicated });\n });\n }, [interactionSessionRef]);\n\n // eslint-disable-next-line replo/no-use-effect -- Installs a window-level console bridge for the preview iframe.\n useEffect(() => {\n installFastRefreshRelay({\n onComplete: () => {\n // Re-apply before re-symbolicating so the snapshot reads the\n // optimistic content, not the source the refresh just repainted.\n reapplyOverrides({ skipElement: editingElementRef.current });\n postSelectedElementSnapshot();\n },\n });\n }, [editingElementRef, postSelectedElementSnapshot, reapplyOverrides]);\n\n useParentMessaging({\n applyOptimisticUpdate,\n markCommitted,\n onExitEditMode: revert,\n onFormatCommand: formatCommand,\n resolveSessionOverrides,\n revertOverride,\n setDisplayMode,\n setSelectionType,\n });\n useCrosshairCursor({ displayMode });\n useSelectionInteractions({\n displayMode,\n editingElementRef,\n interactionSessionRef,\n onBeginEditing: beginEditing,\n });\n useSelectionHover({ displayMode, interactionSessionRef });\n useSelectionScrollSync({\n displayMode,\n interactionSessionRef,\n });\n\n return null;\n}\n"],
5
+ "mappings": ";AAEA,SAAS,aAAa,iBAAiB;AAEvC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,+BAA+B;AACxC,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,yBAAyB;AAClC,SAAS,gCAAgC;AACzC,SAAS,8BAA8B;AACvC,SAAS,+BAA+B;AACxC,SAAS,oCAAoC;AAC7C,SAAS,4BAA4B;AACrC,SAAS,kCAAkC;AAyBpC,SAAS,kBAAkB;AAChC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,wBAAwB;AAC5B,QAAM,EAAE,cAAc,mBAAmB,eAAe,OAAO,IAC7D,wBAAwB;AAAA,IACtB;AAAA,IACA;AAAA,EACF,CAAC;AACH,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,uBAAuB;AAE3B,QAAM,8BAA8B,YAAY,MAAM;AACpD,UAAM,UAAU,sBAAsB;AACtC,QAAI,QAAQ,SAAS,WAAW;AAC9B;AAAA,IACF;AAEA,UAAM,kBACJ,QAAQ,KAAK,SAAS,kBAClB,SAAS,cAAc,QAAQ,KAAK,QAAQ,IAC5C;AACN,UAAM,UAAU,mBAAmB,QAAQ;AAC3C,QAAI,EAAE,mBAAmB,YAAY,CAAC,QAAQ,aAAa;AACzD;AAAA,IACF;AAKA,UAAM,kBAAkB,6BAA6B,EAAE,QAAQ,CAAC;AAChE,UAAM,aAAa,uBAAuB;AAAA,MACxC;AAAA,MACA,eACE,QAAQ,kBACP,QAAQ,KAAK,SAAS,kBACnB,QAAQ,KAAK,gBACb,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,MACnB,kBAAkB,gBAAgB,IAAI,aAAa;AAAA,IACrD,CAAC;AACD,QAAI,WAAW,SAAS,iBAAiB;AACvC;AAAA,IACF;AAEA,0BAAsB,UAAU;AAAA,MAC9B,GAAG;AAAA,MACH;AAAA,MACA;AAAA,MACA,MAAM;AAAA,IACR;AAEA,SAAK,2BAA2B,UAAU,EAAE,KAAK,CAAC,iBAAiB;AACjE,2BAAqB,GAAG,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAAA,IAChE,CAAC;AAAA,EACH,GAAG,CAAC,qBAAqB,CAAC;AAG1B,YAAU,MAAM;AACd,4BAAwB;AAAA,MACtB,YAAY,MAAM;AAGhB,yBAAiB,EAAE,aAAa,kBAAkB,QAAQ,CAAC;AAC3D,oCAA4B;AAAA,MAC9B;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,mBAAmB,6BAA6B,gBAAgB,CAAC;AAErE,qBAAmB;AAAA,IACjB;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,qBAAmB,EAAE,YAAY,CAAC;AAClC,2BAAyB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,EAClB,CAAC;AACD,oBAAkB,EAAE,aAAa,sBAAsB,CAAC;AACxD,yBAAuB;AAAA,IACrB;AAAA,IACA;AAAA,EACF,CAAC;AAED,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,9 @@
1
+ export interface DebugSource {
2
+ fileName: string;
3
+ lineNumber: number;
4
+ columnNumber?: number;
5
+ }
1
6
  export interface ReactFiber {
2
- _debugSource?: {
3
- fileName: string;
4
- lineNumber: number;
5
- columnNumber?: number;
6
- };
7
7
  _debugStack?: unknown;
8
8
  _debugOwner?: ReactFiber;
9
9
  type?: {
@@ -11,17 +11,17 @@ export interface ReactFiber {
11
11
  displayName?: string;
12
12
  } | string | ((...args: unknown[]) => unknown) | null;
13
13
  return?: ReactFiber | null;
14
+ child?: ReactFiber | null;
15
+ sibling?: ReactFiber | null;
16
+ alternate?: ReactFiber | null;
17
+ stateNode?: unknown;
14
18
  memoizedProps?: Record<string, unknown>;
15
19
  pendingProps?: Record<string, unknown>;
16
20
  }
17
21
  export declare function getFiberFromElement(element: HTMLElement): ReactFiber | null;
18
22
  export declare function getComponentName(fiber: ReactFiber | null | undefined): string | undefined;
19
- export declare function findDebugSource(fiber: ReactFiber, maxDepth?: number): {
20
- source: ReactFiber["_debugSource"];
21
- componentName?: string;
22
- } | null;
23
- export declare function findDebugSourceReact19(fiber: ReactFiber): {
24
- source: ReactFiber["_debugSource"];
23
+ export declare function findDebugSource(fiber: ReactFiber): {
24
+ source: DebugSource;
25
25
  componentName?: string;
26
26
  } | null;
27
27
  export declare function getSourceLocation(element: HTMLElement): {
@@ -30,12 +30,7 @@ export declare function getSourceLocation(element: HTMLElement): {
30
30
  columnNumber: number | undefined;
31
31
  componentName: string | undefined;
32
32
  } | undefined;
33
- export declare function getHostSourceIdentity(element: HTMLElement): {
34
- filePath: string;
35
- lineNumber: number;
36
- columnNumber: number | undefined;
37
- tagName: string;
38
- } | null;
33
+ export declare function getRepetitionHostNodes(element: HTMLElement): HTMLElement[];
39
34
  export declare function getFiberChain(element: HTMLElement): {
40
35
  sourceLocation: {
41
36
  filePath: string;
@@ -2,6 +2,10 @@ import { z } from "zod";
2
2
  const MAX_FIBER_CHAIN_DEPTH = 80;
3
3
  const MAX_SERIALIZED_PROP_COUNT = 24;
4
4
  const MAX_SERIALIZED_STRING_LENGTH = 500;
5
+ const MAX_SIBLING_SCAN = 5e3;
6
+ const MAX_SUBTREE_SCAN = 5e3;
7
+ const MAX_ROOT_CLIMB_DEPTH = 1e3;
8
+ const MAX_CURRENT_TREE_SCAN = 5e4;
5
9
  const REACT_INTERNAL_FRAME_PATTERNS = [
6
10
  /react.*jsx-dev-runtime/,
7
11
  /react.*jsx-runtime/,
@@ -20,9 +24,9 @@ function isReactInternalFrame(fileName) {
20
24
  const REACT_INTERNAL_METHOD_PATTERNS = [
21
25
  /(?:^|\.)jsxDEV(?:Impl)?$/,
22
26
  /(?:^|\.)jsxs?$/,
23
- /(?:^|\.)react_stack_bottom_frame$/,
24
27
  /(?:^|\.)fakeJSXCallSite$/
25
28
  ];
29
+ const REACT_STACK_BOTTOM_FRAME_PATTERN = /react[-_]stack[-_]bottom[-_]frame/;
26
30
  function isReactInternalMethod(methodName) {
27
31
  if (!methodName) {
28
32
  return false;
@@ -49,7 +53,12 @@ function parseDebugStackSource(debugStack) {
49
53
  if (!line) {
50
54
  continue;
51
55
  }
52
- const framedMatch = line.match(/^at\s+(.+?)\s+\((.*):(\d+):(\d+)\)$/);
56
+ if (REACT_STACK_BOTTOM_FRAME_PATTERN.test(line)) {
57
+ return null;
58
+ }
59
+ const framedMatch = line.match(/^at\s+(.+?)\s+\((.*):(\d+):(\d+)\)$/) ?? // Method names never contain `/`, which keeps `@`-versioned path
60
+ // segments (`.pnpm/react@19.2.4/…`) from being read as a method.
61
+ line.match(/^([^@/]*)@(.+):(\d+):(\d+)$/);
53
62
  const stackFrameMatch = framedMatch ?? line.match(/^at\s+(?:.*\()?(.*):(\d+):(\d+)\)?$/) ?? line.match(/^(.*):(\d+):(\d+)$/);
54
63
  if (!stackFrameMatch) {
55
64
  continue;
@@ -99,30 +108,6 @@ function getFiberFromElement(element) {
99
108
  if (fiberKey) {
100
109
  return element[fiberKey] ?? null;
101
110
  }
102
- const instanceKey = keys.find((key) => {
103
- return key.startsWith("__reactInternalInstance$");
104
- });
105
- if (instanceKey) {
106
- return (
107
- // Legacy cast - may or may not be necessary
108
- // eslint-disable-next-line replo/no-as-cast
109
- element[instanceKey] ?? null
110
- );
111
- }
112
- const possibleFiberKey = keys.find((key) => {
113
- if (!key.startsWith("__react")) {
114
- return false;
115
- }
116
- const value = element[key];
117
- return value && typeof value === "object" && "_debugSource" in value;
118
- });
119
- if (possibleFiberKey) {
120
- return (
121
- // Legacy cast - may or may not be necessary
122
- // eslint-disable-next-line replo/no-as-cast
123
- element[possibleFiberKey] ?? null
124
- );
125
- }
126
111
  return null;
127
112
  }
128
113
  function getComponentName(fiber) {
@@ -140,28 +125,7 @@ function getComponentName(fiber) {
140
125
  }
141
126
  return void 0;
142
127
  }
143
- function findDebugSource(fiber, maxDepth = 50) {
144
- let current = fiber;
145
- let depth = 0;
146
- while (current && depth < maxDepth) {
147
- if (current._debugSource) {
148
- return {
149
- source: current._debugSource,
150
- componentName: getComponentName(current)
151
- };
152
- }
153
- if (current._debugOwner?._debugSource) {
154
- return {
155
- source: current._debugOwner._debugSource,
156
- componentName: getComponentName(current._debugOwner)
157
- };
158
- }
159
- current = current.return;
160
- depth++;
161
- }
162
- return null;
163
- }
164
- function findDebugSourceReact19(fiber) {
128
+ function findDebugSource(fiber) {
165
129
  let current = fiber;
166
130
  let depth = 0;
167
131
  const maxDepth = 50;
@@ -183,7 +147,7 @@ function getSourceLocation(element) {
183
147
  while (currentElement) {
184
148
  const fiber = getFiberFromElement(currentElement);
185
149
  if (fiber) {
186
- const debugInfo = findDebugSource(fiber) ?? findDebugSourceReact19(fiber);
150
+ const debugInfo = findDebugSource(fiber);
187
151
  if (debugInfo?.source) {
188
152
  return {
189
153
  filePath: debugInfo.source.fileName,
@@ -198,27 +162,228 @@ function getSourceLocation(element) {
198
162
  return void 0;
199
163
  }
200
164
  function getDebugSource(fiber) {
201
- const debugInfo = findDebugSource(fiber) ?? findDebugSourceReact19(fiber);
202
- return debugInfo?.source;
165
+ return findDebugSource(fiber)?.source;
203
166
  }
204
167
  function getTagName(fiber) {
205
168
  return z.string().safeParse(fiber.type).data?.toLowerCase();
206
169
  }
207
- function getHostSourceIdentity(element) {
208
- const fiber = getFiberFromElement(element);
209
- if (!fiber) {
210
- return null;
170
+ const fiberSourceKeyCache = /* @__PURE__ */ new WeakMap();
171
+ function getFiberSourceKey(fiber) {
172
+ const cached = fiberSourceKeyCache.get(fiber);
173
+ if (cached !== void 0) {
174
+ return cached;
211
175
  }
212
176
  const source = getDebugSource(fiber);
213
- if (!source) {
177
+ const key = source ? `${source.fileName}:${source.lineNumber}:${source.columnNumber ?? ""}` : null;
178
+ fiberSourceKeyCache.set(fiber, key);
179
+ return key;
180
+ }
181
+ function findRepetitionGroup(fiber) {
182
+ const parent = fiber.return;
183
+ if (!parent) {
184
+ return null;
185
+ }
186
+ const type = fiber.type;
187
+ const candidates = [];
188
+ let child = parent.child;
189
+ let scanned = 0;
190
+ while (child && scanned < MAX_SIBLING_SCAN) {
191
+ if (child.type === type) {
192
+ candidates.push(child);
193
+ }
194
+ child = child.sibling;
195
+ scanned += 1;
196
+ }
197
+ if (candidates.length < 2) {
198
+ return null;
199
+ }
200
+ const sourceKey = getFiberSourceKey(fiber);
201
+ if (!sourceKey) {
202
+ return null;
203
+ }
204
+ const members = candidates.filter((candidate) => {
205
+ return getFiberSourceKey(candidate) === sourceKey;
206
+ });
207
+ return members.length >= 2 ? members : null;
208
+ }
209
+ function descendChildPath(fiber, path) {
210
+ let current = fiber;
211
+ for (const childIndex of path) {
212
+ if (!current) {
213
+ return null;
214
+ }
215
+ let child = current.child;
216
+ let index = 0;
217
+ while (child && index < childIndex) {
218
+ child = child.sibling;
219
+ index += 1;
220
+ }
221
+ if (!child) {
222
+ return null;
223
+ }
224
+ current = child;
225
+ }
226
+ return current ?? null;
227
+ }
228
+ function getHostNode(fiber) {
229
+ return fiber?.stateNode instanceof HTMLElement ? fiber.stateNode : null;
230
+ }
231
+ function isMatchingHost(fiber, type, sourceKey) {
232
+ if (!fiber || !(fiber.stateNode instanceof HTMLElement)) {
233
+ return false;
234
+ }
235
+ return fiber.type === type && getFiberSourceKey(fiber) === sourceKey;
236
+ }
237
+ function findHostInSubtree(root, type, sourceKey) {
238
+ const stack = [];
239
+ let child = root.child;
240
+ while (child) {
241
+ stack.push(child);
242
+ child = child.sibling;
243
+ }
244
+ let scanned = 0;
245
+ while (stack.length > 0 && scanned < MAX_SUBTREE_SCAN) {
246
+ const current = stack.pop();
247
+ scanned += 1;
248
+ if (!current) {
249
+ continue;
250
+ }
251
+ if (isMatchingHost(current, type, sourceKey)) {
252
+ return getHostNode(current);
253
+ }
254
+ let next = current.child;
255
+ while (next) {
256
+ stack.push(next);
257
+ next = next.sibling;
258
+ }
259
+ }
260
+ return null;
261
+ }
262
+ function resolveCurrentFiberByParentLinkage(fiber) {
263
+ const alternate = fiber.alternate;
264
+ if (!alternate) {
265
+ return fiber;
266
+ }
267
+ let child = fiber.return?.child;
268
+ let scanned = 0;
269
+ while (child && scanned < MAX_SIBLING_SCAN) {
270
+ if (child === fiber) {
271
+ return fiber;
272
+ }
273
+ child = child.sibling;
274
+ scanned += 1;
275
+ }
276
+ return alternate;
277
+ }
278
+ function getFiberRootCurrent(stateNode) {
279
+ if (!stateNode || typeof stateNode !== "object" || !("current" in stateNode)) {
214
280
  return null;
215
281
  }
216
- return {
217
- filePath: source.fileName,
218
- lineNumber: source.lineNumber,
219
- columnNumber: source.columnNumber,
220
- tagName: element.tagName.toLowerCase()
221
- };
282
+ const current = Reflect.get(stateNode, "current");
283
+ if (current && typeof current === "object" && "child" in current) {
284
+ return current;
285
+ }
286
+ return null;
287
+ }
288
+ function resolveCurrentFiber(fiber) {
289
+ const alternate = fiber.alternate;
290
+ if (!alternate) {
291
+ return fiber;
292
+ }
293
+ let rootFiber = fiber;
294
+ let climbed = 0;
295
+ while (rootFiber.return && climbed < MAX_ROOT_CLIMB_DEPTH) {
296
+ rootFiber = rootFiber.return;
297
+ climbed += 1;
298
+ }
299
+ const currentRoot = rootFiber.return ? null : getFiberRootCurrent(rootFiber.stateNode);
300
+ if (!currentRoot) {
301
+ return resolveCurrentFiberByParentLinkage(fiber);
302
+ }
303
+ const stack = [currentRoot];
304
+ let scanned = 0;
305
+ while (stack.length > 0 && scanned < MAX_CURRENT_TREE_SCAN) {
306
+ const node = stack.pop();
307
+ scanned += 1;
308
+ if (!node) {
309
+ continue;
310
+ }
311
+ if (node === fiber || node === alternate) {
312
+ return node;
313
+ }
314
+ if (node.child) {
315
+ stack.push(node.child);
316
+ }
317
+ if (node.sibling) {
318
+ stack.push(node.sibling);
319
+ }
320
+ }
321
+ if (scanned >= MAX_CURRENT_TREE_SCAN) {
322
+ return resolveCurrentFiberByParentLinkage(fiber);
323
+ }
324
+ return fiber;
325
+ }
326
+ function getRepetitionHostNodes(element) {
327
+ const cachedFiber = getFiberFromElement(element);
328
+ if (!cachedFiber) {
329
+ return [element];
330
+ }
331
+ const selectedFiber = resolveCurrentFiber(cachedFiber);
332
+ const climb = [];
333
+ let cursor = selectedFiber;
334
+ let depth = 0;
335
+ let unit = null;
336
+ let group = null;
337
+ while (cursor && depth < MAX_FIBER_CHAIN_DEPTH) {
338
+ climb.push(cursor);
339
+ const candidate = findRepetitionGroup(cursor);
340
+ if (candidate) {
341
+ unit = cursor;
342
+ group = candidate;
343
+ break;
344
+ }
345
+ cursor = cursor.return;
346
+ depth += 1;
347
+ }
348
+ if (!unit || !group) {
349
+ return [element];
350
+ }
351
+ const downward = climb.slice(0, climb.indexOf(unit) + 1).reverse();
352
+ let pathFromUnit = [];
353
+ for (let i = 0; i < downward.length - 1 && pathFromUnit; i += 1) {
354
+ const parentFiber = downward[i];
355
+ const targetFiber = downward[i + 1];
356
+ if (!parentFiber || !targetFiber) {
357
+ break;
358
+ }
359
+ let child = parentFiber.child;
360
+ let index = 0;
361
+ while (child && child !== targetFiber && child !== targetFiber.alternate) {
362
+ child = child.sibling;
363
+ index += 1;
364
+ }
365
+ if (child) {
366
+ pathFromUnit.push(index);
367
+ } else {
368
+ pathFromUnit = null;
369
+ }
370
+ }
371
+ const selectedType = selectedFiber.type;
372
+ const selectedSourceKey = getFiberSourceKey(selectedFiber);
373
+ const nodes = [];
374
+ const seen = /* @__PURE__ */ new Set();
375
+ for (const member of group) {
376
+ const landed = pathFromUnit ? descendChildPath(member, pathFromUnit) : null;
377
+ const node = isMatchingHost(landed, selectedType, selectedSourceKey) ? getHostNode(landed) : findHostInSubtree(member, selectedType, selectedSourceKey);
378
+ if (node && !seen.has(node)) {
379
+ seen.add(node);
380
+ nodes.push(node);
381
+ }
382
+ }
383
+ if (!seen.has(element)) {
384
+ nodes.push(element);
385
+ }
386
+ return nodes;
222
387
  }
223
388
  function getSerializablePropValue(value) {
224
389
  if (value === null || typeof value === "number" || typeof value === "boolean") {
@@ -296,11 +461,10 @@ function getFiberChain(element) {
296
461
  }
297
462
  export {
298
463
  findDebugSource,
299
- findDebugSourceReact19,
300
464
  getComponentName,
301
465
  getFiberChain,
302
466
  getFiberFromElement,
303
- getHostSourceIdentity,
467
+ getRepetitionHostNodes,
304
468
  getSourceLocation
305
469
  };
306
470
  //# sourceMappingURL=fiber.js.map