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
@@ -3,6 +3,7 @@ import { IconMessageCircle, IconMessageCircleFilled, IconPointer2, IconSketching
3
3
  import { IconButton } from "../ui/IconButton.js";
4
4
  import { Tooltip } from "../ui/Tooltip.js";
5
5
  import { DesignPointerFilledIcon, SelectPlayFilledIcon, SelectPlayOutlineIcon, SketchThickIcon, } from "./CanvasToolIcons.js";
6
+ import { SketchToolOptions } from "./SketchToolOptions.js";
6
7
  export const CANVAS_ZOOM_LEVELS = [0.25, 0.5, 0.9, 1];
7
8
  export function closestCanvasZoomLevel(zoom) {
8
9
  let closest = CANVAS_ZOOM_LEVELS[0];
@@ -24,5 +25,5 @@ function ToolButton({ tool, activeTool, disabled, label, shortcut, icon, activeI
24
25
  return (_jsx(Tooltip, { content: label, shortcut: shortcut, stableTrigger: true, children: _jsx(IconButton, { variant: "quiet", size: "large", className: "canvas-toolbar__tool", label: label, "aria-pressed": active, "data-test": `canvas-tool-${tool}`, "data-active": active ? "true" : "false", disabled: disabled, onClick: () => onToolChange(tool), children: active ? activeIcon : icon }) }));
25
26
  }
26
27
  export function CanvasToolbar({ tool, sketchEnabled, onToolChange, }) {
27
- return (_jsxs("div", { className: "canvas-toolbar", "data-test": "canvas-toolbar", role: "toolbar", "aria-label": "Canvas tools", children: [_jsxs("div", { className: "canvas-toolbar__tools", role: "group", "aria-label": "Interaction tools", children: [_jsx(ToolButton, { tool: "design", activeTool: tool, label: "Select", shortcut: "V", icon: _jsx(IconPointer2, { size: ICON_SIZE, stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), activeIcon: _jsx(DesignPointerFilledIcon, { size: ICON_SIZE, "aria-hidden": "true" }), onToolChange: onToolChange }), _jsx(ToolButton, { tool: "comment", activeTool: tool, label: "Comment", shortcut: "C", icon: _jsx(IconMessageCircle, { size: ICON_SIZE, stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), activeIcon: _jsx(IconMessageCircleFilled, { size: ICON_SIZE, "aria-hidden": "true" }), onToolChange: onToolChange }), _jsx(ToolButton, { tool: "sketch", activeTool: tool, label: "Pencil", shortcut: "P", disabled: !sketchEnabled, icon: _jsx(IconSketching, { size: ICON_SIZE, stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), activeIcon: _jsx(SketchThickIcon, { size: ICON_SIZE, "aria-hidden": "true" }), onToolChange: onToolChange })] }), _jsx("div", { className: "canvas-toolbar__divider", "aria-hidden": "true" }), _jsx("div", { className: "canvas-toolbar__tools", role: "group", "aria-label": "Preview tools", children: _jsx(ToolButton, { tool: "select", activeTool: tool, label: "Use app normally", shortcut: "Hold A", icon: _jsx(SelectPlayOutlineIcon, { size: ICON_SIZE, "aria-hidden": "true" }), activeIcon: _jsx(SelectPlayFilledIcon, { size: ICON_SIZE, "aria-hidden": "true" }), onToolChange: onToolChange }) })] }));
28
+ return (_jsxs("div", { className: "canvas-toolbar", "data-test": "canvas-toolbar", role: "toolbar", "aria-label": "Canvas tools", children: [_jsxs("div", { className: "canvas-toolbar__tools", role: "group", "aria-label": "Interaction tools", children: [_jsx(ToolButton, { tool: "design", activeTool: tool, label: "Select", shortcut: "V", icon: _jsx(IconPointer2, { size: ICON_SIZE, stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), activeIcon: _jsx(DesignPointerFilledIcon, { size: ICON_SIZE, "aria-hidden": "true" }), onToolChange: onToolChange }), _jsx(ToolButton, { tool: "comment", activeTool: tool, label: "Comment", shortcut: "C", icon: _jsx(IconMessageCircle, { size: ICON_SIZE, stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), activeIcon: _jsx(IconMessageCircleFilled, { size: ICON_SIZE, "aria-hidden": "true" }), onToolChange: onToolChange }), _jsx(ToolButton, { tool: "sketch", activeTool: tool, label: "Pencil", shortcut: "P", disabled: !sketchEnabled, icon: _jsx(IconSketching, { size: ICON_SIZE, stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), activeIcon: _jsx(SketchThickIcon, { size: ICON_SIZE, "aria-hidden": "true" }), onToolChange: onToolChange }), _jsx(SketchToolOptions, { open: tool === "sketch" && sketchEnabled })] }), _jsx("div", { className: "canvas-toolbar__divider", "aria-hidden": "true" }), _jsx("div", { className: "canvas-toolbar__tools", role: "group", "aria-label": "Preview tools", children: _jsx(ToolButton, { tool: "select", activeTool: tool, label: "Use app normally", shortcut: "Hold A", icon: _jsx(SelectPlayOutlineIcon, { size: ICON_SIZE, "aria-hidden": "true" }), activeIcon: _jsx(SelectPlayFilledIcon, { size: ICON_SIZE, "aria-hidden": "true" }), onToolChange: onToolChange }) })] }));
28
29
  }
@@ -2,32 +2,35 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { getCanonicalFrameProjection } from "./projection.js";
3
3
  import { getDraftForCard } from "../drafts/store.js";
4
4
  import { iterationId, contentSourceUrl, getLinkedFrameIds } from "./frameContent.js";
5
- import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
5
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
6
6
  import { IconCornerLeftUp } from "@tabler/icons-react";
7
7
  import { useCanvasCards, activateIframeWorkspace, getSelectedCardId, getFocusedCardId, getCanvasCards, batchCanvasChanges, fitAllCards, focusCanvasCards, focusCard, useSelectedCardId, useFocusedCardId, resizeCard, setCardPosition, updateCardTitle, addCanvasCard, selectCard, deselectCard, CARD_GAP, updateCardUrl, } from "./canvasStore.js";
8
8
  import { setBoardCamera, getBoardCamera, setCanvasPresentation, hasFitAllRan, useBoardCamera, useCanvasPresentation, useCanvasPresentationTransitioning, useCanvasLayoutTransitioning, useTemporaryAppInteraction, setTemporaryAppInteraction, } from "./viewStore.js";
9
9
  import { CanvasCard } from "./CanvasCard.js";
10
+ import { PageCard } from "./PageCard.js";
11
+ import { isPageSurface } from "./pageSurface.js";
12
+ import { canvasFrameHost } from "./canvasFrame.js";
10
13
  import { useCanvasLayoutAnimation } from "./useCanvasLayoutAnimation.js";
11
14
  import { Button } from "../ui/Button.js";
12
15
  import { useCanvasMode } from "./viewStore.js";
13
- import { subscribeChanges } from "../changes/changesLog.js";
16
+ import { getActiveDraftChanges, getChangesList, subscribeActiveDraftChanges } from "../drafts/store.js";
14
17
  import { subscribeOriginalPreview, isOriginalPreviewActive } from "../shell/originalPreview.js";
15
18
  import { recordCanvasStructuralProjectionReports } from "../projection/structuralProjection.js";
16
19
  import { recordCanvasRenderedInstanceProjectionReports } from "../projection/renderedInstance.js";
17
20
  import { recordCanvasTextProjectionReports } from "../projection/textProjection.js";
18
- import { getChangesList, isPreviewableChange } from "../changes/changesLog.js";
21
+ import { isPreviewableChange } from "../changes/types.js";
19
22
  import { changeKey } from "../changes/model.js";
20
- import { getActiveDraftChanges } from "../changes/draftChanges.js";
21
23
  import { beginPreviewAttempt, publishPreviewDiagnostic } from "../changes/previewDiagnostics.js";
22
24
  import { verifyManagedStyleProjection } from "../changes/managedStyleProjection.js";
23
- import { getCanvasPreviewDocument, getRegisteredFrames, isCanvasCanonicalProjectionRevisionCurrent, PROJECT_ID, projectToAllReadyCards, recordCanvasProjectionApplied, WORKSPACE_ID, } from "./projection.js";
25
+ import { getCanvasPreviewDocument, getRegisteredFrames, isCanvasCanonicalProjectionRevisionCurrent, projectToAllReadyCards, recordCanvasProjectionApplied, getFrameRegistryVersion, subscribeFrameRegistry, } from "./projection.js";
26
+ import { connectFrame, toViewportPoint } from "./frameConnection.js";
24
27
  import { normalizeUrl, normalizedUrlKey } from "./normalizeUrl.js";
25
28
  import { createNudgeUiEditorUrl } from "../../transport/editor.js";
26
29
  import { disposeInlineTextEdit } from "../inline-text/inlineTextEditor.js";
27
- import { PROTOCOL_VERSION, isProjectionAppliedMessage, isKeyboardShortcutMessage, isRenderedInstanceProjectionReportMessage, isTextProjectionReportMessage, isStructuralProjectionReportMessage, } from "./frameProtocol.js";
30
+ import { isProjectionAppliedMessage, isKeyboardShortcutMessage, isRenderedInstanceProjectionReportMessage, isTextProjectionReportMessage, isStructuralProjectionReportMessage, } from "./frameProtocol.js";
28
31
  import { wheelPanDelta } from "./wheelPan.js";
29
- import { iframePointToClientPoint, zoomCameraAtPointer } from "./canvasGestures.js";
30
- const canvasWorkspaceStyles = ".canvas-workspace {\n position: fixed;\n inset: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface-panel);\n pointer-events: auto;\n}\n\n.canvas-workspace__board {\n flex: 1;\n overflow: hidden;\n position: relative;\n background: var(--surface-panel);\n}\n\n.canvas-workspace__focus-action {\n position: absolute;\n top: max(12px, env(safe-area-inset-top));\n left: max(12px, env(safe-area-inset-left));\n z-index: calc(var(--z-index) - 1);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--space-4);\n border-radius: var(--radius-12);\n background: var(--surface-raised-2x);\n box-shadow: var(--shadow-popup);\n pointer-events: auto;\n}\n\n.canvas-workspace__board.is-grabbable {\n cursor: grab;\n}\n\n.canvas-workspace__board.is-grabbing {\n cursor: grabbing;\n}\n\n/* Canvas gestures take precedence over every card interaction while Space is\n held. This also closes the short postMessage gap before an iframe receives\n its mirrored modifier state. */\n.canvas-workspace__board.is-grabbable .canvas-card,\n.canvas-workspace__board.is-grabbing .canvas-card {\n pointer-events: none;\n}\n\n.canvas-workspace__board-content {\n position: absolute;\n width: 100%;\n height: 100%;\n /*\n * Leave `will-change` unset so the browser can re-rasterize this subtree at\n * the current camera scale. A permanent `will-change: transform` promotes\n * it to a fixed bitmap, which makes iframe content look soft when zoomed.\n */\n}\n\n.canvas-workspace__board.is-grabbing .canvas-workspace__board-content {\n /* Panning benefits from a cached composited layer; zoom restores the crisp\n * default path as soon as the gesture ends. */\n will-change: transform;\n}\n\n.canvas-workspace[data-presentation=\"focus\"] .canvas-workspace__board-content {\n inset: 0;\n transform: none !important;\n}\n\n.canvas-workspace.is-presentation-transitioning .canvas-card:not(.is-presentation-card) {\n visibility: hidden !important;\n pointer-events: none !important;\n}\n\n.canvas-workspace__target-error {\n position: absolute;\n inset: 0;\n display: grid;\n place-items: center;\n color: #6b7280;\n font: 500 14px/1.4 system-ui, sans-serif;\n}\n\n/* Moving iframes must not acquire hover state beneath a stationary pointer. */\n.canvas-workspace.is-presentation-transitioning .canvas-card__iframe,\n.canvas-workspace__board.is-layout-transitioning .canvas-card__iframe {\n pointer-events: none;\n}\n";
32
+ import { zoomCameraAtPointer } from "./canvasGestures.js";
33
+ const canvasWorkspaceStyles = ".canvas-workspace {\n position: fixed;\n inset: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface-panel);\n pointer-events: auto;\n}\n\n.canvas-workspace__board {\n flex: 1;\n overflow: hidden;\n position: relative;\n background: var(--surface-panel);\n}\n\n.canvas-workspace__focus-action {\n position: absolute;\n top: max(12px, env(safe-area-inset-top));\n left: max(12px, env(safe-area-inset-left));\n z-index: calc(var(--z-index) - 1);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--space-4);\n border-radius: var(--radius-12);\n background: var(--surface-raised-2x);\n box-shadow: var(--shadow-popup);\n pointer-events: auto;\n}\n\n.canvas-workspace__board.is-grabbable {\n cursor: grab;\n}\n\n.canvas-workspace__board.is-grabbing {\n cursor: grabbing;\n}\n\n/* Canvas gestures take precedence over every card interaction while Space is\n held. This also closes the short postMessage gap before an iframe receives\n its mirrored modifier state. */\n.canvas-workspace__board.is-grabbable .canvas-card,\n.canvas-workspace__board.is-grabbing .canvas-card {\n pointer-events: none;\n}\n\n.canvas-workspace__board-content {\n position: absolute;\n width: 100%;\n height: 100%;\n /*\n * Leave `will-change` unset so the browser can re-rasterize this subtree at\n * the current camera scale. A permanent `will-change: transform` promotes\n * it to a fixed bitmap, which makes iframe content look soft when zoomed.\n */\n}\n\n.canvas-workspace__board.is-grabbing .canvas-workspace__board-content {\n /* Panning benefits from a cached composited layer; zoom restores the crisp\n * default path as soon as the gesture ends. */\n will-change: transform;\n}\n\n.canvas-workspace[data-presentation=\"focus\"] .canvas-workspace__board-content {\n inset: 0;\n transform: none !important;\n}\n\n.canvas-workspace.is-presentation-transitioning .canvas-card:not(.is-presentation-card) {\n visibility: hidden !important;\n pointer-events: none !important;\n}\n\n.canvas-workspace__target-error {\n position: absolute;\n inset: 0;\n display: grid;\n place-items: center;\n color: #6b7280;\n font: 500 14px/1.4 system-ui, sans-serif;\n}\n\n/* Moving iframes must not acquire hover state beneath a stationary pointer. */\n.canvas-workspace.is-presentation-transitioning .canvas-card__iframe,\n.canvas-workspace__board.is-layout-transitioning .canvas-card__iframe {\n pointer-events: none;\n}\n\n/* The page surface edits the live page under the workspace, so only the\n toolbar takes pointer input. */\n.canvas-workspace[data-surface=\"page\"],\n.canvas-workspace[data-surface=\"page\"] .canvas-workspace__board {\n background: transparent;\n pointer-events: none;\n}\n\n.canvas-workspace[data-surface=\"page\"] .canvas-toolbar {\n pointer-events: auto;\n}\n";
31
34
  const canvasCardStyles = ".canvas-card {\n position: relative;\n}\n\n/* Extend the card's hover region to the toolbar above the iframe. */\n.canvas-card::before {\n content: \"\";\n position: absolute;\n right: 0;\n bottom: 100%;\n left: 0;\n height: calc((var(--control-height-large) + var(--space-16)) * var(--canvas-toolbar-scale, 1));\n pointer-events: auto;\n}\n\n.canvas-card__frame {\n position: relative;\n width: 100%;\n height: 100%;\n background: var(--surface-panel);\n border-radius: 0;\n overflow: hidden;\n}\n\n.canvas-card.is-selected .canvas-card__frame {\n outline: 2px solid var(--frame-accent, var(--accent));\n outline-offset: -2px;\n}\n\n.canvas-card.is-focus-presentation .canvas-card__frame {\n outline: none;\n}\n\n.canvas-card.is-focused.is-selected:not(.is-linked) .canvas-card__frame {\n animation: canvas-card-focus 900ms ease-out;\n}\n\n@keyframes canvas-card-focus {\n 0%, 35% { outline: 3px solid var(--frame-accent, var(--accent)); outline-offset: -3px; }\n 100% { outline: 0 solid transparent; outline-offset: -2px; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .canvas-card.is-focused .canvas-card__frame {\n animation: none;\n }\n}\n\n.canvas-card__header {\n display: flex;\n align-items: center;\n position: absolute;\n left: 0;\n right: 0;\n bottom: calc(100% + var(--space-8));\n justify-content: flex-end;\n z-index: 3;\n opacity: 1;\n visibility: visible;\n pointer-events: auto;\n}\n\n.canvas-card__drag-surface {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--space-8);\n flex: 0 1 auto;\n margin-right: auto;\n min-width: 0;\n min-height: var(--control-height-large);\n padding: 0;\n cursor: grab;\n user-select: none;\n touch-action: none;\n}\n\n.canvas-card.is-dragging .canvas-card__drag-surface {\n cursor: grabbing;\n}\n\n.canvas-card__identity {\n display: flex;\n align-items: center;\n gap: var(--space-8);\n min-width: 0;\n}\n\n.canvas-card__dimensions {\n display: flex;\n align-items: center;\n gap: var(--space-8);\n position: relative;\n flex: 0 1 auto;\n min-width: 0;\n max-width: 260px;\n color: var(--text-secondary);\n font-size: var(--font-size-base);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n pointer-events: none;\n}\n\n.canvas-card__title {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.canvas-card__live-badge,\n.canvas-card__iteration-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 20px;\n height: 20px;\n background: var(--accent-surface);\n color: var(--accent);\n border-radius: var(--radius-4);\n pointer-events: auto;\n}\n\n.canvas-card__floating-toolbar {\n position: absolute;\n left: 50%;\n flex-shrink: 0;\n width: max-content;\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n transform: translate(-50%, calc(-24px + var(--space-4)));\n transition: opacity 140ms ease-out, transform 140ms ease-out, visibility 140ms;\n}\n\n.canvas-card:hover .canvas-card__floating-toolbar,\n.canvas-card:focus-within .canvas-card__floating-toolbar,\n.canvas-card__floating-toolbar[data-busy=\"true\"] {\n opacity: 1;\n visibility: visible;\n pointer-events: auto;\n transform: translate(-50%, -24px);\n}\n\n.canvas-card__actions.canvas-toolbar {\n position: static;\n width: max-content;\n transform: none;\n white-space: nowrap;\n}\n\n.canvas-card__actions .canvas-toolbar__divider {\n margin-inline: 0;\n}\n\n@media (hover: none) {\n .canvas-card.is-selected .canvas-card__floating-toolbar {\n opacity: 1;\n visibility: visible;\n pointer-events: auto;\n transform: translate(-50%, -24px);\n }\n}\n\n.canvas-card.is-dragging .canvas-card__floating-toolbar,\n.canvas-workspace__board.is-grabbable .canvas-card__floating-toolbar,\n.canvas-workspace__board.is-grabbing .canvas-card__floating-toolbar,\n.canvas-workspace.is-presentation-transitioning .canvas-card__floating-toolbar,\n.canvas-workspace__board.is-layout-transitioning .canvas-card__floating-toolbar {\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n transition: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .canvas-card__floating-toolbar { transition: none; }\n}\n\n.canvas-card__iteration-error {\n position: absolute;\n right: 0;\n bottom: calc(100% + var(--space-8));\n color: var(--text-danger, #b42318);\n font-size: 12px;\n white-space: nowrap;\n}\n\n.canvas-card__iframe {\n width: 100%;\n height: 100%;\n border: none;\n display: block;\n}\n\n.canvas-card__sketch-overlay {\n position: absolute;\n inset: 0;\n z-index: 2;\n display: block;\n pointer-events: none;\n}\n\n.canvas-card__loading {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--text-muted, #777777);\n font-size: 12px;\n font-weight: 500;\n}\n\n.canvas-card__error {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n height: 100%;\n padding: 20px;\n text-align: center;\n}\n\n.canvas-card__error p {\n margin: 0;\n color: var(--text-muted, #777777);\n font-size: 12px;\n font-weight: 500;\n}\n\n.canvas-card__error-actions {\n display: flex;\n gap: 8px;\n}\n\n.canvas-card__resize-handle {\n position: absolute;\n z-index: 3;\n display: flex;\n align-items: flex-end;\n justify-content: flex-end;\n padding: 4px;\n color: var(--text-muted, #777777);\n opacity: 0;\n transition: opacity 150ms ease;\n touch-action: none;\n user-select: none;\n}\n\n.canvas-card__resize-handle--top,\n.canvas-card__resize-handle--bottom {\n right: 16px;\n left: 16px;\n height: 16px;\n cursor: ns-resize;\n}\n\n.canvas-card__resize-handle--top {\n top: 0;\n}\n\n.canvas-card__resize-handle--bottom {\n bottom: 0;\n}\n\n.canvas-card__resize-handle--left,\n.canvas-card__resize-handle--right {\n top: 16px;\n bottom: 16px;\n width: 16px;\n cursor: ew-resize;\n}\n\n.canvas-card__resize-handle--left {\n left: 0;\n}\n\n.canvas-card__resize-handle--right {\n right: 0;\n}\n\n.canvas-card__resize-handle--top-left,\n.canvas-card__resize-handle--top-right,\n.canvas-card__resize-handle--bottom-right,\n.canvas-card__resize-handle--bottom-left {\n z-index: 4;\n width: 24px;\n height: 24px;\n cursor: nwse-resize;\n}\n\n.canvas-card__resize-handle--top-left {\n top: 0;\n left: 0;\n}\n\n.canvas-card__resize-handle--top-right {\n top: 0;\n right: 0;\n cursor: nesw-resize;\n}\n\n.canvas-card__resize-handle--bottom-right {\n right: 0;\n bottom: 0;\n}\n\n.canvas-card__resize-handle--bottom-left {\n bottom: 0;\n left: 0;\n cursor: nesw-resize;\n}\n\n.canvas-card:hover .canvas-card__resize-handle,\n.canvas-card:focus-within .canvas-card__resize-handle {\n opacity: 1;\n}\n\n.canvas-card__resize-handle:hover {\n color: var(--accent);\n}\n\n.canvas-card__resize-handle:focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 1px;\n opacity: 1;\n}\n\n.canvas-card.is-linked {\n --frame-accent: #a855f7;\n}\n\n.canvas-card.is-linked:not(.is-focus-presentation) .canvas-card__frame {\n outline: 2px solid var(--frame-accent);\n outline-offset: -2px;\n}\n\n.canvas-card__iteration-badge {\n color: var(--text-secondary);\n background: var(--surface-raised-2x);\n}\n";
32
35
  const foundationStyles = ":host {\n all: initial;\n --surface-panel: #F5F5F4;\n --surface-control: var(--surface-well);\n --surface-control-hover: var(--surface-well-2x);\n --surface-raised: #FAFAFA;\n --surface-raised-2x: #fff ;\n --surface-raised-hover: #F5F5F4;\n --surface-well: hsla(0, 0%, 0%, 0.05);\n --surface-well-2x: hsla(0, 0%, 0%, 0.10);\n\n --border-subtle: hsla(0, 0%, 0%, 0.1);\n --border-medium: hsla(0, 0%, 0%, 0.2);\n --border-strong: #d8d8d8;\n\n --text-primary: #242424;\n --text-secondary: #6f6f6f;\n --text-muted: #777777;\n --text-muted-subtle: #a1a1a1;\n\n --accent: #0096ff;\n --accent-surface: rgba(0, 176, 255, 0.15);\n --warning: #d97706;\n --warning-text: #9a5b00;\n --warning-surface: #fff6e5;\n --danger: #df4b4b;\n --danger-text: #bd3636;\n --danger-surface: rgba(223, 75, 75, 0.2);\n --success: #2e7d52;\n\n --space-0: 0;\n --space-4: 4px;\n --space-8: 8px;\n --space-12: 12px;\n --space-16: 16px;\n --space-20: 20px;\n --space-24: 24px;\n --space-32: 32px;\n --space-40: 40px;\n --space-48: 48px;\n\n /* Radius */\n --radius-0: 0;\n --radius-4: 4px;\n --radius-8: 8px;\n --radius-12: 12px;\n --radius-16: 16px;\n --radius-20: 20px;\n --radius-24: 24px;\n --radius-pill: 999px;\n\n --radius-control: var(--radius-8);\n --shadow-outline: 0 0 0 1px rgba(0, 0, 0, 0.05);\n --shadow-popup: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 4px 16px rgba(0, 0, 0, 0.10);\n --shadow-control: inset 0 -2px 0 rgba(0, 0, 0, 0.03), 0 0 0 0.5px rgba(0, 0, 0, 0.25);\n --z-index: 2147483647;\n\n --font-family: \"Inter Variable\", Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n --font-size-base: 12px;\n --font-size-small: 10px;\n --font-weight-base: 400;\n --font-weight-label: 600;\n\n /* Control semantic sizes */\n --control-height-small: 24px;\n --control-height-large: 32px;\n --control-height-xlarge: 40px;\n --label-column: 104px;\n\n --icon-size-small: 16px;\n --icon-size-large: 20px;\n --icon-stroke-width: 1.5px;\n\n position: fixed;\n inset: 0;\n z-index: var(--z-index);\n pointer-events: none;\n font-family: var(--font-family);\n background: transparent;\n box-sizing: border-box;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nbutton,\ninput,\nselect {\n font-family: inherit;\n}\n";
33
36
  import { useInspectorOpen } from "../shell/openStore.js";
@@ -39,7 +42,8 @@ import { useNudgeUiRuntimeConfig } from "../runtime/useRuntimeConfig.js";
39
42
  import { subscribeCanvasRendererMessages } from "./rendererMessageRouter.js";
40
43
  import { adjacentCanvasZoomLevel, CanvasToolbar, } from "./CanvasToolbar.js";
41
44
  import { APP_INTERACTION_KEY, canvasToolForEvent, canvasToolForCode } from "./keyboardShortcuts.js";
42
- const canvasToolbarStyles = ".canvas-toolbar {\n position: absolute;\n left: 50%;\n bottom: var(--space-20);\n /* Keep the persistent controls above the live sketch surface. The sketch\n toolbar gets the next layer so both toolbars remain available together. */\n z-index: calc(var(--z-index) - 2);\n display: flex;\n align-items: center;\n gap: var(--space-4);\n padding: var(--space-4);\n border-radius: var(--radius-12);\n background: var(--surface-raised-2x);\n box-shadow: var(--shadow-popup);\n transform: translateX(-50%);\n pointer-events: auto;\n}\n\n/* The live sketch surface is a separate stacking context. Keep a copy of the\n toolbar at the root layer while sketching so pointer events reach the\n controls instead of the full-screen SVG drawing surface. */\n.canvas-toolbar__portal {\n position: fixed;\n inset: 0;\n z-index: var(--z-index);\n pointer-events: none;\n}\n\n.canvas-toolbar__portal > .canvas-toolbar {\n pointer-events: auto;\n}\n\n.canvas-toolbar__tools {\n display: flex;\n align-items: center;\n gap: var(--space-4);\n}\n\n.canvas-toolbar__divider {\n flex-shrink: 0;\n width: 1px;\n height: var(--control-height-small);\n margin-inline: 0;\n background: var(--border-subtle);\n}\n\n.canvas-toolbar__tool[data-active=\"true\"] {\n background: var(--accent-surface);\n color: var(--accent);\n}\n";
45
+ const canvasToolbarStyles = ".canvas-toolbar {\n position: absolute;\n left: 50%;\n bottom: var(--space-20);\n /* Keep the persistent controls above the live sketch surface. The sketch\n toolbar gets the next layer so both toolbars remain available together. */\n z-index: calc(var(--z-index) - 2);\n display: flex;\n align-items: center;\n gap: var(--space-4);\n padding: var(--space-4);\n border-radius: var(--radius-12);\n background: var(--surface-raised-2x);\n box-shadow: var(--shadow-popup);\n transform: translateX(-50%);\n pointer-events: auto;\n}\n\n/* The live sketch surface is a separate stacking context. Keep a copy of the\n toolbar at the root layer while sketching so pointer events reach the\n controls instead of the full-screen SVG drawing surface. */\n.canvas-toolbar__portal {\n position: fixed;\n inset: 0;\n z-index: var(--z-index);\n pointer-events: none;\n}\n\n.canvas-toolbar__portal > .canvas-toolbar {\n pointer-events: auto;\n}\n\n.canvas-toolbar__tools {\n display: flex;\n align-items: center;\n gap: var(--space-4);\n}\n\n.canvas-toolbar__divider {\n flex-shrink: 0;\n width: 1px;\n height: var(--control-height-small);\n margin-inline: 0;\n background: var(--border-subtle);\n}\n\n.canvas-toolbar__tool[data-active=\"true\"] {\n background: var(--accent-surface);\n color: var(--accent);\n}\n\n/* Tool options slide out beside their tool. The component sets an explicit\n pixel width (0 or the content's natural width) so the toolbar grows smoothly. */\n.tool-options {\n flex-shrink: 0;\n margin-inline-start: calc(var(--space-4) * -1);\n /* Clip the slide horizontally only. Vertically the clip reaches the bar's\n padding, so illustrations that bleed to its bottom edge are never cut\n while the drawer is still animating open. */\n clip-path: inset(-64px 0 calc(var(--space-4) * -1));\n opacity: 0;\n transition:\n width 220ms cubic-bezier(0.2, 0, 0, 1),\n margin-inline-start 220ms cubic-bezier(0.2, 0, 0, 1),\n opacity 160ms ease;\n}\n\n.tool-options[data-expanded=\"true\"] {\n margin-inline-start: 0;\n opacity: 1;\n}\n\n/* Once the slide finishes, let tool content and popovers rise above the bar. */\n.tool-options[data-settled=\"true\"] {\n clip-path: none;\n}\n\n.tool-options[aria-hidden=\"true\"] {\n pointer-events: none;\n}\n\n.tool-options__inner {\n display: flex;\n align-items: center;\n gap: var(--space-4);\n width: max-content;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .tool-options {\n transition: none;\n }\n}\n";
46
+ const sketchToolOptionsStyles = "/* Pen, eraser, color and rectangle options shown in the canvas toolbar drawer. */\n\n.sketch-options__illustrated {\n appearance: none;\n position: relative;\n flex-shrink: 0;\n width: var(--control-height-xlarge);\n height: var(--control-height-xlarge);\n padding: 0;\n border: 0;\n border-radius: var(--radius-control);\n background: transparent;\n cursor: pointer;\n /* Clip the illustration at the toolbar's bottom edge, never above it. */\n clip-path: inset(-64px calc(var(--space-4) * -1) calc(var(--space-4) * -1));\n}\n\n/* Well behind the active tool, running through the toolbar padding to its bottom edge. */\n.sketch-options__illustrated::before {\n content: \"\";\n position: absolute;\n inset: 0 0 calc(var(--space-4) * -1);\n border-radius: var(--radius-control) var(--radius-control) 0 0;\n background: var(--surface-well);\n opacity: 0;\n transition: opacity 180ms ease;\n}\n\n.tool-options[data-settled=\"true\"] .sketch-options__illustrated[data-active=\"true\"]::before {\n opacity: 1;\n}\n\n.sketch-options__illustration {\n /* Artwork below the original crop line; lets the base stay visible while raised. */\n --illustration-bleed: 0px;\n position: absolute;\n left: 50%;\n /* Extend through the toolbar padding so the base sits on its bottom edge. */\n bottom: calc(var(--space-4) * -1 - var(--illustration-bleed));\n filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.12));\n transform: translate(-50%, 0);\n transition: transform 180ms cubic-bezier(0.2, 0, 0, 1);\n}\n\n.sketch-options__illustration--pen {\n --illustration-bleed: 9px;\n}\n\n.sketch-options__illustration--eraser {\n --illustration-bleed: 11px;\n}\n\n.sketch-options__illustrated:hover .sketch-options__illustration {\n transform: translate(-50%, calc(var(--space-4) * -1));\n}\n\n.tool-options[data-settled=\"true\"] .sketch-options__illustrated[data-active=\"true\"] .sketch-options__illustration {\n transform: translate(-50%, 0);\n}\n\n.sketch-options__illustrated:focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: -2px;\n}\n\n/* The rectangle shares the pen and eraser's quiet well instead of the accent active style. */\n.canvas-toolbar__tool.sketch-options__rectangle[data-active=\"true\"] {\n background: var(--surface-well);\n color: var(--text-primary);\n}\n\n.sketch-options__color {\n position: relative;\n display: flex;\n}\n\n.sketch-options__color-trigger {\n appearance: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--control-height-xlarge);\n height: var(--control-height-xlarge);\n padding: 0;\n border: 0;\n border-radius: var(--radius-control);\n background: transparent;\n color: var(--text-primary);\n cursor: pointer;\n transition: background-color 120ms ease;\n}\n\n.sketch-options__color-trigger:hover,\n.sketch-options__color-trigger[data-open=\"true\"] {\n background: var(--surface-well);\n}\n\n.sketch-options__swatch {\n display: block;\n /* Fades back in as the palette collapses onto it. */\n transition: opacity 120ms ease 150ms;\n width: 20px;\n height: 20px;\n border-radius: 50%;\n box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08);\n}\n\n/* The hexagon frames the trigger, so its swatch steps aside while the palette is open. */\n.sketch-options__color-trigger[data-open=\"true\"] .sketch-options__swatch {\n opacity: 0;\n transition-delay: 0s;\n}\n\n/* Colors pop out as a hexagon of overlapping swatches centered on the color trigger. */\n.sketch-options__palette {\n position: absolute;\n z-index: 1;\n left: 50%;\n top: 50%;\n width: 0;\n height: 0;\n}\n\n.sketch-options__palette-option {\n appearance: none;\n position: absolute;\n left: calc(var(--swatch-size) / -2);\n top: calc(var(--swatch-size) / -2);\n width: var(--swatch-size);\n height: var(--swatch-size);\n padding: 0;\n border: 0;\n border-radius: 50%;\n cursor: pointer;\n translate: var(--swatch-x) var(--swatch-y);\n box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08);\n transition:\n scale 120ms cubic-bezier(0.2, 0, 0, 1),\n box-shadow 120ms ease;\n animation: sketch-options-swatch-in 180ms cubic-bezier(0.2, 0, 0, 1) var(--swatch-delay, 0ms) backwards;\n}\n\n.sketch-options__palette-option:hover {\n scale: 1.05;\n}\n\n/* The selected swatch sits above its neighbors, separated from them by a light rim. */\n.sketch-options__palette-option[data-active=\"true\"] {\n box-shadow:\n inset 0 0 0 1px rgba(0, 0, 0, 0.08),\n 0 0 0 2px var(--surface-raised-2x),\n 0 2px 8px rgba(0, 0, 0, 0.2);\n}\n\n.sketch-options__palette-option:focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 2px;\n}\n\n.sketch-options__palette[data-state=\"closing\"] {\n pointer-events: none;\n}\n\n/* Closing plays the fan-out in reverse, last swatch first. */\n.sketch-options__palette[data-state=\"closing\"] .sketch-options__palette-option {\n animation: sketch-options-swatch-out 180ms cubic-bezier(0.2, 0, 0, 1) var(--swatch-delay-out, 0ms) forwards;\n}\n\n@keyframes sketch-options-swatch-out {\n to {\n opacity: 0;\n scale: 0.4;\n translate: 0 0;\n }\n}\n\n@keyframes sketch-options-swatch-in {\n from {\n opacity: 0;\n scale: 0.4;\n translate: 0 0;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sketch-options__illustrated::before,\n .sketch-options__illustration {\n transition: none;\n }\n\n .sketch-options__palette-option {\n animation: none;\n transition: none;\n }\n}\n";
43
47
  import { getActiveCanvasFrame } from "./activeCanvasDocument.js";
44
48
  import { startSketchCapture } from "../sketch/SketchWorkspace.js";
45
49
  import { cancelSketchInteraction, useSketchInteractionActive } from "../sketch/interaction.js";
@@ -48,7 +52,8 @@ import { createCommentCursor, DESIGN_SELECT_CURSOR, PAN_CURSOR } from "../ui/cus
48
52
  import { addLinkedFrame, createIteration as createWorkspaceIteration, removeFrame } from "../workspace/commands.js";
49
53
  import { getSelectedElements, setSelectedElement } from "../selection/selectionStore.js";
50
54
  import { setCommentToolActive } from "../comments/store.js";
51
- const WORKSPACE_STYLES = [foundationStyles, canvasWorkspaceStyles, canvasCardStyles, canvasToolbarStyles].join("\n");
55
+ import { InspectorStyle } from "../ui/InspectorStyle.js";
56
+ const WORKSPACE_STYLES = [foundationStyles, canvasWorkspaceStyles, canvasCardStyles, canvasToolbarStyles, sketchToolOptionsStyles].join("\n");
52
57
  const PRESENTATION_TRANSITION_MS = 200;
53
58
  function snapshotRect(rect) {
54
59
  return {
@@ -60,6 +65,7 @@ function snapshotRect(rect) {
60
65
  }
61
66
  export function CanvasWorkspace({ primaryUrl }) {
62
67
  const mode = useCanvasMode();
68
+ const pageSurface = isPageSurface();
63
69
  const runtimeConfig = useNudgeUiRuntimeConfig();
64
70
  const inspectorSession = useInspectorSession();
65
71
  const inspectorOpen = useInspectorOpen();
@@ -70,8 +76,10 @@ export function CanvasWorkspace({ primaryUrl }) {
70
76
  const selectedCardId = useSelectedCardId();
71
77
  const focusedCardId = useFocusedCardId();
72
78
  const sketchActive = useSketchInteractionActive();
73
- const sketchEnabled = isNudgeUiDev() && runtimeConfig.demo !== true;
79
+ const sketchEnabled = isNudgeUiDev()
80
+ && (runtimeConfig.demo !== true || isNudgeUiFeatureEnabled(runtimeConfig.features, "sketch"));
74
81
  const canvasIterationsEnabled = isNudgeUiFeatureEnabled(runtimeConfig.features, "canvasIterations");
82
+ useSyncExternalStore(subscribeFrameRegistry, getFrameRegistryVersion);
75
83
  const sketchAvailable = sketchEnabled && getActiveCanvasFrame() !== null;
76
84
  const boardRef = useRef(null);
77
85
  useCanvasLayoutAnimation(boardRef, cards, camera, presentation);
@@ -81,16 +89,17 @@ export function CanvasWorkspace({ primaryUrl }) {
81
89
  const framePanContextRef = useRef(null);
82
90
  const spaceHeldRef = useRef(false);
83
91
  const fitAllScheduledRef = useRef(false);
84
- const activatedTargetRef = useRef(null);
92
+ // Track the consumed primaryUrl prop separately from navigation inside a frame.
93
+ const activatedPrimaryUrlRef = useRef(null);
85
94
  const demoSeededRef = useRef(false);
86
95
  const previousPresentationRef = useRef(presentation);
87
96
  const presentationAnimationRef = useRef(null);
88
97
  const [boardCursorClass, setBoardCursorClass] = useState("");
89
- const [selectedTool, setInteractionTool] = useState("design");
98
+ const [selectedTool, setInteractionTool] = useState(runtimeConfig.demo === true ? "select" : "design");
90
99
  const temporaryAppInteraction = useTemporaryAppInteraction();
91
100
  const interactionTool = temporaryAppInteraction ? "select" : selectedTool;
92
101
  const layoutTransitioning = useCanvasLayoutTransitioning();
93
- const rendererInteractionsEnabled = interactionTool !== "select" && !layoutTransitioning;
102
+ const rendererInteractionsEnabled = interactionTool !== "select" && !layoutTransitioning && !sketchActive;
94
103
  const rendererCursor = interactionTool === "comment" ? "comment" : "design";
95
104
  const boardCursor = boardCursorClass === "is-grabbing"
96
105
  ? "grabbing"
@@ -157,9 +166,9 @@ export function CanvasWorkspace({ primaryUrl }) {
157
166
  const board = boardRef.current;
158
167
  if (!primaryUrl || !board)
159
168
  return;
160
- if (activatedTargetRef.current === primaryUrl && cards.length > 0)
169
+ if (activatedPrimaryUrlRef.current === primaryUrl && cards.length > 0)
161
170
  return;
162
- activatedTargetRef.current = primaryUrl;
171
+ activatedPrimaryUrlRef.current = primaryUrl;
163
172
  const viewport = {
164
173
  width: board.clientWidth,
165
174
  height: board.clientHeight,
@@ -219,7 +228,9 @@ export function CanvasWorkspace({ primaryUrl }) {
219
228
  }
220
229
  }, [primaryUrl, cards.length, presentation, runtimeConfig.demo, runtimeConfig.demoPages, runtimeConfig.demoCardLabels]);
221
230
  useEffect(() => {
222
- if (primaryUrl && activatedTargetRef.current !== primaryUrl)
231
+ if (pageSurface)
232
+ return;
233
+ if (primaryUrl && activatedPrimaryUrlRef.current !== primaryUrl)
223
234
  return;
224
235
  const activeCardId = selectedCardId ?? focusedCardId ?? cards[0]?.id;
225
236
  const activeCard = cards.find((card) => card.id === activeCardId);
@@ -230,63 +241,31 @@ export function CanvasWorkspace({ primaryUrl }) {
230
241
  window.history.replaceState(window.history.state, "", editorUrl);
231
242
  }
232
243
  }, [cards, focusedCardId, primaryUrl, selectedCardId]);
233
- const sendPanModifier = useCallback((iframe, cardId, spaceHeld) => {
234
- iframe.contentWindow?.postMessage({
235
- type: "pan-modifier",
236
- protocolVersion: PROTOCOL_VERSION,
237
- projectId: PROJECT_ID,
238
- workspaceId: WORKSPACE_ID,
239
- cardId,
240
- spaceHeld,
241
- cursor: spaceHeld ? "drag" : rendererCursor,
242
- }, window.location.origin);
243
- }, [rendererCursor]);
244
- const sendInspectorInteractionState = useCallback((iframe, cardId, open, interactionsEnabled, cursor) => {
245
- iframe.contentWindow?.postMessage({
244
+ const sendInspectorInteractionState = useCallback((frame, open, interactionsEnabled, cursor) => {
245
+ frame.post({
246
246
  type: "inspector-interaction-state",
247
- protocolVersion: PROTOCOL_VERSION,
248
- projectId: PROJECT_ID,
249
- workspaceId: WORKSPACE_ID,
250
- cardId,
251
247
  open,
252
248
  interactionsEnabled,
253
249
  cursor,
254
- commentCursor: cursor === "comment" ? createCommentCursor(boardRef.current) : undefined,
255
- }, window.location.origin);
256
- }, []);
257
- const sendLinkTargetState = useCallback((iframe, cardId, openInCard) => {
258
- iframe.contentWindow?.postMessage({
259
- type: "link-target-state",
260
- protocolVersion: PROTOCOL_VERSION,
261
- projectId: PROJECT_ID,
262
- workspaceId: WORKSPACE_ID,
263
- cardId,
264
- openInCard,
265
- }, window.location.origin);
250
+ ...(cursor === "comment" ? { commentCursor: createCommentCursor(boardRef.current) } : {}),
251
+ });
266
252
  }, []);
267
- const sendBoardGestureState = useCallback((iframe, cardId, enabled) => {
268
- iframe.contentWindow?.postMessage({
269
- type: "board-gesture-state",
270
- protocolVersion: PROTOCOL_VERSION,
271
- projectId: PROJECT_ID,
272
- workspaceId: WORKSPACE_ID,
273
- cardId,
274
- enabled,
275
- panScroll: enabled && interactionTool !== "select",
276
- }, window.location.origin);
253
+ const sendBoardGestureState = useCallback((frame, enabled) => {
254
+ frame.post({ type: "board-gesture-state", enabled, panScroll: enabled && interactionTool !== "select" });
277
255
  }, [interactionTool]);
278
256
  useEffect(() => {
279
257
  for (const [cardId, iframe] of getRegisteredFrames()) {
280
- sendInspectorInteractionState(iframe, cardId, inspectorOpen, rendererInteractionsEnabled, rendererCursor);
281
- sendBoardGestureState(iframe, cardId, presentation === "canvas");
282
- sendLinkTargetState(iframe, cardId, linksOpenInCards);
258
+ const frame = connectFrame(cardId, iframe);
259
+ sendInspectorInteractionState(frame, inspectorOpen, rendererInteractionsEnabled, rendererCursor);
260
+ sendBoardGestureState(frame, presentation === "canvas");
261
+ frame.post({ type: "link-target-state", openInCard: linksOpenInCards });
283
262
  }
284
- }, [inspectorOpen, linksOpenInCards, presentation, rendererCursor, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState, sendLinkTargetState]);
263
+ }, [inspectorOpen, linksOpenInCards, presentation, rendererCursor, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState]);
285
264
  const broadcastPanModifier = useCallback((spaceHeld) => {
286
265
  for (const [cardId, iframe] of getRegisteredFrames()) {
287
- sendPanModifier(iframe, cardId, spaceHeld);
266
+ connectFrame(cardId, iframe).post({ type: "pan-modifier", spaceHeld });
288
267
  }
289
- }, [sendPanModifier]);
268
+ }, []);
290
269
  const zoomAtPointer = useCallback((point, deltaY) => {
291
270
  const board = boardRef.current;
292
271
  if (!board)
@@ -347,7 +326,7 @@ export function CanvasWorkspace({ primaryUrl }) {
347
326
  if (nextTool === "sketch") {
348
327
  if (!sketchEnabled)
349
328
  return;
350
- const hostElement = getActiveCanvasFrame();
329
+ const hostElement = canvasFrameHost(getActiveCanvasFrame());
351
330
  if (!hostElement)
352
331
  return;
353
332
  stopPanMode();
@@ -413,6 +392,8 @@ export function CanvasWorkspace({ primaryUrl }) {
413
392
  return () => window.removeEventListener("keydown", onKeyDown, true);
414
393
  }, [handleToolChange, mode, sketchAvailable]);
415
394
  const handleZoomStep = useCallback((direction) => {
395
+ if (sketchActive)
396
+ return;
416
397
  if (presentation === "focus") {
417
398
  if (direction < 0) {
418
399
  batchCanvasChanges(() => {
@@ -437,7 +418,7 @@ export function CanvasWorkspace({ primaryUrl }) {
437
418
  if (nextZoom === null)
438
419
  return;
439
420
  setBoardCamera({ ...getBoardCamera(), zoom: nextZoom });
440
- }, [presentation]);
421
+ }, [presentation, sketchActive]);
441
422
  const showCanvas = useCallback(() => {
442
423
  handleZoomStep(-1);
443
424
  }, [handleZoomStep]);
@@ -476,7 +457,7 @@ export function CanvasWorkspace({ primaryUrl }) {
476
457
  broadcastPanModifier(false);
477
458
  }, [broadcastPanModifier, endPanning, presentation]);
478
459
  useEffect(() => {
479
- const stopChanges = subscribeChanges(() => {
460
+ const stopChanges = subscribeActiveDraftChanges(() => {
480
461
  projectToAllReadyCards();
481
462
  });
482
463
  const stopPreview = subscribeOriginalPreview(() => {
@@ -488,7 +469,8 @@ export function CanvasWorkspace({ primaryUrl }) {
488
469
  };
489
470
  }, []);
490
471
  useEffect(() => {
491
- return subscribeCanvasRendererMessages(({ cardId, iframe, identity, message: msg }) => {
472
+ return subscribeCanvasRendererMessages(({ frame, message: msg }) => {
473
+ const { identity } = frame;
492
474
  if (msg.type === "renderer-hello")
493
475
  return;
494
476
  if (msg.type === "app-interaction-modifier") {
@@ -504,13 +486,11 @@ export function CanvasWorkspace({ primaryUrl }) {
504
486
  }
505
487
  return;
506
488
  }
507
- const frame = { cardId, iframe };
508
489
  const syncActiveFrameUrl = (url) => {
509
490
  if (getCanvasCards().some((card) => card.id === frame.cardId && iterationId(card.content)))
510
491
  return;
511
492
  const activeCardId = getSelectedCardId() ?? getFocusedCardId() ?? getCanvasCards()[0]?.id;
512
- if (activeCardId === frame.cardId || getCanvasCards().length === 1) {
513
- activatedTargetRef.current = url;
493
+ if (!pageSurface && (activeCardId === frame.cardId || getCanvasCards().length === 1)) {
514
494
  window.history.replaceState(window.history.state, "", createNudgeUiEditorUrl(url));
515
495
  }
516
496
  updateCardUrl(frame.cardId, url);
@@ -521,7 +501,7 @@ export function CanvasWorkspace({ primaryUrl }) {
521
501
  // the page without inspector changes; skip conflict verification.
522
502
  if (isOriginalPreviewActive())
523
503
  return;
524
- const frameDocument = frame.iframe.contentDocument;
504
+ const frameDocument = frame.document();
525
505
  const plan = getCanonicalFrameProjection(frame.cardId, msg.revision);
526
506
  const draft = getDraftForCard(frame.cardId);
527
507
  if (frameDocument && plan && draft && isCanvasCanonicalProjectionRevisionCurrent(frameDocument, msg.revision)) {
@@ -571,10 +551,10 @@ export function CanvasWorkspace({ primaryUrl }) {
571
551
  syncActiveFrameUrl(msg.url);
572
552
  // A card can finish loading after Space was pressed on the controller.
573
553
  // Seed it with the current modifier state before its first pointer event.
574
- sendPanModifier(frame.iframe, frame.cardId, spaceHeldRef.current);
575
- sendInspectorInteractionState(frame.iframe, frame.cardId, inspectorOpen, rendererInteractionsEnabled, spaceHeldRef.current ? "drag" : rendererCursor);
576
- sendBoardGestureState(frame.iframe, frame.cardId, presentation === "canvas");
577
- sendLinkTargetState(frame.iframe, frame.cardId, linksOpenInCards);
554
+ frame.post({ type: "pan-modifier", spaceHeld: spaceHeldRef.current });
555
+ sendInspectorInteractionState(frame, inspectorOpen, rendererInteractionsEnabled, spaceHeldRef.current ? "drag" : rendererCursor);
556
+ sendBoardGestureState(frame, presentation === "canvas");
557
+ frame.post({ type: "link-target-state", openInCard: linksOpenInCards });
578
558
  }
579
559
  if (msg.type === "pan-modifier") {
580
560
  if (presentation !== "canvas")
@@ -587,34 +567,25 @@ export function CanvasWorkspace({ primaryUrl }) {
587
567
  return;
588
568
  }
589
569
  if (msg.type === "wheel-pan") {
590
- if (presentation === "canvas" && interactionTool !== "select")
570
+ if (presentation === "canvas" && !sketchActive && interactionTool !== "select")
591
571
  panCanvasByWheel(msg.deltaX, msg.deltaY);
592
572
  return;
593
573
  }
594
574
  if (msg.type === "zoom") {
595
- if (presentation !== "canvas")
575
+ if (presentation !== "canvas" || sketchActive)
596
576
  return;
597
- const iframeRect = frame.iframe.getBoundingClientRect();
598
- const point = iframePointToClientPoint(iframeRect, msg.point, getBoardCamera().zoom);
599
- zoomAtPointer(point, msg.deltaY);
577
+ zoomAtPointer(toViewportPoint(frame.viewport(), msg.point), msg.deltaY);
600
578
  return;
601
579
  }
580
+ // The pan moves the frame, so every pan point maps through the viewport captured at pan start.
602
581
  const pointInBoard = (point) => {
603
582
  const context = framePanContextRef.current;
604
- if (!context)
605
- return null;
606
- return {
607
- x: context.left + point.x * context.zoom,
608
- y: context.top + point.y * context.zoom,
609
- };
583
+ return context ? toViewportPoint(context, point) : null;
610
584
  };
611
585
  if (msg.type === "pan-start") {
612
- const iframeRect = frame.iframe.getBoundingClientRect();
613
- framePanContextRef.current = {
614
- left: iframeRect.left,
615
- top: iframeRect.top,
616
- zoom: getBoardCamera().zoom,
617
- };
586
+ if (sketchActive)
587
+ return;
588
+ framePanContextRef.current = frame.viewport();
618
589
  const point = pointInBoard(msg.point);
619
590
  if (point)
620
591
  startPanning(point);
@@ -632,7 +603,7 @@ export function CanvasWorkspace({ primaryUrl }) {
632
603
  return;
633
604
  }
634
605
  if (msg.type === "frame-metadata") {
635
- const frameDocument = frame.iframe.contentDocument;
606
+ const frameDocument = frame.document();
636
607
  if (frameDocument)
637
608
  disposeInlineTextEdit("route-disposed", frameDocument);
638
609
  if (msg.url)
@@ -663,11 +634,11 @@ export function CanvasWorkspace({ primaryUrl }) {
663
634
  activateIframeWorkspace(msg.url, { width: boardRef.current?.clientWidth ?? window.innerWidth, height: boardRef.current?.clientHeight ?? window.innerHeight }, { replaceActiveCard: true });
664
635
  return;
665
636
  }
666
- const frameDocument = frame.iframe.contentDocument;
637
+ const frameDocument = frame.document();
667
638
  if (frameDocument)
668
639
  disposeInlineTextEdit("route-disposed", frameDocument);
669
640
  });
670
- }, [broadcastPanModifier, endPanning, handleToolChange, interactionTool, mode, panCanvasByWheel, inspectorOpen, linksOpenInCards, movePanning, presentation, rendererCursor, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState, sendLinkTargetState, sendPanModifier, sketchAvailable, startPanning, zoomAtPointer]);
641
+ }, [broadcastPanModifier, endPanning, handleToolChange, interactionTool, mode, panCanvasByWheel, inspectorOpen, linksOpenInCards, movePanning, presentation, rendererCursor, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState, sketchActive, sketchAvailable, startPanning, zoomAtPointer]);
671
642
  useEffect(() => {
672
643
  if (mode === "canvas" && !hasFitAllRan()) {
673
644
  if (!fitAllScheduledRef.current) {
@@ -758,8 +729,13 @@ export function CanvasWorkspace({ primaryUrl }) {
758
729
  function handleGlobalWheel(e) {
759
730
  const zoom = e.ctrlKey || e.metaKey;
760
731
  const board = boardRef.current;
761
- if (zoom && sketchActive)
732
+ if (sketchActive) {
733
+ if (e.composedPath().some((node) => node instanceof Element && node.closest(".sketch__prompt-panel, .sketch__annotation-editor, .panel")))
734
+ return;
735
+ e.preventDefault();
736
+ e.stopPropagation();
762
737
  return;
738
+ }
763
739
  const path = e.composedPath();
764
740
  // Comment pins overlay the board without being its DOM descendants.
765
741
  const overCommentPin = path.some((node) => node instanceof Element && node.matches(".comment-pin"));
@@ -777,10 +753,11 @@ export function CanvasWorkspace({ primaryUrl }) {
777
753
  window.addEventListener("wheel", handleGlobalWheel, { capture: true, passive: false });
778
754
  return () => window.removeEventListener("wheel", handleGlobalWheel, { capture: true });
779
755
  }, [mode, presentation, sketchActive, interactionTool, panCanvasByWheel, zoomAtPointer]);
780
- const renderCard = (card) => (_jsx(CanvasCard, { card: card, presentation: presentation, presentationCard: card.id === presentationCardId, linkedToActive: linkedFrameIds.includes(card.id), onShowFocus: showFocus, onDuplicate: duplicateDesign, onCreateIteration: canvasIterationsEnabled ? createIteration : undefined, documentOwner: inspectorSession }, card.id));
756
+ // Every page card would share the one page renderer, so restored extra cards stay unmounted.
757
+ const renderCard = (card) => pageSurface ? (card.id === presentationCardId ? _jsx(PageCard, { card: card, documentOwner: inspectorSession }, card.id) : null) : (_jsx(CanvasCard, { card: card, presentation: presentation, presentationCard: card.id === presentationCardId, linkedToActive: linkedFrameIds.includes(card.id), onShowFocus: showFocus, onDuplicate: duplicateDesign, onCreateIteration: canvasIterationsEnabled ? createIteration : undefined, documentOwner: inspectorSession }, card.id));
781
758
  if (mode !== "canvas")
782
759
  return null;
783
- return (_jsxs(_Fragment, { children: [_jsx("style", { "data-test": "canvas-styles", children: WORKSPACE_STYLES }), _jsx("div", { className: `canvas-workspace${presentationTransitioning ? " is-presentation-transitioning" : ""}`, "data-test": "canvas-workspace", "data-presentation": presentation, style: { right: inspectorOpen ? "min(320px, 100vw)" : 0 }, children: _jsxs("div", { className: `canvas-workspace__board ${boardCursorClass}${layoutTransitioning ? " is-layout-transitioning" : ""}`.trim(), "data-test": "canvas-board", ref: boardRef, style: { cursor: boardCursor }, onPointerDown: handleBoardPointerDown, onClick: handleBoardClick, children: [!primaryUrl ? (_jsx("div", { className: "canvas-workspace__target-error", "data-test": "canvas-target-error", children: "This editor URL does not identify an application page." })) : null, inspectorOpen && presentation === "focus" ? (_jsx("div", { className: "canvas-workspace__focus-action", "data-test": "canvas-show-canvas", children: _jsxs(Button, { variant: "quiet", size: "default", type: "button", onClick: showCanvas, children: [_jsx(IconCornerLeftUp, { size: "var(--icon-size-small)", stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), "Canvas"] }) })) : null, _jsx("div", { className: "canvas-workspace__board-content", style: {
760
+ return (_jsxs(_Fragment, { children: [_jsx(InspectorStyle, { testId: "canvas-styles", css: WORKSPACE_STYLES }), _jsx("div", { className: `canvas-workspace${presentationTransitioning ? " is-presentation-transitioning" : ""}`, "data-test": "canvas-workspace", "data-presentation": presentation, "data-surface": pageSurface ? "page" : undefined, style: { right: inspectorOpen ? "min(320px, 100vw)" : 0 }, children: _jsxs("div", { className: `canvas-workspace__board ${boardCursorClass}${layoutTransitioning ? " is-layout-transitioning" : ""}`.trim(), "data-test": "canvas-board", ref: boardRef, style: { cursor: boardCursor }, onPointerDown: handleBoardPointerDown, onClick: handleBoardClick, children: [!primaryUrl ? (_jsx("div", { className: "canvas-workspace__target-error", "data-test": "canvas-target-error", children: "This editor URL does not identify an application page." })) : null, inspectorOpen && presentation === "focus" && !pageSurface ? (_jsx("div", { className: "canvas-workspace__focus-action", "data-test": "canvas-show-canvas", children: _jsxs(Button, { variant: "quiet", size: "default", type: "button", onClick: showCanvas, children: [_jsx(IconCornerLeftUp, { size: "var(--icon-size-small)", stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), "Canvas"] }) })) : null, _jsx("div", { className: "canvas-workspace__board-content", style: {
784
761
  transform: `translate(${camera.x}px, ${camera.y}px) scale(${camera.zoom})`,
785
762
  transformOrigin: "0 0",
786
763
  }, "data-test": "canvas-board-content", children: cards.map(renderCard) }), inspectorOpen && !sketchActive ? (_jsx(CanvasToolbar, { tool: interactionTool, sketchEnabled: sketchAvailable, onToolChange: handleToolChange })) : null] }) }), inspectorOpen && sketchActive ? (_jsx("div", { className: "canvas-toolbar__portal", "data-test": "canvas-toolbar-portal", style: { right: inspectorOpen ? "min(320px, 100vw)" : 0 }, children: _jsx(CanvasToolbar, { tool: interactionTool, sketchEnabled: sketchAvailable, onToolChange: handleToolChange }) })) : null] }));
@@ -1,6 +1,7 @@
1
1
  import type { ReactElement } from "react";
2
2
  interface LockedWorkspaceNoticeProps {
3
- onTakeover: () => void;
3
+ onTakeover: () => void | Promise<void>;
4
+ storageError?: boolean;
4
5
  }
5
- export declare function LockedWorkspaceNotice({ onTakeover }: LockedWorkspaceNoticeProps): ReactElement;
6
+ export declare function LockedWorkspaceNotice({ onTakeover, storageError }: LockedWorkspaceNoticeProps): ReactElement;
6
7
  export {};
@@ -4,14 +4,18 @@ import { requestTakeover } from "./workspaceLease.js";
4
4
  import { Button } from "../ui/Button.js";
5
5
  import { UI_STYLES } from "../ui/styles.js";
6
6
  const lockedWorkspaceStyles = ".locked-notice {\n position: fixed;\n top: max(12px, env(safe-area-inset-top));\n right: max(12px, env(safe-area-inset-right));\n width: min(320px, calc(100vw - 24px));\n padding: 18px;\n border-radius: 12px;\n background: var(--surface-raised-2x);\n box-shadow: var(--shadow-popup);\n color: var(--text-primary);\n font-family: var(--font-family);\n pointer-events: auto;\n}\n\n.locked-notice__message {\n margin-bottom: 14px;\n font-size: 14px;\n font-weight: 650;\n}\n";
7
- export function LockedWorkspaceNotice({ onTakeover }) {
7
+ import { InspectorStyle } from "../ui/InspectorStyle.js";
8
+ export function LockedWorkspaceNotice({ onTakeover, storageError = false }) {
8
9
  const [takingOver, setTakingOver] = useState(false);
9
- function handleTakeover() {
10
+ async function handleTakeover() {
10
11
  setTakingOver(true);
11
- if (requestTakeover())
12
- onTakeover();
13
- else
12
+ try {
13
+ if (requestTakeover())
14
+ await onTakeover();
15
+ }
16
+ finally {
14
17
  setTakingOver(false);
18
+ }
15
19
  }
16
- return (_jsxs(_Fragment, { children: [_jsx("style", { "data-test": "locked-workspace-styles", children: `${UI_STYLES}\n${lockedWorkspaceStyles}` }), _jsxs("aside", { className: "locked-notice", "data-test": "locked-workspace-notice", "aria-live": "polite", children: [_jsx("div", { className: "locked-notice__message", children: "Nudge UI is open in another tab" }), _jsx(Button, { "data-test": "takeover-here", disabled: takingOver, onClick: handleTakeover, children: takingOver ? "Taking over..." : "Take Over Here" })] })] }));
20
+ return (_jsxs(_Fragment, { children: [_jsx(InspectorStyle, { testId: "locked-workspace-styles", css: `${UI_STYLES}\n${lockedWorkspaceStyles}` }), _jsxs("aside", { className: "locked-notice", "data-test": "locked-workspace-notice", "aria-live": "polite", children: [_jsx("div", { className: "locked-notice__message", children: storageError ? "Can’t load saved changes. Try again." : "Nudge UI is open in another tab" }), _jsx(Button, { "data-test": "takeover-here", disabled: takingOver, onClick: handleTakeover, children: takingOver ? "Taking over..." : "Take Over Here" })] })] }));
17
21
  }
@@ -0,0 +1,10 @@
1
+ import { type ReactElement } from "react";
2
+ import type { CanvasCard } from "./canvasStore.js";
3
+ import type { InspectorSession } from "../session/sessionFactory.js";
4
+ interface PageCardProps {
5
+ card: CanvasCard;
6
+ documentOwner?: InspectorSession;
7
+ }
8
+ /** The page-surface Canvas card: the inspected page is the renderer. */
9
+ export declare function PageCard({ card, documentOwner }: PageCardProps): ReactElement;
10
+ export {};
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { connectFrame } from "./frameConnection.js";
4
+ import { contentSourceUrl } from "./frameContent.js";
5
+ import { getPageFrame } from "./pageSurface.js";
6
+ import { registerCardFrame, registerCardFrameSource, sendProjectionToCard, unregisterCardFrame } from "./projection.js";
7
+ import { subscribeCanvasRendererMessages } from "./rendererMessageRouter.js";
8
+ import { disposeBrowserCssInspection } from "../inspection/browserCssInspectionRegistry.js";
9
+ import { releaseDocumentProjection } from "../projection/structuralProjection.js";
10
+ import { disposeRuntimeIdentityClaims } from "../runtime/runtimeIdentityClaims.js";
11
+ import { disposeInlineTextEdit } from "../inline-text/inlineTextEditor.js";
12
+ import { startClipboardHandoffController } from "../prompt/clipboardHandoff.js";
13
+ import { SketchFrameOverlay } from "../sketch/SketchFrameOverlay.js";
14
+ /** The page-surface Canvas card: the inspected page is the renderer. */
15
+ export function PageCard({ card, documentOwner }) {
16
+ const cardRef = useRef(card);
17
+ cardRef.current = card;
18
+ const [ready, setReady] = useState(false);
19
+ const frame = getPageFrame();
20
+ useEffect(() => {
21
+ registerCardFrameSource(card.id, frame);
22
+ const session = documentOwner?.createDocumentSession(document);
23
+ session?.registerCleanup(() => disposeBrowserCssInspection(document));
24
+ session?.registerCleanup(() => releaseDocumentProjection(document));
25
+ session?.registerCleanup(() => disposeRuntimeIdentityClaims(document));
26
+ session?.registerCleanup(() => disposeInlineTextEdit("frame-disposed", document));
27
+ session?.registerCleanup(startClipboardHandoffController(document));
28
+ const announce = () => connectFrame(card.id, frame).post({ type: "parent-ready" });
29
+ const unsubscribe = subscribeCanvasRendererMessages(({ frame: connection, message }) => {
30
+ if (connection.cardId !== card.id)
31
+ return;
32
+ if (message.type === "renderer-hello") {
33
+ announce();
34
+ return;
35
+ }
36
+ if (message.type === "frame-ready") {
37
+ registerCardFrame(card.id, frame);
38
+ sendProjectionToCard(cardRef.current, frame);
39
+ setReady(true);
40
+ }
41
+ });
42
+ announce();
43
+ return () => {
44
+ unsubscribe();
45
+ session?.dispose();
46
+ unregisterCardFrame(card.id);
47
+ setReady(false);
48
+ };
49
+ }, [card.id, documentOwner, frame]);
50
+ return (_jsx(SketchFrameOverlay, { cardId: card.id, iframe: frame, cardUrl: contentSourceUrl(card.content), ready: ready }));
51
+ }
@@ -0,0 +1,5 @@
1
+ import type { ReactElement } from "react";
2
+ /** Pen, eraser, color and rectangle controls that slide out beside the sketch tool. */
3
+ export declare function SketchToolOptions({ open }: {
4
+ readonly open: boolean;
5
+ }): ReactElement;