nudge-ui 0.2.4 → 0.2.5

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 (250) hide show
  1. package/dist/cli/agentCommand.js +4 -28
  2. package/dist/client.mjs +863 -213
  3. package/dist/hosts/next/wrapper.js +1 -7
  4. package/dist/hosts/projectBridge.js +3 -17
  5. package/dist/inspector/agent/McpConnectionDialog.js +7 -3
  6. package/dist/inspector/agent/activity.d.ts +1 -1
  7. package/dist/inspector/agent/activity.js +3 -2
  8. package/dist/inspector/agent/client.d.ts +2 -2
  9. package/dist/inspector/agent/client.js +28 -29
  10. package/dist/inspector/agent/connectionStatus.d.ts +3 -1
  11. package/dist/inspector/agent/connectionStatus.js +25 -0
  12. package/dist/inspector/agent/httpTransport.js +31 -147
  13. package/dist/inspector/agent/protocol.d.ts +1 -1
  14. package/dist/inspector/agent/protocol.js +1 -1
  15. package/dist/inspector/agent/verification.d.ts +1 -1
  16. package/dist/inspector/agent/verification.js +7 -4
  17. package/dist/inspector/boot.d.ts +3 -0
  18. package/dist/inspector/boot.js +297 -0
  19. package/dist/inspector/canvas/AgentActivityOverlay.d.ts +2 -1
  20. package/dist/inspector/canvas/AgentActivityOverlay.js +6 -3
  21. package/dist/inspector/canvas/CanvasCard.js +21 -12
  22. package/dist/inspector/canvas/CanvasElementOverlay.js +82 -127
  23. package/dist/inspector/canvas/CanvasToolbar.js +2 -1
  24. package/dist/inspector/canvas/CanvasWorkspace.js +74 -97
  25. package/dist/inspector/canvas/LockedWorkspaceNotice.d.ts +3 -2
  26. package/dist/inspector/canvas/LockedWorkspaceNotice.js +10 -6
  27. package/dist/inspector/canvas/PageCard.d.ts +10 -0
  28. package/dist/inspector/canvas/PageCard.js +51 -0
  29. package/dist/inspector/canvas/SketchToolOptions.d.ts +5 -0
  30. package/dist/inspector/canvas/SketchToolOptions.js +86 -0
  31. package/dist/inspector/canvas/StaleChangeIndicator.d.ts +1 -1
  32. package/dist/inspector/canvas/StaleChangeIndicator.js +1 -1
  33. package/dist/inspector/canvas/ToolOptionsDrawer.d.ts +21 -0
  34. package/dist/inspector/canvas/ToolOptionsDrawer.js +77 -0
  35. package/dist/inspector/canvas/activeCanvasDocument.d.ts +3 -2
  36. package/dist/inspector/canvas/assets/eraser.svg +28 -0
  37. package/dist/inspector/canvas/assets/pen.svg +25 -0
  38. package/dist/inspector/canvas/canvasFrame.d.ts +16 -0
  39. package/dist/inspector/canvas/canvasFrame.js +10 -0
  40. package/dist/inspector/canvas/canvasGestures.d.ts +0 -1
  41. package/dist/inspector/canvas/canvasGestures.js +0 -6
  42. package/dist/inspector/canvas/canvasStore.d.ts +6 -0
  43. package/dist/inspector/canvas/canvasStore.js +36 -1
  44. package/dist/inspector/canvas/frameConnection.d.ts +48 -0
  45. package/dist/inspector/canvas/frameConnection.js +73 -0
  46. package/dist/inspector/canvas/frameProtocol.d.ts +18 -1
  47. package/dist/inspector/canvas/frameProtocol.js +36 -0
  48. package/dist/inspector/canvas/pageSurface.d.ts +11 -0
  49. package/dist/inspector/canvas/pageSurface.js +34 -0
  50. package/dist/inspector/canvas/projection.d.ts +19 -9
  51. package/dist/inspector/canvas/projection.js +58 -31
  52. package/dist/inspector/canvas/rendererBootstrap.js +4 -1
  53. package/dist/inspector/canvas/rendererElementSelector.js +9 -1
  54. package/dist/inspector/canvas/rendererMessageRouter.d.ts +4 -5
  55. package/dist/inspector/canvas/rendererMessageRouter.js +12 -16
  56. package/dist/inspector/canvas/rendererSelectionProxy.d.ts +7 -1
  57. package/dist/inspector/canvas/rendererSelectionProxy.js +8 -36
  58. package/dist/inspector/canvas/rendererStylesheet.js +15 -0
  59. package/dist/inspector/canvas/sessionStore.d.ts +2 -2
  60. package/dist/inspector/canvas/sessionStore.js +12 -9
  61. package/dist/inspector/canvas/staleChangeDetector.d.ts +1 -1
  62. package/dist/inspector/canvas/staleChangeDetector.js +10 -22
  63. package/dist/inspector/changes/codecs.d.ts +1 -0
  64. package/dist/inspector/changes/codecs.js +20 -5
  65. package/dist/inspector/changes/draftPreview.d.ts +12 -0
  66. package/dist/inspector/changes/draftPreview.js +133 -0
  67. package/dist/inspector/changes/editModel.d.ts +8 -0
  68. package/dist/inspector/changes/editModel.js +8 -2
  69. package/dist/inspector/changes/managedStyleProjection.js +2 -1
  70. package/dist/inspector/changes/previewDiagnostics.js +1 -1
  71. package/dist/inspector/changes/types.d.ts +11 -1
  72. package/dist/inspector/comments/CommentsOverlay.js +18 -16
  73. package/dist/inspector/comments/store.js +3 -2
  74. package/dist/inspector/comments/verification.js +1 -1
  75. package/dist/inspector/componentSemantics/ComponentPropsSection.js +2 -1
  76. package/dist/inspector/componentSemantics/changeAction.js +1 -1
  77. package/dist/inspector/componentSemantics/textBinding.d.ts +1 -2
  78. package/dist/inspector/componentSemantics/textBinding.js +4 -45
  79. package/dist/inspector/drafts/draftStore.d.ts +109 -0
  80. package/dist/inspector/drafts/draftStore.js +451 -0
  81. package/dist/inspector/drafts/model.d.ts +9 -1
  82. package/dist/inspector/drafts/model.js +5 -1
  83. package/dist/inspector/drafts/store.d.ts +53 -15
  84. package/dist/inspector/drafts/store.js +143 -169
  85. package/dist/inspector/index.d.ts +2 -4
  86. package/dist/inspector/index.js +2 -264
  87. package/dist/inspector/inline-text/inlineTextEditor.d.ts +1 -1
  88. package/dist/inspector/inline-text/inlineTextEditor.js +1 -2
  89. package/dist/inspector/inline-text/textEditChange.d.ts +1 -1
  90. package/dist/inspector/inspection/bridge.d.ts +1 -1
  91. package/dist/inspector/inspection/bridge.js +2 -1
  92. package/dist/inspector/inspection/browserCssInspection.d.ts +8 -0
  93. package/dist/inspector/inspection/browserCssInspection.js +28 -6
  94. package/dist/inspector/inspection/inspectionSnapshot.d.ts +6 -0
  95. package/dist/inspector/inspection/inspectionSnapshot.js +10 -0
  96. package/dist/inspector/inspection/performanceDiagnostics.d.ts +18 -0
  97. package/dist/inspector/inspection/performanceDiagnostics.js +31 -0
  98. package/dist/inspector/inspection/useBrowserCssInspection.js +17 -38
  99. package/dist/inspector/iterations/capture.d.ts +2 -1
  100. package/dist/inspector/iterations/client.d.ts +2 -1
  101. package/dist/inspector/iterations/coordinator.d.ts +4 -3
  102. package/dist/inspector/iterations/coordinator.js +5 -3
  103. package/dist/inspector/iterations/prompt.js +9 -8
  104. package/dist/inspector/overlay/interactionStyles.js +17 -0
  105. package/dist/inspector/pageInspector.d.ts +42 -0
  106. package/dist/inspector/pageInspector.js +342 -0
  107. package/dist/inspector/projection/draftProjection.d.ts +10 -2
  108. package/dist/inspector/projection/draftProjection.js +9 -1
  109. package/dist/inspector/projection/managedStylesheet.js +24 -4
  110. package/dist/inspector/projection/renderedInstance.js +7 -1
  111. package/dist/inspector/projection/structuralProjection.d.ts +0 -9
  112. package/dist/inspector/projection/structuralProjection.js +14 -44
  113. package/dist/inspector/projection/textProjection.d.ts +8 -2
  114. package/dist/inspector/projection/textProjection.js +21 -13
  115. package/dist/inspector/prompt/clipboardHandoff.d.ts +9 -0
  116. package/dist/inspector/prompt/clipboardHandoff.js +42 -16
  117. package/dist/inspector/prompt/generatePrompt.d.ts +1 -1
  118. package/dist/inspector/prompt/generatePrompt.js +243 -147
  119. package/dist/inspector/prompt/promptDocument.d.ts +3 -0
  120. package/dist/inspector/prompt/promptDocument.js +8 -0
  121. package/dist/inspector/prompt/promptSettings.js +7 -5
  122. package/dist/inspector/runtime/inspectorStorage.d.ts +17 -0
  123. package/dist/inspector/runtime/inspectorStorage.js +42 -0
  124. package/dist/inspector/runtime/ownedStylesheets.d.ts +20 -0
  125. package/dist/inspector/runtime/ownedStylesheets.js +72 -0
  126. package/dist/inspector/runtime/runtimeConfig.d.ts +4 -0
  127. package/dist/inspector/runtime/runtimeConfig.js +10 -1
  128. package/dist/inspector/runtime/runtimeIdentityClaims.d.ts +29 -0
  129. package/dist/inspector/runtime/runtimeIdentityClaims.js +188 -0
  130. package/dist/inspector/runtime/staticHtmlRuntimeIdentity.d.ts +15 -0
  131. package/dist/inspector/runtime/staticHtmlRuntimeIdentity.js +113 -29
  132. package/dist/inspector/selection/editScope.d.ts +1 -0
  133. package/dist/inspector/selection/editScope.js +13 -2
  134. package/dist/inspector/shell/AgentStatusButton.js +2 -2
  135. package/dist/inspector/shell/AppShell.js +6 -2
  136. package/dist/inspector/shell/ChangesLog.js +27 -9
  137. package/dist/inspector/shell/CopyPromptButton.js +60 -54
  138. package/dist/inspector/shell/EmptyState.js +3 -3
  139. package/dist/inspector/shell/InspectorShell.d.ts +0 -3
  140. package/dist/inspector/shell/InspectorShell.js +21 -13
  141. package/dist/inspector/shell/PeekOriginalButton.js +3 -3
  142. package/dist/inspector/shell/interactionStatePreview.d.ts +9 -0
  143. package/dist/inspector/shell/interactionStatePreview.js +112 -0
  144. package/dist/inspector/shell/originalPreview.d.ts +3 -2
  145. package/dist/inspector/shell/originalPreview.js +10 -37
  146. package/dist/inspector/shell/stateValue.js +2 -3
  147. package/dist/inspector/sketch/SketchEditor.js +5 -4
  148. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +2 -1
  149. package/dist/inspector/sketch/SketchFrameOverlay.js +5 -4
  150. package/dist/inspector/sketch/SketchOverlay.d.ts +3 -5
  151. package/dist/inspector/sketch/SketchOverlay.js +139 -263
  152. package/dist/inspector/sketch/SketchPromptPanel.d.ts +1 -1
  153. package/dist/inspector/sketch/SketchPromptPanel.js +8 -1
  154. package/dist/inspector/sketch/SketchThumbnail.d.ts +5 -0
  155. package/dist/inspector/sketch/SketchThumbnail.js +18 -0
  156. package/dist/inspector/sketch/SketchWorkspace.d.ts +1 -2
  157. package/dist/inspector/sketch/SketchWorkspace.js +69 -34
  158. package/dist/inspector/sketch/capture.js +19 -3
  159. package/dist/inspector/sketch/copySketch.d.ts +10 -0
  160. package/dist/inspector/sketch/copySketch.js +52 -0
  161. package/dist/inspector/sketch/drawingTools.d.ts +17 -0
  162. package/dist/inspector/sketch/drawingTools.js +47 -0
  163. package/dist/inspector/sketch/frameHeading.d.ts +9 -0
  164. package/dist/inspector/sketch/frameHeading.js +30 -0
  165. package/dist/inspector/sketch/freehand.js +3 -3
  166. package/dist/inspector/sketch/geometry.d.ts +6 -0
  167. package/dist/inspector/sketch/geometry.js +19 -0
  168. package/dist/inspector/sketch/history.d.ts +30 -0
  169. package/dist/inspector/sketch/history.js +58 -0
  170. package/dist/inspector/sketch/interaction.d.ts +1 -3
  171. package/dist/inspector/sketch/interaction.js +6 -9
  172. package/dist/inspector/sketch/model.d.ts +35 -1
  173. package/dist/inspector/sketch/model.js +116 -61
  174. package/dist/inspector/sketch/persistence.d.ts +14 -0
  175. package/dist/inspector/sketch/persistence.js +63 -7
  176. package/dist/inspector/sketch/raster.d.ts +6 -2
  177. package/dist/inspector/sketch/raster.js +43 -17
  178. package/dist/inspector/sketch/store.d.ts +1 -1
  179. package/dist/inspector/sketch/store.js +1 -1
  180. package/dist/inspector/sketch/videoFrames.d.ts +6 -0
  181. package/dist/inspector/sketch/videoFrames.js +81 -0
  182. package/dist/inspector/styleEditors/BackgroundEditor.js +2 -1
  183. package/dist/inspector/styleEditors/BorderEditor.js +30 -130
  184. package/dist/inspector/styleEditors/BorderRadiusEditor.js +8 -3
  185. package/dist/inspector/styleEditors/ImageBackgroundEditor.js +1 -1
  186. package/dist/inspector/styleEditors/LayoutSection.js +2 -2
  187. package/dist/inspector/styleEditors/borderModel.d.ts +42 -0
  188. package/dist/inspector/styleEditors/borderModel.js +114 -0
  189. package/dist/inspector/styleEditors/layoutValue.js +2 -3
  190. package/dist/inspector/tokens/TokenField.js +34 -3
  191. package/dist/inspector/tokens/TokensPanel.js +2 -1
  192. package/dist/inspector/tokens/editActions.d.ts +3 -3
  193. package/dist/inspector/tokens/editActions.js +9 -3
  194. package/dist/inspector/tokens/resolution/containerQueryEvaluator.d.ts +15 -0
  195. package/dist/inspector/tokens/resolution/containerQueryEvaluator.js +86 -0
  196. package/dist/inspector/tokens/resolution/cssomCollector.js +44 -9
  197. package/dist/inspector/tokens/resolution/nestedSelectors.d.ts +6 -0
  198. package/dist/inspector/tokens/resolution/nestedSelectors.js +67 -0
  199. package/dist/inspector/tokens/resolution/ruleMatching.js +23 -4
  200. package/dist/inspector/tokens/resolution.d.ts +6 -3
  201. package/dist/inspector/tokens/resolution.js +91 -112
  202. package/dist/inspector/tokens/tokenEdits.d.ts +1 -1
  203. package/dist/inspector/tokens/tokenEdits.js +1 -1
  204. package/dist/inspector/ui/InlineStyleWarning.js +2 -2
  205. package/dist/inspector/ui/InspectorStyle.d.ts +8 -0
  206. package/dist/inspector/ui/InspectorStyle.js +26 -0
  207. package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
  208. package/dist/inspector/ui/PopoverListbox.js +3 -3
  209. package/dist/inspector/ui/Toast.d.ts +14 -0
  210. package/dist/inspector/ui/Toast.js +30 -0
  211. package/dist/inspector/ui/assets/canvasIllustration.d.ts +2 -0
  212. package/dist/inspector/ui/assets/canvasIllustration.js +21 -0
  213. package/dist/inspector/ui/assets/gridIllustration.d.ts +2 -0
  214. package/dist/inspector/ui/assets/{grid.svg → gridIllustration.js} +3 -2
  215. package/dist/inspector/ui/customCursors.d.ts +5 -0
  216. package/dist/inspector/ui/customCursors.js +32 -6
  217. package/dist/inspector/ui/styles.js +12 -10
  218. package/dist/inspector/workspace/commands.js +1 -2
  219. package/dist/inspector/workspace/controller.js +1 -2
  220. package/dist/inspector/workspace/handoff.js +3 -2
  221. package/dist/inspector/workspace/sentItems.d.ts +3 -6
  222. package/dist/inspector/workspace/sentItems.js +6 -24
  223. package/dist/inspector/workspace/timeline.d.ts +24 -10
  224. package/dist/inspector/workspace/timeline.js +118 -96
  225. package/dist/internal/agent-protocol.d.ts +50 -13
  226. package/dist/internal/agent-protocol.js +145 -19
  227. package/dist/nudge-ui.mjs +81 -65
  228. package/dist/project/mcpPackage.d.ts +8 -0
  229. package/dist/project/mcpPackage.js +60 -0
  230. package/package.json +12 -2
  231. package/dist/inspector/canvas/measurementProjection.d.ts +0 -8
  232. package/dist/inspector/canvas/measurementProjection.js +0 -16
  233. package/dist/inspector/changes/changesLog.d.ts +0 -49
  234. package/dist/inspector/changes/changesLog.js +0 -255
  235. package/dist/inspector/changes/draftChanges.d.ts +0 -58
  236. package/dist/inspector/changes/draftChanges.js +0 -304
  237. package/dist/inspector/conformance/projection.d.ts +0 -2
  238. package/dist/inspector/conformance/projection.js +0 -3
  239. package/dist/inspector/inline-text/textChangeBoundary.d.ts +0 -6
  240. package/dist/inspector/inline-text/textChangeBoundary.js +0 -1
  241. package/dist/inspector/overlay/InspectorOverlay.d.ts +0 -5
  242. package/dist/inspector/overlay/InspectorOverlay.js +0 -332
  243. package/dist/inspector/overlay/elementSelector.d.ts +0 -1
  244. package/dist/inspector/overlay/elementSelector.js +0 -111
  245. package/dist/inspector/sketch/SketchChanges.d.ts +0 -2
  246. package/dist/inspector/sketch/SketchChanges.js +0 -72
  247. package/dist/inspector/sketch/SketchEntryButton.d.ts +0 -4
  248. package/dist/inspector/sketch/SketchEntryButton.js +0 -11
  249. package/dist/inspector/sketch/SketchLayersPanel.d.ts +0 -2
  250. package/dist/inspector/sketch/SketchLayersPanel.js +0 -71
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { canvasFrameElement } from "./canvasFrame.js";
2
3
  const activityStyles = ".canvas-agent-activity {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: 12;\n overflow: hidden;\n border-radius: 4px;\n}\n\n.canvas-agent-activity__label {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n position: absolute;\n top: 12px;\n right: 12px;\n max-width: 80%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n padding: 4px 10px 4px 4px;\n border-radius: 20px;\n background: #242424e8;\n color: #fff;\n font: 12px system-ui;\n box-shadow: 0 2px 12px #0003;\n}\n\n.canvas-agent-activity__label canvas {\n flex: 0 0 auto;\n}\n\n.canvas-agent-activity__label > span {\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.canvas-agent-activity__shimmer {\n position: absolute;\n border-radius: 5px;\n background: linear-gradient(110deg, transparent 20%, rgb(212 212 212 / 0.18) 35%, rgb(212 212 212 / 0.35) 50%, rgb(212 212 212 / 0.18) 65%, transparent 80%);\n background-size: 250% 100%;\n animation: nudge-component-shimmer 2.4s ease-in-out infinite;\n}\n\n@keyframes nudge-component-shimmer {\n from { background-position: 150% 0; }\n to { background-position: -150% 0; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .canvas-agent-activity__shimmer {\n animation: none;\n }\n}\n";
3
4
  import { useEffect, useState } from "react";
4
5
  import { ThinkingOrb } from "thinking-orbs";
@@ -7,6 +8,7 @@ import { getNudgeUiRuntimeConfig } from "../runtime/runtimeConfig.js";
7
8
  import { parseDataSrc } from "../selection/resolveSelection.js";
8
9
  import { targetKey } from "../drafts/model.js";
9
10
  import { contentEditTarget } from "./frameContent.js";
11
+ import { InspectorStyle } from "../ui/InspectorStyle.js";
10
12
  /** Reuses rendered source identities. Iteration provenance cannot target application source. */
11
13
  export function attributeActivity(content, document, files) {
12
14
  const target = contentEditTarget(content);
@@ -99,13 +101,14 @@ export function AgentActivityOverlay({ cardId, content, iframe }) {
99
101
  };
100
102
  };
101
103
  attachDocument();
102
- iframe.addEventListener("load", attachDocument);
103
- return () => { iframe.removeEventListener("load", attachDocument); detachDocument(); };
104
+ const element = canvasFrameElement(iframe);
105
+ element?.addEventListener("load", attachDocument);
106
+ return () => { element?.removeEventListener("load", attachDocument); detachDocument(); };
104
107
  }, [activity, content, iframe]);
105
108
  const submitted = activity.requestId !== null && activity.targets.includes(cardId)
106
109
  && (!activity.target || targetKey(activity.target) === targetKey(contentEditTarget(content)));
107
110
  const working = activity.requestId !== null && (submitted || visual.files.length > 0);
108
111
  if (!working)
109
112
  return null;
110
- return _jsxs("div", { className: "canvas-agent-activity", "data-test": `canvas-agent-activity-${cardId}`, children: [visual.highlights.map((rect, index) => _jsx("div", { className: "canvas-agent-activity__shimmer", "data-test": "canvas-agent-component-shimmer", style: { left: rect.x, top: rect.y, width: rect.width, height: rect.height } }, index)), _jsxs("span", { className: "canvas-agent-activity__label", role: "status", children: [_jsx(ThinkingOrb, { state: "solving", size: 20, theme: "dark", "aria-hidden": "true" }), _jsx("span", { children: "Agent working\u2026" })] }), _jsx("style", { children: activityStyles })] });
113
+ return _jsxs("div", { className: "canvas-agent-activity", "data-test": `canvas-agent-activity-${cardId}`, children: [visual.highlights.map((rect, index) => _jsx("div", { className: "canvas-agent-activity__shimmer", "data-test": "canvas-agent-component-shimmer", style: { left: rect.x, top: rect.y, width: rect.width, height: rect.height } }, index)), _jsxs("span", { className: "canvas-agent-activity__label", role: "status", children: [_jsx(ThinkingOrb, { state: "solving", size: 20, theme: "dark", "aria-hidden": "true" }), _jsx("span", { children: "Agent working\u2026" })] }), _jsx(InspectorStyle, { css: activityStyles })] });
111
114
  }
@@ -4,11 +4,11 @@ import { contentDocumentUrl, contentNavigationUrl, contentSourceUrl, iterationId
4
4
  import { refreshHtmlIteration } from "../iterations/coordinator.js";
5
5
  import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
6
6
  import { CANVAS_RENDERER_ATTR } from "./roleDetection.js";
7
- import { useCanvasCards, getCanvasCardLabel, updateCardUrl, resizeCard, setCardPosition, selectCard, getSelectedCardId, useSelectedCardId, useFocusedCardId, } from "./canvasStore.js";
7
+ import { useCanvasCards, getCanvasCardLabel, updateCardUrl, resizeCard, setCardPosition, beginCardGeometryChange, selectCard, getSelectedCardId, useSelectedCardId, useFocusedCardId, } from "./canvasStore.js";
8
8
  import { useBoardCamera } from "./viewStore.js";
9
9
  import { IconArrowsDiagonal, IconCopy, IconCornerLeftDown, IconBoltFilled, IconArtboard, IconTrash } from "@tabler/icons-react";
10
- import { PROTOCOL_VERSION, } from "./frameProtocol.js";
11
- import { registerCardFrame, registerCardFrameSource, unregisterCardFrame, sendProjectionToCard, invalidateCanvasPreviewDocument, PROJECT_ID, WORKSPACE_ID } from "./projection.js";
10
+ import { connectFrame } from "./frameConnection.js";
11
+ import { registerCardFrame, registerCardFrameSource, unregisterCardFrame, sendProjectionToCard, invalidateCanvasPreviewDocument } from "./projection.js";
12
12
  import { Button } from "../ui/Button.js";
13
13
  import { IconButton } from "../ui/IconButton.js";
14
14
  import { Tooltip } from "../ui/Tooltip.js";
@@ -20,6 +20,7 @@ import { getCanvasToolbarScale } from "./toolbarScale.js";
20
20
  import { getCanvasResizeHandleScale } from "./resizeHandleScale.js";
21
21
  import { disposeBrowserCssInspection } from "../inspection/browserCssInspectionRegistry.js";
22
22
  import { releaseDocumentProjection } from "../projection/structuralProjection.js";
23
+ import { disposeRuntimeIdentityClaims } from "../runtime/runtimeIdentityClaims.js";
23
24
  import { startClipboardHandoffController } from "../prompt/clipboardHandoff.js";
24
25
  import { disposeInlineTextEdit, isInlineTextEditingActive } from "../inline-text/inlineTextEditor.js";
25
26
  import { configureNudgeUiRuntime, getNudgeUiRuntimeConfig } from "../runtime/runtimeConfig.js";
@@ -93,12 +94,17 @@ export function CanvasCard({ card, presentation = "canvas", presentationCard = t
93
94
  const frameDocument = iframeRef.current?.contentDocument;
94
95
  if (!documentOwner || !frameDocument)
95
96
  return;
97
+ // The initial blank document is discarded by navigation. A blocked frame
98
+ // can make its Window inaccessible before cleanup runs.
99
+ if (frameDocument.URL === "about:blank")
100
+ return;
96
101
  if (documentSessionRef.current?.document === frameDocument)
97
102
  return;
98
103
  disposeDocumentSession();
99
104
  const session = documentOwner.createDocumentSession(frameDocument);
100
105
  session.registerCleanup(() => disposeBrowserCssInspection(frameDocument));
101
106
  session.registerCleanup(() => releaseDocumentProjection(frameDocument));
107
+ session.registerCleanup(() => disposeRuntimeIdentityClaims(frameDocument));
102
108
  session.registerCleanup(() => disposeInlineTextEdit("frame-disposed", frameDocument));
103
109
  session.registerCleanup(startClipboardHandoffController(frameDocument));
104
110
  session.registerCleanup(installFrameScrollbarStyles(frameDocument));
@@ -170,13 +176,8 @@ export function CanvasCard({ card, presentation = "canvas", presentationCard = t
170
176
  }
171
177
  /** The controller half of the handshake: announce workspace identity. */
172
178
  function sendParentReady() {
173
- iframeRef.current?.contentWindow?.postMessage({
174
- type: "parent-ready",
175
- protocolVersion: PROTOCOL_VERSION,
176
- projectId: PROJECT_ID,
177
- workspaceId: WORKSPACE_ID,
178
- cardId: card.id,
179
- }, window.location.origin);
179
+ if (iframeRef.current)
180
+ connectFrame(card.id, iframeRef.current).post({ type: "parent-ready" });
180
181
  }
181
182
  function adoptFrameRuntime(runtime) {
182
183
  frameRuntimeRef.current = runtime;
@@ -202,8 +203,8 @@ export function CanvasCard({ card, presentation = "canvas", presentationCard = t
202
203
  adoptFrameRuntime(frameRuntimeRef.current);
203
204
  }, [isSelected]);
204
205
  useEffect(() => {
205
- return subscribeCanvasRendererMessages(({ cardId, message: data }) => {
206
- if (cardId !== card.id)
206
+ return subscribeCanvasRendererMessages(({ frame, message: data }) => {
207
+ if (frame.cardId !== card.id)
207
208
  return;
208
209
  // A renderer whose runtime finished booting after the iframe load event
209
210
  // asks for the identity announcement it may have missed.
@@ -294,6 +295,8 @@ export function CanvasCard({ card, presentation = "canvas", presentationCard = t
294
295
  e.stopPropagation();
295
296
  e.preventDefault();
296
297
  const iterationDrag = e.altKey && !!onCreateIteration && loadState === "ready" && !creatingIteration;
298
+ // Iteration creation history already records where the new frame was dropped.
299
+ const commitMove = iterationDrag ? null : beginCardGeometryChange(card.id);
297
300
  let moved = false;
298
301
  let iterationId;
299
302
  let creationStarted = false;
@@ -344,6 +347,7 @@ export function CanvasCard({ card, presentation = "canvas", presentationCard = t
344
347
  cleanup();
345
348
  setIsDragging(false);
346
349
  cancelled = true;
350
+ commitMove?.();
347
351
  if (iterationId) {
348
352
  removeFrame(iterationId, { undoable: false });
349
353
  selectCard(card.id);
@@ -352,6 +356,7 @@ export function CanvasCard({ card, presentation = "canvas", presentationCard = t
352
356
  function onUp() {
353
357
  cleanup();
354
358
  setIsDragging(false);
359
+ commitMove?.();
355
360
  if (iterationId)
356
361
  setCardPosition(iterationId, position.x, position.y);
357
362
  }
@@ -380,6 +385,7 @@ export function CanvasCard({ card, presentation = "canvas", presentationCard = t
380
385
  width: card.width,
381
386
  height: card.height,
382
387
  };
388
+ const commitResize = beginCardGeometryChange(card.id);
383
389
  function onMove(ev) {
384
390
  if (ev.pointerId !== pointerId)
385
391
  return;
@@ -399,6 +405,7 @@ export function CanvasCard({ card, presentation = "canvas", presentationCard = t
399
405
  target.releasePointerCapture(pointerId);
400
406
  if (resizeCleanupRef.current === cleanup)
401
407
  resizeCleanupRef.current = null;
408
+ commitResize();
402
409
  }
403
410
  function onUp(ev) {
404
411
  if (ev.pointerId !== pointerId)
@@ -441,7 +448,9 @@ export function CanvasCard({ card, presentation = "canvas", presentationCard = t
441
448
  if ((delta.x !== 0 && edges.horizontal === null) || (delta.y !== 0 && edges.vertical === null))
442
449
  return;
443
450
  const next = resizeCanvasRect(card, direction, delta);
451
+ const commitResize = beginCardGeometryChange(card.id);
444
452
  resizeCard(card.id, next.width, next.height, { x: next.x, y: next.y });
453
+ commitResize();
445
454
  }
446
455
  return (_jsxs("div", { className: `canvas-card${linkedToActive ? " is-linked" : ""}${isDragging ? " is-dragging" : ""}${isSelected ? " is-selected" : ""}${isFocused ? " is-focused" : ""}${isFocusPresentationCard ? " is-focus-presentation" : ""}${presentationCard ? " is-presentation-card" : ""}`, "data-card-id": card.id, tabIndex: presentation === "canvas" ? 0 : undefined, role: "group", "aria-label": `Frame: ${frameLabel}`, style: {
447
456
  "--canvas-toolbar-scale": toolbarScale,
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
3
- import { PROTOCOL_VERSION, isElementClickMessage, isInlineTextIntentMessage, } from "./frameProtocol.js";
3
+ import { isElementClickMessage, isInlineTextIntentMessage } from "./frameProtocol.js";
4
4
  import { useCanvasCards } from "./canvasStore.js";
5
5
  import { useBoardCamera, useCanvasPresentation, useCanvasPresentationTransitioning, useCanvasLayoutTransitioning, getCanvasLayoutTransitioning, useTemporaryAppInteraction, } from "./viewStore.js";
6
6
  import { handleElementClick } from "./rendererSelectionProxy.js";
@@ -9,7 +9,7 @@ import { readBorderWidths, toRect, } from "../overlay/overlayGeometry.js";
9
9
  const overlayStyles = ".canvas-element-overlay {\n position: fixed;\n pointer-events: none;\n z-index: 1;\n background: transparent;\n outline: 1px solid var(--accent);\n outline-offset: -1px;\n}\n\n.canvas-spacing-guide {\n position: fixed;\n z-index: 1;\n border-radius: 999px;\n background: var(--accent);\n outline: none;\n box-shadow: none;\n pointer-events: none;\n}\n\n.canvas-spacing-fill {\n position: fixed;\n z-index: 1;\n pointer-events: none;\n background: rgb(59 130 246 / 14%);\n}\n\n.canvas-spacing-fill[data-kind=\"gap\"] {\n background: rgb(236 72 153 / 14%);\n}\n\n.canvas-spacing-guide[data-kind=\"gap\"] {\n background: #ec4899;\n}\n\n.canvas-spacing-value-badge {\n position: fixed;\n z-index: 2;\n transform: translate(-50%, calc(-100% - var(--space-8)));\n padding: var(--space-4) var(--space-8);\n border-radius: var(--radius-8);\n background: var(--surface-raised-2x);\n box-shadow: var(--shadow-popup);\n color: var(--text-primary);\n font-family: var(--font-family);\n font-size: var(--font-size-base);\n font-weight: 400;\n line-height: 1.4;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n pointer-events: none;\n}\n\n.canvas-selected-outline {\n position: fixed;\n pointer-events: none;\n z-index: 1;\n outline: 1px solid var(--accent);\n outline-offset: -1px;\n}\n\n.canvas-dom-drop-line {\n z-index: 2147483647;\n border-radius: 999px;\n background: #3b82f6;\n box-shadow: 0 0 0 1px rgb(255 255 255 / 95%), 0 1px 5px rgb(37 99 235 / 72%);\n}\n\n.canvas-dom-drop-line::before,\n.canvas-dom-drop-line::after {\n position: absolute;\n top: 50%;\n width: 8px;\n height: 8px;\n border: 2px solid #ffffff;\n border-radius: 50%;\n background: #2563eb;\n content: \"\";\n transform: translateY(-50%);\n}\n\n.canvas-dom-drop-line::before { left: -3px; }\n.canvas-dom-drop-line::after { right: -3px; }\n\n.canvas-dom-drop-line[data-orientation=\"vertical\"]::before,\n.canvas-dom-drop-line[data-orientation=\"vertical\"]::after {\n left: 50%;\n right: auto;\n transform: translateX(-50%);\n}\n\n.canvas-dom-drop-line[data-orientation=\"vertical\"]::before { top: -3px; }\n.canvas-dom-drop-line[data-orientation=\"vertical\"]::after { top: auto; bottom: -3px; }\n\n.canvas-dom-drop-target {\n z-index: 2147483646;\n box-sizing: border-box;\n border: 1px solid rgb(59 130 246 / 72%);\n border-radius: 4px;\n background: rgb(59 130 246 / 5%);\n}\n";
10
10
  import { deleteElement, getDropLocationAtPoint, moveElement, nudgeElement } from "../overlay/structuralGestures.js";
11
11
  import { changeKey } from "../changes/model.js";
12
- import { getChanges, redo, subscribeChanges, undo } from "../changes/changesLog.js";
12
+ import { getActiveDraftChanges, redo, subscribeActiveDraftChanges, undo } from "../drafts/store.js";
13
13
  import { resolveSelectionFromElement } from "../selection/resolveSelection.js";
14
14
  import { setSelectedElement } from "../selection/selectionStore.js";
15
15
  import { clearDropGuide, showDropGuide, useDropGuide } from "../overlay/dropGuide.js";
@@ -17,8 +17,8 @@ import { DropGuideOverlay } from "../overlay/DropGuideOverlay.js";
17
17
  import { getMeasurementGeometry } from "../overlay/measurementGeometry.js";
18
18
  import { getElementComputedStyle } from "../runtime/domRealm.js";
19
19
  import { MeasurementGuideOverlay } from "../overlay/MeasurementGuideOverlay.js";
20
- import { projectMeasurementSegments } from "./measurementProjection.js";
21
- import { getRegisteredFrames, PROJECT_ID, WORKSPACE_ID } from "./projection.js";
20
+ import { findCanvasFrameByDocument, getRegisteredFrames } from "./projection.js";
21
+ import { connectFrame, measureFrameViewport, toViewportPoint, toViewportRect } from "./frameConnection.js";
22
22
  import { RENDERER_ELEMENT_ID_ATTR } from "./rendererCidIndex.js";
23
23
  import { observeSelectedGeometry } from "../overlay/selectedGeometry.js";
24
24
  import { handleInlineTextEditIntent, useInlineTextSession } from "../inline-text/inlineTextEditor.js";
@@ -30,22 +30,7 @@ import { getSpacingAffordanceAtPoint, getSpacingAffordanceForDescriptor, getSpac
30
30
  import { beginLayoutPreview, endLayoutPreview } from "../styleEditors/layoutPreviewState.js";
31
31
  import { useNudgeUiRuntimeConfig } from "../runtime/useRuntimeConfig.js";
32
32
  import { DomNavigator } from "../overlay/DomNavigator.js";
33
- function projectRect(iframe, rect, zoom) {
34
- const frameRect = iframe.getBoundingClientRect();
35
- return {
36
- left: frameRect.left + rect.left * zoom,
37
- top: frameRect.top + rect.top * zoom,
38
- width: rect.width * zoom,
39
- height: rect.height * zoom,
40
- };
41
- }
42
- function projectPoint(iframe, x, y, zoom) {
43
- const frameRect = iframe.getBoundingClientRect();
44
- return {
45
- x: frameRect.left + x * zoom,
46
- y: frameRect.top + y * zoom,
47
- };
48
- }
33
+ import { InspectorStyle } from "../ui/InspectorStyle.js";
49
34
  function spacingBadgeForDrag(drag, point) {
50
35
  return {
51
36
  iframe: drag.iframe,
@@ -74,40 +59,18 @@ function spacingGuideStyle(rect) {
74
59
  height,
75
60
  });
76
61
  }
77
- function findFrameElement(iframe, elementId, cid, src) {
78
- const doc = iframe.contentDocument;
79
- if (!doc)
80
- return null;
81
- if (!/^r\d+$/.test(elementId))
82
- return null;
83
- const candidates = doc.querySelectorAll(`[${RENDERER_ELEMENT_ID_ATTR}="${elementId}"]`);
84
- if (candidates.length !== 1)
85
- return null;
86
- const candidate = candidates[0] ?? null;
87
- if (!candidate
88
- || candidate.getAttribute("data-cid") !== cid
89
- || (candidate.getAttribute("data-src") ?? "") !== src)
90
- return null;
91
- const frameWindow = doc.defaultView;
92
- return frameWindow && candidate instanceof frameWindow.HTMLElement ? candidate : null;
93
- }
94
- function projectGuideToCanvas(guide, zoom) {
95
- if (!guide)
96
- return null;
97
- const iframe = guide.document.defaultView?.frameElement;
98
- if (!(iframe instanceof HTMLIFrameElement))
62
+ function projectGuideToCanvas(guide) {
63
+ const iframe = findCanvasFrameByDocument(guide?.document);
64
+ if (!guide || !iframe)
99
65
  return null;
100
- const iframeRect = iframe.getBoundingClientRect();
101
- const project = (rect, scaleWidth, scaleHeight) => ({
102
- left: iframeRect.left + rect.left * zoom,
103
- top: iframeRect.top + rect.top * zoom,
104
- width: scaleWidth ? rect.width * zoom : rect.width,
105
- height: scaleHeight ? rect.height * zoom : rect.height,
106
- });
66
+ const viewport = measureFrameViewport(iframe);
107
67
  return {
108
68
  orientation: guide.orientation,
109
- line: project(guide.line, guide.orientation === "horizontal", guide.orientation === "vertical"),
110
- target: project(guide.target, true, true),
69
+ line: toViewportRect(viewport, guide.line, {
70
+ width: guide.orientation === "horizontal",
71
+ height: guide.orientation === "vertical",
72
+ }),
73
+ target: toViewportRect(viewport, guide.target),
111
74
  };
112
75
  }
113
76
  function spacingDescriptorMatches(affordance, descriptor) {
@@ -191,7 +154,7 @@ function releasePendingSpacingCommits(pending, iframe) {
191
154
  function reconcilePendingSpacingCommits(pending) {
192
155
  if (pending.length === 0)
193
156
  return;
194
- const survivingKeys = new Set(getChanges().map(changeKey));
157
+ const survivingKeys = new Set(getActiveDraftChanges().changes.map(changeKey));
195
158
  let write = 0;
196
159
  for (const commit of pending) {
197
160
  if (commit.keys.some((key) => survivingKeys.has(key))) {
@@ -227,16 +190,6 @@ function pushPendingSpacingCommit(pending, next) {
227
190
  originalInlinePriority: pending[existing].originalInlinePriority,
228
191
  };
229
192
  }
230
- function sendMeasureModifier(iframe, cardId, altKey) {
231
- iframe.contentWindow?.postMessage({
232
- type: "measure-modifier",
233
- protocolVersion: PROTOCOL_VERSION,
234
- projectId: PROJECT_ID,
235
- workspaceId: WORKSPACE_ID,
236
- cardId,
237
- altKey,
238
- }, window.location.origin);
239
- }
240
193
  export function CanvasElementOverlay() {
241
194
  const [hover, setHover] = useState(null);
242
195
  const [measureState, setMeasureState] = useState(null);
@@ -257,10 +210,9 @@ export function CanvasElementOverlay() {
257
210
  setHover(null);
258
211
  setMeasureState(null);
259
212
  }, [layoutTransitioning]);
260
- const projectionZoom = presentation === "focus" ? 1 : camera.zoom;
261
213
  const cards = useCanvasCards(); // Projected geometry must follow card drag and resize updates.
262
214
  const cardGeometryKey = cards.map((card) => `${card.id}:${card.x}:${card.y}:${card.width}:${card.height}`).join("|");
263
- const projectionGeometryKey = `${presentation}:${camera.x}:${camera.y}:${projectionZoom}:${cardGeometryKey}`;
215
+ const projectionGeometryKey = `${presentation}:${camera.x}:${camera.y}:${camera.zoom}:${cardGeometryKey}`;
264
216
  const previousProjectionGeometryKey = useRef(projectionGeometryKey);
265
217
  const dragRef = useRef(null);
266
218
  const spacingDragRef = useRef(null);
@@ -268,29 +220,26 @@ export function CanvasElementOverlay() {
268
220
  const [spacingDragBadge, setSpacingDragBadge] = useState(null);
269
221
  const measureAltKeyRef = useRef(false);
270
222
  const dropGuide = useDropGuide("canvas");
271
- const projectedDropGuide = projectGuideToCanvas(dropGuide, projectionZoom);
272
- const selectedFrame = selected?.domElement.ownerDocument.defaultView?.frameElement;
273
- const selectedInCanvas = selectedFrame instanceof HTMLIFrameElement
274
- && selectedFrame.hasAttribute("data-nudge-ui-canvas-renderer");
223
+ const projectedDropGuide = projectGuideToCanvas(dropGuide);
224
+ const selectedFrame = findCanvasFrameByDocument(selected?.domElement.ownerDocument);
225
+ const selectedInCanvas = selectedFrame !== null;
275
226
  const selectedRects = selectedInCanvas
276
227
  ? selectedElements.map((candidate) => {
277
- const frame = candidate.domElement.ownerDocument.defaultView?.frameElement;
228
+ const frame = findCanvasFrameByDocument(candidate.domElement.ownerDocument);
278
229
  return frame === selectedFrame ? toRect(candidate.domElement.getBoundingClientRect()) : null;
279
230
  })
280
231
  : [];
281
232
  const selectedPrimaryIndex = selectedElements.findIndex((candidate) => candidate.domElement === selected?.domElement);
282
233
  const selectedLocalRect = selectedRects[selectedPrimaryIndex >= 0 ? selectedPrimaryIndex : 0] ?? null;
283
- const selectedFrameRect = selectedInCanvas
284
- ? toRect(selectedFrame.getBoundingClientRect())
234
+ const selectedFrameViewport = selectedInCanvas ? measureFrameViewport(selectedFrame) : null;
235
+ const selectedRect = selectedFrameViewport && selectedLocalRect
236
+ ? toViewportRect(selectedFrameViewport, selectedLocalRect)
285
237
  : null;
286
- const selectedRect = selectedInCanvas && selectedLocalRect
287
- ? projectRect(selectedFrame, selectedLocalRect, projectionZoom)
288
- : null;
289
- const projectedSelectedGeometry = selectedInCanvas
238
+ const projectedSelectedGeometry = selectedFrameViewport
290
239
  ? selectedRects.flatMap((rect, index) => {
291
240
  const element = selectedElements[index];
292
241
  return rect && element
293
- ? [{ element, rect: projectRect(selectedFrame, rect, projectionZoom) }]
242
+ ? [{ element, rect: toViewportRect(selectedFrameViewport, rect) }]
294
243
  : [];
295
244
  })
296
245
  : [];
@@ -316,7 +265,7 @@ export function CanvasElementOverlay() {
316
265
  const broadcastMeasureModifier = (altKey) => {
317
266
  measureAltKeyRef.current = altKey;
318
267
  for (const [cardId, iframe] of getRegisteredFrames()) {
319
- sendMeasureModifier(iframe, cardId, altKey);
268
+ connectFrame(cardId, iframe).post({ type: "measure-modifier", altKey });
320
269
  }
321
270
  };
322
271
  const onKeyDown = (event) => {
@@ -352,7 +301,7 @@ export function CanvasElementOverlay() {
352
301
  // it: undo/redo/revert/clear remove the record synchronously, so restore the
353
302
  // authored declaration before the renderer's next replace-styles lands.
354
303
  useEffect(() => {
355
- return subscribeChanges(() => {
304
+ return subscribeActiveDraftChanges(() => {
356
305
  reconcilePendingSpacingCommits(pendingSpacingCommitsRef.current);
357
306
  });
358
307
  }, []);
@@ -389,14 +338,14 @@ export function CanvasElementOverlay() {
389
338
  };
390
339
  });
391
340
  };
392
- const unsubscribe = subscribeCanvasRendererMessages(({ cardId: sourceCardId, iframe: sourceIframe, identity: frameIdentity, message: data, }) => {
341
+ const unsubscribe = subscribeCanvasRendererMessages(({ frame, message: data }) => {
393
342
  if (data.type === "renderer-hello" || data.type === "frame-ready") {
394
- sendMeasureModifier(sourceIframe, sourceCardId, measureAltKeyRef.current);
343
+ frame.post({ type: "measure-modifier", altKey: measureAltKeyRef.current });
395
344
  }
396
345
  if (data.type === "renderer-hello")
397
346
  return;
398
347
  if (data.type === "projection-applied") {
399
- releasePendingSpacingCommits(pendingSpacingCommitsRef.current, sourceIframe);
348
+ releasePendingSpacingCommits(pendingSpacingCommitsRef.current, frame.iframe);
400
349
  return;
401
350
  }
402
351
  if (getCanvasLayoutTransitioning())
@@ -406,18 +355,17 @@ export function CanvasElementOverlay() {
406
355
  if (!msg.cid)
407
356
  return;
408
357
  if (msg.rect === null) {
409
- setHover((current) => current?.cardId === sourceCardId ? null : current);
358
+ setHover((current) => current?.cardId === frame.cardId ? null : current);
410
359
  return;
411
360
  }
412
361
  const spacingDescriptor = msg.hoverSpacing ?? msg.spacing ?? null;
413
362
  const hoverPoint = msg.point ?? null;
414
- const hoverElement = spacingDescriptor
415
- ? findFrameElement(sourceIframe, msg.elementId, msg.cid, msg.src)
416
- : null;
363
+ const hoverElement = spacingDescriptor ? frame.element(msg) : null;
364
+ const frameDocument = frame.document();
417
365
  const hoverAffordance = spacingDescriptor && hoverElement
418
366
  ? getSpacingAffordanceForDescriptor(hoverElement, spacingDescriptor)
419
- : spacingDescriptor && hoverPoint && sourceIframe.contentDocument
420
- ? getSpacingHoverAtPoint(sourceIframe.contentDocument, hoverPoint.x, hoverPoint.y)
367
+ : spacingDescriptor && hoverPoint && frameDocument
368
+ ? getSpacingHoverAtPoint(frameDocument, hoverPoint.x, hoverPoint.y)
421
369
  : null;
422
370
  const matchingAffordance = spacingDescriptorMatches(hoverAffordance, spacingDescriptor)
423
371
  ? hoverAffordance
@@ -433,11 +381,11 @@ export function CanvasElementOverlay() {
433
381
  ? { point: hoverPoint, value: handleAffordance.value }
434
382
  : null;
435
383
  setHover({
436
- iframe: sourceIframe,
384
+ iframe: frame.iframe,
437
385
  identity: { elementId: msg.elementId },
438
386
  rect: msg.rect,
439
387
  borders: msg.borders ?? { top: 0, right: 0, bottom: 0, left: 0 },
440
- cardId: sourceCardId,
388
+ cardId: frame.cardId,
441
389
  spacing: spacingGuides.length > 0 ? spacingDescriptor : null,
442
390
  spacingGuides,
443
391
  spacingAreas,
@@ -447,29 +395,29 @@ export function CanvasElementOverlay() {
447
395
  else if (data.type === "element-measure-state") {
448
396
  const msg = data;
449
397
  setMeasureState({
450
- iframe: sourceIframe,
398
+ iframe: frame.iframe,
451
399
  altKey: msg.altKey,
452
400
  pointerOverPage: msg.pointerOverPage,
453
401
  });
454
402
  }
455
403
  else if (data.type === "element-click") {
456
- if (!isElementClickMessage(data, frameIdentity))
404
+ if (!isElementClickMessage(data, frame.identity))
457
405
  return;
458
406
  const msg = data;
459
407
  if (!msg.cid)
460
408
  return;
461
- handleElementClick(msg, sourceIframe, sourceCardId);
409
+ handleElementClick(msg, frame);
462
410
  }
463
411
  else if (data.type === "inline-text-intent") {
464
- if (!isInlineTextIntentMessage(data, frameIdentity))
412
+ if (!isInlineTextIntentMessage(data, frame.identity))
465
413
  return;
466
414
  if (getSelectedElements().length > 1)
467
415
  return;
468
416
  const emptyProjection = data.emptyProjectionId
469
- ? Array.from(sourceIframe.contentDocument?.querySelectorAll(`[${EMPTY_TEXT_PROJECTION_ATTR}]`) ?? [])
417
+ ? Array.from(frame.document()?.querySelectorAll(`[${EMPTY_TEXT_PROJECTION_ATTR}]`) ?? [])
470
418
  .find((candidate) => candidate.getAttribute(EMPTY_TEXT_PROJECTION_ATTR) === data.emptyProjectionId)
471
419
  : null;
472
- const element = emptyProjection ?? findFrameElement(sourceIframe, data.elementId, data.cid, data.src);
420
+ const element = emptyProjection ?? frame.element(data);
473
421
  if (!element)
474
422
  return;
475
423
  handleInlineTextEditIntent(data.intent === "double-click"
@@ -492,23 +440,24 @@ export function CanvasElementOverlay() {
492
440
  if (getSelectedElements().length > 1)
493
441
  return;
494
442
  const msg = data;
495
- const element = findFrameElement(sourceIframe, msg.elementId, msg.cid, msg.src);
443
+ const element = frame.element(msg);
496
444
  if (!element)
497
445
  return;
498
446
  clearSpacingDrag(spacingDragRef);
499
447
  dragRef.current = null;
500
448
  const startPoint = msg.startPoint ?? msg.point;
449
+ const frameDocument = frame.document();
501
450
  const spacing = msg.spacing
502
451
  ? getSpacingAffordanceForDescriptor(element, msg.spacing)
503
- : sourceIframe.contentDocument
504
- ? getSpacingAffordanceAtPoint(sourceIframe.contentDocument, startPoint.x, startPoint.y)
452
+ : frameDocument
453
+ ? getSpacingAffordanceAtPoint(frameDocument, startPoint.x, startPoint.y)
505
454
  : null;
506
455
  const isSpacingDrag = spacing
507
456
  && spacing.element === element
508
457
  && (!msg.spacing || spacingDescriptorMatches(spacing, msg.spacing));
509
458
  if (isSpacingDrag) {
510
459
  spacingDragRef.current = {
511
- iframe: sourceIframe,
460
+ iframe: frame.iframe,
512
461
  element,
513
462
  affordance: spacing,
514
463
  start: startPoint,
@@ -525,26 +474,26 @@ export function CanvasElementOverlay() {
525
474
  refreshSpacingPreviewOverlay(spacingDragRef.current);
526
475
  return;
527
476
  }
528
- dragRef.current = { iframe: sourceIframe, element };
477
+ dragRef.current = { iframe: frame.iframe, element };
529
478
  const selectedElement = resolveSelectionFromElement(element);
530
479
  if (selectedElement)
531
480
  setSelectedElement(selectedElement);
532
- updateDropGuide(msg.point, sourceIframe);
481
+ updateDropGuide(msg.point);
533
482
  }
534
483
  else if (data.type === "element-drag-move") {
535
484
  const spacing = spacingDragRef.current;
536
- if (spacing && spacing.iframe === sourceIframe) {
485
+ if (spacing && spacing.iframe === frame.iframe) {
537
486
  applySpacingPreview(spacing, data.point, data.shiftKey === true);
538
487
  setSpacingDragBadge(spacingBadgeForDrag(spacing, data.point));
539
488
  refreshSpacingPreviewOverlay(spacing);
540
489
  return;
541
490
  }
542
- updateDropGuide(data.point, sourceIframe);
491
+ updateDropGuide(data.point);
543
492
  }
544
493
  else if (data.type === "element-drag-end") {
545
494
  const msg = data;
546
495
  const spacing = spacingDragRef.current;
547
- if (spacing && spacing.iframe === sourceIframe) {
496
+ if (spacing && spacing.iframe === frame.iframe) {
548
497
  spacingDragRef.current = null;
549
498
  setSpacingDragBadge(null);
550
499
  if (msg.cancelled) {
@@ -556,7 +505,7 @@ export function CanvasElementOverlay() {
556
505
  const records = commitSpacingDrag(spacing);
557
506
  if (records.length > 0) {
558
507
  pushPendingSpacingCommit(pendingSpacingCommitsRef.current, {
559
- iframe: sourceIframe,
508
+ iframe: frame.iframe,
560
509
  element: spacing.element,
561
510
  property: spacing.affordance.property,
562
511
  committedValue: spacingValueCss(spacing.currentValue),
@@ -576,8 +525,9 @@ export function CanvasElementOverlay() {
576
525
  return;
577
526
  }
578
527
  const current = dragRef.current;
579
- if (!msg.cancelled && current && current.iframe === sourceIframe && sourceIframe.contentDocument) {
580
- const drop = getDropLocationAtPoint(sourceIframe.contentDocument, current.element, msg.point.x, msg.point.y);
528
+ const frameDocument = frame.document();
529
+ if (!msg.cancelled && current && current.iframe === frame.iframe && frameDocument) {
530
+ const drop = getDropLocationAtPoint(frameDocument, current.element, msg.point.x, msg.point.y);
581
531
  if (drop)
582
532
  moveElement(current.element, drop);
583
533
  const selectedElement = resolveSelectionFromElement(current.element);
@@ -590,8 +540,7 @@ export function CanvasElementOverlay() {
590
540
  else if (data.type === "element-delete") {
591
541
  if (getSelectedElements().length !== 1)
592
542
  return;
593
- const msg = data;
594
- const element = findFrameElement(sourceIframe, msg.elementId, msg.cid, msg.src);
543
+ const element = frame.element(data);
595
544
  const selectedElement = element ? resolveSelectionFromElement(element) : null;
596
545
  if (selectedElement && deleteElement(selectedElement))
597
546
  setSelectedElement(null);
@@ -599,9 +548,8 @@ export function CanvasElementOverlay() {
599
548
  else if (data.type === "element-nudge") {
600
549
  if (getSelectedElements().length !== 1)
601
550
  return;
602
- const msg = data;
603
- const element = findFrameElement(sourceIframe, msg.elementId, msg.cid, msg.src);
604
- const record = element ? nudgeElement(element, msg.key) : null;
551
+ const element = frame.element(data);
552
+ const record = element ? nudgeElement(element, data.key) : null;
605
553
  if (element && record) {
606
554
  const selectedElement = resolveSelectionFromElement(element);
607
555
  if (selectedElement)
@@ -623,16 +571,17 @@ export function CanvasElementOverlay() {
623
571
  else if (data.type === "inspector-open-request") {
624
572
  setInspectorOpen(true);
625
573
  }
626
- function updateDropGuide(point, iframe) {
574
+ function updateDropGuide(point) {
627
575
  const current = dragRef.current;
628
- if (!current || current.iframe !== iframe || !iframe.contentDocument)
576
+ const frameDocument = frame.document();
577
+ if (!current || current.iframe !== frame.iframe || !frameDocument)
629
578
  return;
630
- const drop = getDropLocationAtPoint(iframe.contentDocument, current.element, point.x, point.y);
579
+ const drop = getDropLocationAtPoint(frameDocument, current.element, point.x, point.y);
631
580
  if (!drop) {
632
581
  clearDropGuide("canvas");
633
582
  return;
634
583
  }
635
- showDropGuide("canvas", iframe.contentDocument, drop);
584
+ showDropGuide("canvas", frameDocument, drop);
636
585
  }
637
586
  });
638
587
  return () => {
@@ -647,20 +596,21 @@ export function CanvasElementOverlay() {
647
596
  return null;
648
597
  if (!hover && projectedSelectedGeometry.length === 0 && !projectedDropGuide)
649
598
  return null;
650
- const projectedHoverRect = hover ? projectRect(hover.iframe, hover.rect, projectionZoom) : null;
651
- const projectedSpacingGuides = hover?.spacingGuides.map((guide) => projectRect(hover.iframe, guide, projectionZoom)) ?? [];
652
- const projectedSpacingAreas = hover?.spacingAreas.map((area) => projectRect(hover.iframe, area, projectionZoom)) ?? [];
599
+ const hoverViewport = hover ? measureFrameViewport(hover.iframe) : null;
600
+ const projectedHoverRect = hover && hoverViewport ? toViewportRect(hoverViewport, hover.rect) : null;
601
+ const projectedSpacingGuides = hover && hoverViewport ? hover.spacingGuides.map((guide) => toViewportRect(hoverViewport, guide)) : [];
602
+ const projectedSpacingAreas = hover && hoverViewport ? hover.spacingAreas.map((area) => toViewportRect(hoverViewport, area)) : [];
653
603
  const spacingBadge = spacingDragBadge
654
604
  ?? (hover?.spacingHandleBadge ? { iframe: hover.iframe, ...hover.spacingHandleBadge } : null);
655
605
  const projectedSpacingBadge = spacingBadge
656
- ? projectPoint(spacingBadge.iframe, spacingBadge.point.x, spacingBadge.point.y, projectionZoom)
606
+ ? toViewportPoint(measureFrameViewport(spacingBadge.iframe), spacingBadge.point)
657
607
  : null;
658
608
  const hoverSpacing = hover?.spacing ?? null;
659
609
  const measureStateForSelectedFrame = measureState?.iframe === selectedFrame ? measureState : null;
660
610
  const hoverInSelectedFrame = hover?.iframe === selectedFrame ? hover : null;
661
611
  const showGuideOverlay = Boolean(selectedElements.length === 1
662
612
  && selectedRect
663
- && selectedFrameRect
613
+ && selectedFrameViewport
664
614
  && measureStateForSelectedFrame?.altKey
665
615
  && measureStateForSelectedFrame.pointerOverPage);
666
616
  const isSelfHover = Boolean(hoverInSelectedFrame
@@ -673,15 +623,20 @@ export function CanvasElementOverlay() {
673
623
  && selectedLocalRect
674
624
  && hoverInSelectedFrame
675
625
  && !isSelfHover);
676
- const measurementSegments = showMeasurement && selectedFrameRect && selectedLocalRect && hoverInSelectedFrame
677
- ? projectMeasurementSegments(selectedFrameRect, getMeasurementGeometry(selectedLocalRect, hoverInSelectedFrame.rect, {
626
+ // Segment distances stay in frame CSS pixels so labels read the same at any board zoom.
627
+ const measurementSegments = showMeasurement && selectedFrameViewport && selectedLocalRect && hoverInSelectedFrame
628
+ ? getMeasurementGeometry(selectedLocalRect, hoverInSelectedFrame.rect, {
678
629
  selectedBorders: selectedBorders ?? undefined,
679
630
  hoveredBorders: hoverInSelectedFrame.borders,
680
- }).segments, projectionZoom)
631
+ }).segments.map((segment) => ({
632
+ ...segment,
633
+ from: toViewportPoint(selectedFrameViewport, segment.from),
634
+ to: toViewportPoint(selectedFrameViewport, segment.to),
635
+ }))
681
636
  : [];
682
637
  if (presentationTransitioning || layoutTransitioning || temporaryAppInteraction)
683
- return _jsx("style", { "data-test": "canvas-element-overlay-styles", children: overlayStyles });
684
- return (_jsxs(_Fragment, { children: [_jsx("style", { "data-test": "canvas-element-overlay-styles", children: overlayStyles }), showGuideOverlay && selectedRect && selectedFrameRect ? (_jsx(MeasurementGuideOverlay, { testId: "canvas-measurement-overlay", selectedRect: selectedRect, guideViewport: selectedFrameRect, segments: measurementSegments })) : null, projectedHoverRect ? (_jsxs(_Fragment, { children: [hoverSpacing ? projectedSpacingAreas.map((area, index) => (_jsx("div", { className: "canvas-spacing-fill", "data-test": "canvas-spacing-fill", "data-kind": hoverSpacing.kind, "data-property": hoverSpacing.property, "data-side": hoverSpacing.side ?? undefined, "data-area-index": index, style: overlayStyle(area), "aria-hidden": "true" }, `${hoverSpacing.property}-area-${index}`))) : null, showHoverOutline ? (_jsx("div", { className: "canvas-element-overlay", "data-test": "canvas-hover-outline", style: overlayStyle(projectedHoverRect), "aria-hidden": "true" })) : null, hoverSpacing ? projectedSpacingGuides.map((guide, index) => (_jsx("div", { className: "canvas-spacing-guide", "data-test": "canvas-spacing-guide", "data-kind": hoverSpacing.kind, "data-property": hoverSpacing.property, "data-side": hoverSpacing.side ?? undefined, "data-guide-index": index, style: spacingGuideStyle(guide), "aria-hidden": "true" }, `${hoverSpacing.property}-${index}`))) : null] })) : null, projectedSelectedGeometry.map(({ element, rect }, index) => (_jsx("div", { className: "canvas-selected-outline", "data-test": "canvas-selected-outline", "data-selected-cid": element.cid, "data-selected-index": index, "data-selected-src": element.src, style: overlayStyle(rect), "aria-hidden": "true" }, `${element.cid}-${index}`))), projectedSpacingBadge && spacingBadge ? (_jsx("div", { className: "canvas-spacing-value-badge", "data-test": "canvas-spacing-value-badge", style: { left: projectedSpacingBadge.x, top: projectedSpacingBadge.y }, "aria-hidden": "true", children: spacingValueCss(spacingBadge.value) })) : null, domNavigationEnabled && !inlineTextSession && selectedElements.length === 1 && selected && selectedRect && selectedFrame instanceof HTMLIFrameElement ? (_jsx(DomNavigator, { selected: selected, hierarchy: hierarchy, anchor: selectedRect, project: (element) => element.ownerDocument === selected.domElement.ownerDocument
685
- ? projectRect(selectedFrame, toRect(element.getBoundingClientRect()), projectionZoom)
638
+ return _jsx(InspectorStyle, { testId: "canvas-element-overlay-styles", css: overlayStyles });
639
+ return (_jsxs(_Fragment, { children: [_jsx(InspectorStyle, { testId: "canvas-element-overlay-styles", css: overlayStyles }), showGuideOverlay && selectedRect && selectedFrameViewport ? (_jsx(MeasurementGuideOverlay, { testId: "canvas-measurement-overlay", selectedRect: selectedRect, guideViewport: selectedFrameViewport, segments: measurementSegments })) : null, projectedHoverRect ? (_jsxs(_Fragment, { children: [hoverSpacing ? projectedSpacingAreas.map((area, index) => (_jsx("div", { className: "canvas-spacing-fill", "data-test": "canvas-spacing-fill", "data-kind": hoverSpacing.kind, "data-property": hoverSpacing.property, "data-side": hoverSpacing.side ?? undefined, "data-area-index": index, style: overlayStyle(area), "aria-hidden": "true" }, `${hoverSpacing.property}-area-${index}`))) : null, showHoverOutline ? (_jsx("div", { className: "canvas-element-overlay", "data-test": "canvas-hover-outline", style: overlayStyle(projectedHoverRect), "aria-hidden": "true" })) : null, hoverSpacing ? projectedSpacingGuides.map((guide, index) => (_jsx("div", { className: "canvas-spacing-guide", "data-test": "canvas-spacing-guide", "data-kind": hoverSpacing.kind, "data-property": hoverSpacing.property, "data-side": hoverSpacing.side ?? undefined, "data-guide-index": index, style: spacingGuideStyle(guide), "aria-hidden": "true" }, `${hoverSpacing.property}-${index}`))) : null] })) : null, projectedSelectedGeometry.map(({ element, rect }, index) => (_jsx("div", { className: "canvas-selected-outline", "data-test": "canvas-selected-outline", "data-selected-cid": element.cid, "data-selected-index": index, "data-selected-src": element.src, style: overlayStyle(rect), "aria-hidden": "true" }, `${element.cid}-${index}`))), projectedSpacingBadge && spacingBadge ? (_jsx("div", { className: "canvas-spacing-value-badge", "data-test": "canvas-spacing-value-badge", style: { left: projectedSpacingBadge.x, top: projectedSpacingBadge.y }, "aria-hidden": "true", children: spacingValueCss(spacingBadge.value) })) : null, domNavigationEnabled && !inlineTextSession && selectedElements.length === 1 && selected && selectedRect && selectedFrameViewport ? (_jsx(DomNavigator, { selected: selected, hierarchy: hierarchy, anchor: selectedRect, project: (element) => element.ownerDocument === selected.domElement.ownerDocument
640
+ ? toViewportRect(selectedFrameViewport, toRect(element.getBoundingClientRect()))
686
641
  : null })) : null, _jsx(DropGuideOverlay, { guide: projectedDropGuide, lineClassName: "canvas-dom-drop-line", lineTestId: "canvas-dom-drop-line", targetClassName: "canvas-dom-drop-target", targetTestId: "canvas-dom-drop-target" })] }));
687
642
  }