nudge-ui 0.2.3 → 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 (264) hide show
  1. package/dist/cli/agentCommand.js +4 -28
  2. package/dist/client.mjs +870 -212
  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 +5 -7
  16. package/dist/inspector/agent/verification.js +67 -20
  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 +23 -12
  22. package/dist/inspector/canvas/CanvasElementOverlay.js +97 -128
  23. package/dist/inspector/canvas/CanvasToolbar.js +2 -1
  24. package/dist/inspector/canvas/CanvasWorkspace.js +119 -162
  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/frameScrollbarStyles.d.ts +3 -0
  49. package/dist/inspector/canvas/frameScrollbarStyles.js +21 -0
  50. package/dist/inspector/canvas/pageSurface.d.ts +11 -0
  51. package/dist/inspector/canvas/pageSurface.js +34 -0
  52. package/dist/inspector/canvas/projection.d.ts +19 -9
  53. package/dist/inspector/canvas/projection.js +58 -31
  54. package/dist/inspector/canvas/rendererBootstrap.js +4 -1
  55. package/dist/inspector/canvas/rendererElementSelector.js +9 -1
  56. package/dist/inspector/canvas/rendererMessageRouter.d.ts +4 -5
  57. package/dist/inspector/canvas/rendererMessageRouter.js +12 -16
  58. package/dist/inspector/canvas/rendererSelectionProxy.d.ts +7 -1
  59. package/dist/inspector/canvas/rendererSelectionProxy.js +8 -36
  60. package/dist/inspector/canvas/rendererStylesheet.js +15 -0
  61. package/dist/inspector/canvas/sessionStore.d.ts +2 -2
  62. package/dist/inspector/canvas/sessionStore.js +12 -9
  63. package/dist/inspector/canvas/staleChangeDetector.d.ts +1 -1
  64. package/dist/inspector/canvas/staleChangeDetector.js +10 -22
  65. package/dist/inspector/changes/codecs.d.ts +2 -0
  66. package/dist/inspector/changes/codecs.js +27 -3
  67. package/dist/inspector/changes/draftPreview.d.ts +12 -0
  68. package/dist/inspector/changes/draftPreview.js +133 -0
  69. package/dist/inspector/changes/editModel.d.ts +18 -0
  70. package/dist/inspector/changes/editModel.js +16 -3
  71. package/dist/inspector/changes/managedStyleProjection.js +2 -1
  72. package/dist/inspector/changes/previewDiagnostics.js +1 -1
  73. package/dist/inspector/changes/types.d.ts +18 -1
  74. package/dist/inspector/comments/CommentsOverlay.js +18 -16
  75. package/dist/inspector/comments/element.js +10 -3
  76. package/dist/inspector/comments/store.js +3 -2
  77. package/dist/inspector/comments/verification.js +1 -1
  78. package/dist/inspector/componentSemantics/ComponentPropsSection.js +2 -1
  79. package/dist/inspector/componentSemantics/adapterRegistry.d.ts +6 -1
  80. package/dist/inspector/componentSemantics/adapterRegistry.js +23 -1
  81. package/dist/inspector/componentSemantics/changeAction.js +1 -1
  82. package/dist/inspector/componentSemantics/reactRuntime.d.ts +2 -1
  83. package/dist/inspector/componentSemantics/reactRuntime.js +38 -0
  84. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +4 -2
  85. package/dist/inspector/componentSemantics/runtimeBridge.js +17 -0
  86. package/dist/inspector/componentSemantics/textBinding.d.ts +1 -2
  87. package/dist/inspector/componentSemantics/textBinding.js +4 -45
  88. package/dist/inspector/componentSemantics/types.d.ts +11 -0
  89. package/dist/inspector/drafts/draftStore.d.ts +109 -0
  90. package/dist/inspector/drafts/draftStore.js +451 -0
  91. package/dist/inspector/drafts/model.d.ts +9 -1
  92. package/dist/inspector/drafts/model.js +5 -1
  93. package/dist/inspector/drafts/store.d.ts +53 -15
  94. package/dist/inspector/drafts/store.js +143 -169
  95. package/dist/inspector/index.d.ts +2 -4
  96. package/dist/inspector/index.js +2 -264
  97. package/dist/inspector/inline-text/inlineTextEditor.d.ts +1 -1
  98. package/dist/inspector/inline-text/inlineTextEditor.js +1 -2
  99. package/dist/inspector/inline-text/textEditChange.d.ts +1 -1
  100. package/dist/inspector/inspection/bridge.d.ts +1 -1
  101. package/dist/inspector/inspection/bridge.js +2 -1
  102. package/dist/inspector/inspection/browserCssInspection.d.ts +8 -0
  103. package/dist/inspector/inspection/browserCssInspection.js +28 -6
  104. package/dist/inspector/inspection/inspectionSnapshot.d.ts +6 -0
  105. package/dist/inspector/inspection/inspectionSnapshot.js +10 -0
  106. package/dist/inspector/inspection/performanceDiagnostics.d.ts +18 -0
  107. package/dist/inspector/inspection/performanceDiagnostics.js +31 -0
  108. package/dist/inspector/inspection/useBrowserCssInspection.js +17 -38
  109. package/dist/inspector/iterations/capture.d.ts +2 -1
  110. package/dist/inspector/iterations/capture.js +2 -1
  111. package/dist/inspector/iterations/client.d.ts +2 -1
  112. package/dist/inspector/iterations/coordinator.d.ts +4 -3
  113. package/dist/inspector/iterations/coordinator.js +5 -3
  114. package/dist/inspector/iterations/prompt.js +9 -8
  115. package/dist/inspector/overlay/interactionStyles.js +17 -0
  116. package/dist/inspector/pageInspector.d.ts +42 -0
  117. package/dist/inspector/pageInspector.js +342 -0
  118. package/dist/inspector/projection/draftProjection.d.ts +10 -2
  119. package/dist/inspector/projection/draftProjection.js +9 -1
  120. package/dist/inspector/projection/managedStylesheet.js +24 -4
  121. package/dist/inspector/projection/renderedInstance.d.ts +15 -2
  122. package/dist/inspector/projection/renderedInstance.js +60 -7
  123. package/dist/inspector/projection/structuralProjection.d.ts +0 -9
  124. package/dist/inspector/projection/structuralProjection.js +16 -46
  125. package/dist/inspector/projection/textProjection.d.ts +8 -2
  126. package/dist/inspector/projection/textProjection.js +77 -24
  127. package/dist/inspector/prompt/clipboardHandoff.d.ts +14 -0
  128. package/dist/inspector/prompt/clipboardHandoff.js +47 -16
  129. package/dist/inspector/prompt/generatePrompt.d.ts +1 -1
  130. package/dist/inspector/prompt/generatePrompt.js +248 -148
  131. package/dist/inspector/prompt/promptDocument.d.ts +3 -0
  132. package/dist/inspector/prompt/promptDocument.js +8 -0
  133. package/dist/inspector/prompt/promptSettings.js +7 -5
  134. package/dist/inspector/runtime/inspectorStorage.d.ts +17 -0
  135. package/dist/inspector/runtime/inspectorStorage.js +42 -0
  136. package/dist/inspector/runtime/ownedStylesheets.d.ts +20 -0
  137. package/dist/inspector/runtime/ownedStylesheets.js +72 -0
  138. package/dist/inspector/runtime/runtimeConfig.d.ts +4 -0
  139. package/dist/inspector/runtime/runtimeConfig.js +10 -1
  140. package/dist/inspector/runtime/runtimeIdentityClaims.d.ts +29 -0
  141. package/dist/inspector/runtime/runtimeIdentityClaims.js +188 -0
  142. package/dist/inspector/runtime/staticHtmlRuntimeIdentity.d.ts +15 -0
  143. package/dist/inspector/runtime/staticHtmlRuntimeIdentity.js +113 -29
  144. package/dist/inspector/selection/editScope.d.ts +1 -0
  145. package/dist/inspector/selection/editScope.js +13 -2
  146. package/dist/inspector/selection/sourceSite.d.ts +12 -0
  147. package/dist/inspector/selection/sourceSite.js +28 -0
  148. package/dist/inspector/shell/AgentStatusButton.js +2 -2
  149. package/dist/inspector/shell/AppShell.js +6 -2
  150. package/dist/inspector/shell/ChangesLog.js +27 -9
  151. package/dist/inspector/shell/CopyPromptButton.js +74 -26
  152. package/dist/inspector/shell/EmptyState.js +3 -3
  153. package/dist/inspector/shell/InspectorShell.d.ts +0 -3
  154. package/dist/inspector/shell/InspectorShell.js +21 -13
  155. package/dist/inspector/shell/PeekOriginalButton.js +3 -3
  156. package/dist/inspector/shell/interactionStatePreview.d.ts +9 -0
  157. package/dist/inspector/shell/interactionStatePreview.js +112 -0
  158. package/dist/inspector/shell/originalPreview.d.ts +3 -2
  159. package/dist/inspector/shell/originalPreview.js +10 -37
  160. package/dist/inspector/shell/stateValue.js +2 -3
  161. package/dist/inspector/sketch/SketchEditor.js +5 -4
  162. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +2 -1
  163. package/dist/inspector/sketch/SketchFrameOverlay.js +5 -4
  164. package/dist/inspector/sketch/SketchOverlay.d.ts +3 -5
  165. package/dist/inspector/sketch/SketchOverlay.js +139 -263
  166. package/dist/inspector/sketch/SketchPromptPanel.d.ts +1 -1
  167. package/dist/inspector/sketch/SketchPromptPanel.js +8 -1
  168. package/dist/inspector/sketch/SketchThumbnail.d.ts +5 -0
  169. package/dist/inspector/sketch/SketchThumbnail.js +18 -0
  170. package/dist/inspector/sketch/SketchWorkspace.d.ts +1 -2
  171. package/dist/inspector/sketch/SketchWorkspace.js +69 -34
  172. package/dist/inspector/sketch/capture.js +19 -3
  173. package/dist/inspector/sketch/copySketch.d.ts +10 -0
  174. package/dist/inspector/sketch/copySketch.js +52 -0
  175. package/dist/inspector/sketch/drawingTools.d.ts +17 -0
  176. package/dist/inspector/sketch/drawingTools.js +47 -0
  177. package/dist/inspector/sketch/frameHeading.d.ts +9 -0
  178. package/dist/inspector/sketch/frameHeading.js +30 -0
  179. package/dist/inspector/sketch/freehand.js +3 -3
  180. package/dist/inspector/sketch/geometry.d.ts +6 -0
  181. package/dist/inspector/sketch/geometry.js +19 -0
  182. package/dist/inspector/sketch/history.d.ts +30 -0
  183. package/dist/inspector/sketch/history.js +58 -0
  184. package/dist/inspector/sketch/interaction.d.ts +1 -3
  185. package/dist/inspector/sketch/interaction.js +6 -9
  186. package/dist/inspector/sketch/model.d.ts +35 -1
  187. package/dist/inspector/sketch/model.js +116 -61
  188. package/dist/inspector/sketch/persistence.d.ts +14 -0
  189. package/dist/inspector/sketch/persistence.js +63 -7
  190. package/dist/inspector/sketch/raster.d.ts +6 -2
  191. package/dist/inspector/sketch/raster.js +43 -17
  192. package/dist/inspector/sketch/store.d.ts +1 -1
  193. package/dist/inspector/sketch/store.js +1 -1
  194. package/dist/inspector/sketch/videoFrames.d.ts +6 -0
  195. package/dist/inspector/sketch/videoFrames.js +81 -0
  196. package/dist/inspector/styleEditors/BackgroundEditor.js +3 -2
  197. package/dist/inspector/styleEditors/BorderEditor.js +30 -130
  198. package/dist/inspector/styleEditors/BorderRadiusEditor.js +8 -3
  199. package/dist/inspector/styleEditors/ImageBackgroundEditor.js +1 -1
  200. package/dist/inspector/styleEditors/LayoutSection.js +2 -2
  201. package/dist/inspector/styleEditors/borderModel.d.ts +42 -0
  202. package/dist/inspector/styleEditors/borderModel.js +114 -0
  203. package/dist/inspector/styleEditors/layoutValue.js +2 -3
  204. package/dist/inspector/tokens/TokenField.js +40 -7
  205. package/dist/inspector/tokens/TokensPanel.js +2 -1
  206. package/dist/inspector/tokens/editActions.d.ts +3 -3
  207. package/dist/inspector/tokens/editActions.js +15 -3
  208. package/dist/inspector/tokens/resolution/containerQueryEvaluator.d.ts +15 -0
  209. package/dist/inspector/tokens/resolution/containerQueryEvaluator.js +86 -0
  210. package/dist/inspector/tokens/resolution/cssomCollector.js +44 -9
  211. package/dist/inspector/tokens/resolution/nestedSelectors.d.ts +6 -0
  212. package/dist/inspector/tokens/resolution/nestedSelectors.js +67 -0
  213. package/dist/inspector/tokens/resolution/ruleMatching.js +23 -4
  214. package/dist/inspector/tokens/resolution.d.ts +6 -3
  215. package/dist/inspector/tokens/resolution.js +91 -112
  216. package/dist/inspector/tokens/tokenEdits.d.ts +1 -1
  217. package/dist/inspector/tokens/tokenEdits.js +1 -1
  218. package/dist/inspector/ui/InlineStyleWarning.js +2 -2
  219. package/dist/inspector/ui/InspectorStyle.d.ts +8 -0
  220. package/dist/inspector/ui/InspectorStyle.js +26 -0
  221. package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
  222. package/dist/inspector/ui/PopoverListbox.js +3 -3
  223. package/dist/inspector/ui/Toast.d.ts +14 -0
  224. package/dist/inspector/ui/Toast.js +30 -0
  225. package/dist/inspector/ui/assets/canvasIllustration.d.ts +2 -0
  226. package/dist/inspector/ui/assets/canvasIllustration.js +21 -0
  227. package/dist/inspector/ui/assets/gridIllustration.d.ts +2 -0
  228. package/dist/inspector/ui/assets/{grid.svg → gridIllustration.js} +3 -2
  229. package/dist/inspector/ui/customCursors.d.ts +5 -0
  230. package/dist/inspector/ui/customCursors.js +32 -6
  231. package/dist/inspector/ui/styles.js +12 -10
  232. package/dist/inspector/workspace/commands.js +1 -2
  233. package/dist/inspector/workspace/controller.js +1 -2
  234. package/dist/inspector/workspace/handoff.js +3 -2
  235. package/dist/inspector/workspace/sentItems.d.ts +16 -0
  236. package/dist/inspector/workspace/sentItems.js +33 -0
  237. package/dist/inspector/workspace/timeline.d.ts +24 -10
  238. package/dist/inspector/workspace/timeline.js +118 -96
  239. package/dist/internal/agent-protocol.d.ts +50 -13
  240. package/dist/internal/agent-protocol.js +145 -19
  241. package/dist/nudge-ui.mjs +81 -65
  242. package/dist/project/mcpPackage.d.ts +8 -0
  243. package/dist/project/mcpPackage.js +60 -0
  244. package/package.json +13 -3
  245. package/dist/inspector/canvas/measurementProjection.d.ts +0 -8
  246. package/dist/inspector/canvas/measurementProjection.js +0 -16
  247. package/dist/inspector/changes/changesLog.d.ts +0 -47
  248. package/dist/inspector/changes/changesLog.js +0 -247
  249. package/dist/inspector/changes/draftChanges.d.ts +0 -56
  250. package/dist/inspector/changes/draftChanges.js +0 -300
  251. package/dist/inspector/conformance/projection.d.ts +0 -2
  252. package/dist/inspector/conformance/projection.js +0 -3
  253. package/dist/inspector/inline-text/textChangeBoundary.d.ts +0 -6
  254. package/dist/inspector/inline-text/textChangeBoundary.js +0 -1
  255. package/dist/inspector/overlay/InspectorOverlay.d.ts +0 -5
  256. package/dist/inspector/overlay/InspectorOverlay.js +0 -332
  257. package/dist/inspector/overlay/elementSelector.d.ts +0 -1
  258. package/dist/inspector/overlay/elementSelector.js +0 -111
  259. package/dist/inspector/sketch/SketchChanges.d.ts +0 -2
  260. package/dist/inspector/sketch/SketchChanges.js +0 -72
  261. package/dist/inspector/sketch/SketchEntryButton.d.ts +0 -4
  262. package/dist/inspector/sketch/SketchEntryButton.js +0 -11
  263. package/dist/inspector/sketch/SketchLayersPanel.d.ts +0 -2
  264. package/dist/inspector/sketch/SketchLayersPanel.js +0 -71
@@ -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.is-presentation-transitioning .canvas-workspace__board-content {\n transition: transform 200ms ease;\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.is-presentation-card {\n transition: left 200ms ease, top 200ms ease;\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@media (prefers-reduced-motion: reduce) {\n .canvas-workspace.is-presentation-transitioning .canvas-workspace__board-content,\n .canvas-workspace.is-presentation-transitioning .canvas-card.is-presentation-card {\n transition: none;\n }\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,17 +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
- const focusCardRectRef = useRef(null);
88
- const canvasEntryAnimationRef = useRef(null);
96
+ const presentationAnimationRef = useRef(null);
89
97
  const [boardCursorClass, setBoardCursorClass] = useState("");
90
- const [selectedTool, setInteractionTool] = useState("design");
98
+ const [selectedTool, setInteractionTool] = useState(runtimeConfig.demo === true ? "select" : "design");
91
99
  const temporaryAppInteraction = useTemporaryAppInteraction();
92
100
  const interactionTool = temporaryAppInteraction ? "select" : selectedTool;
93
101
  const layoutTransitioning = useCanvasLayoutTransitioning();
94
- const rendererInteractionsEnabled = interactionTool !== "select" && !layoutTransitioning;
102
+ const rendererInteractionsEnabled = interactionTool !== "select" && !layoutTransitioning && !sketchActive;
95
103
  const rendererCursor = interactionTool === "comment" ? "comment" : "design";
96
104
  const boardCursor = boardCursorClass === "is-grabbing"
97
105
  ? "grabbing"
@@ -106,80 +114,61 @@ export function CanvasWorkspace({ primaryUrl }) {
106
114
  const presentationCardId = selectedCardId ?? focusedCardId ?? cards[0]?.id ?? null;
107
115
  const linkedFrameIds = getLinkedFrameIds(cards, selectedCardId);
108
116
  useLayoutEffect(() => {
117
+ const previousPresentation = previousPresentationRef.current;
118
+ previousPresentationRef.current = presentation;
119
+ if (previousPresentation === presentation)
120
+ return;
121
+ presentationAnimationRef.current?.cancel();
122
+ presentationAnimationRef.current = null;
123
+ if (!presentationTransitioning)
124
+ return;
109
125
  const board = boardRef.current;
126
+ const card = cards.find((candidate) => candidate.id === presentationCardId);
110
127
  const cardElement = presentationCardId
111
128
  ? board?.querySelector(`[data-card-id="${presentationCardId}"]`)
112
129
  : null;
113
- const boardContent = board?.querySelector('[data-test="canvas-board-content"]');
114
- if (presentation === "focus") {
115
- canvasEntryAnimationRef.current?.cancel();
116
- canvasEntryAnimationRef.current = null;
117
- if (boardContent)
118
- boardContent.style.transition = "";
119
- if (cardElement)
120
- cardElement.style.transition = "";
121
- focusCardRectRef.current = cardElement ? snapshotRect(cardElement.getBoundingClientRect()) : null;
122
- }
123
- else if (presentation === "canvas"
124
- && previousPresentationRef.current === "focus"
125
- && presentationTransitioning
126
- && cardElement
127
- && boardContent
128
- && focusCardRectRef.current
129
- && typeof cardElement.animate === "function") {
130
- // Commit the final layout before measuring it. The old implementation
131
- // transitioned both the camera and the card's position, which made the
132
- // nested scale and translation produce a sideways drift.
133
- boardContent.style.transition = "none";
134
- cardElement.style.transition = "none";
135
- const oldRect = focusCardRectRef.current;
136
- const newRect = snapshotRect(cardElement.getBoundingClientRect());
137
- if (newRect.width > 0 && newRect.height > 0 && camera.zoom > 0) {
138
- const oldCenterX = oldRect.left + oldRect.width / 2;
139
- const oldCenterY = oldRect.top + oldRect.height / 2;
140
- const newCenterX = newRect.left + newRect.width / 2;
141
- const newCenterY = newRect.top + newRect.height / 2;
142
- const localTranslateX = (oldCenterX - newCenterX) / camera.zoom;
143
- const localTranslateY = (oldCenterY - newCenterY) / camera.zoom;
144
- const scaleX = oldRect.width / newRect.width;
145
- const scaleY = oldRect.height / newRect.height;
146
- const animation = cardElement.animate([
147
- {
148
- transformOrigin: "center center",
149
- transform: `translate(${localTranslateX}px, ${localTranslateY}px) scale(${scaleX}, ${scaleY})`,
150
- },
151
- { transformOrigin: "center center", transform: "none" },
152
- ], { duration: PRESENTATION_TRANSITION_MS, easing: "ease", fill: "both" });
153
- canvasEntryAnimationRef.current = animation;
154
- void animation.finished.then(() => {
155
- if (canvasEntryAnimationRef.current !== animation)
156
- return;
157
- canvasEntryAnimationRef.current = null;
158
- animation.cancel();
159
- boardContent.style.transition = "";
160
- cardElement.style.transition = "";
161
- }).catch(() => {
162
- if (canvasEntryAnimationRef.current !== animation)
163
- return;
164
- canvasEntryAnimationRef.current = null;
165
- boardContent.style.transition = "";
166
- cardElement.style.transition = "";
167
- });
168
- }
169
- else {
170
- boardContent.style.transition = "";
171
- cardElement.style.transition = "";
172
- }
173
- }
174
- previousPresentationRef.current = presentation;
175
- }, [camera.zoom, presentation, presentationCardId, presentationTransitioning]);
130
+ if (!board || !card || !cardElement || typeof cardElement.animate !== "function" || camera.zoom <= 0)
131
+ return;
132
+ // The previous layout is already gone, so derive it: focus fills the
133
+ // board, and canvas places the card through the camera.
134
+ const boardRect = snapshotRect(board.getBoundingClientRect());
135
+ const canvasRect = {
136
+ left: boardRect.left + camera.x + card.x * camera.zoom,
137
+ top: boardRect.top + camera.y + card.y * camera.zoom,
138
+ width: card.width * camera.zoom,
139
+ height: card.height * camera.zoom,
140
+ };
141
+ const oldRect = presentation === "focus" ? canvasRect : boardRect;
142
+ const localScale = presentation === "focus" ? 1 : camera.zoom;
143
+ const newRect = snapshotRect(cardElement.getBoundingClientRect());
144
+ if (newRect.width <= 0 || newRect.height <= 0)
145
+ return;
146
+ const localTranslateX = (oldRect.left + oldRect.width / 2 - (newRect.left + newRect.width / 2)) / localScale;
147
+ const localTranslateY = (oldRect.top + oldRect.height / 2 - (newRect.top + newRect.height / 2)) / localScale;
148
+ const scaleX = oldRect.width / newRect.width;
149
+ const scaleY = oldRect.height / newRect.height;
150
+ const animation = cardElement.animate([
151
+ {
152
+ transformOrigin: "center center",
153
+ transform: `translate(${localTranslateX}px, ${localTranslateY}px) scale(${scaleX}, ${scaleY})`,
154
+ },
155
+ { transformOrigin: "center center", transform: "none" },
156
+ ], { duration: PRESENTATION_TRANSITION_MS, easing: "ease", fill: "both" });
157
+ presentationAnimationRef.current = animation;
158
+ void animation.finished.then(() => {
159
+ if (presentationAnimationRef.current !== animation)
160
+ return;
161
+ presentationAnimationRef.current = null;
162
+ animation.cancel();
163
+ }).catch(() => undefined);
164
+ }, [camera, cards, presentation, presentationCardId, presentationTransitioning]);
176
165
  useLayoutEffect(() => {
177
166
  const board = boardRef.current;
178
167
  if (!primaryUrl || !board)
179
168
  return;
180
- if (activatedTargetRef.current === primaryUrl && cards.length > 0)
169
+ if (activatedPrimaryUrlRef.current === primaryUrl && cards.length > 0)
181
170
  return;
182
- activatedTargetRef.current = primaryUrl;
171
+ activatedPrimaryUrlRef.current = primaryUrl;
183
172
  const viewport = {
184
173
  width: board.clientWidth,
185
174
  height: board.clientHeight,
@@ -239,7 +228,9 @@ export function CanvasWorkspace({ primaryUrl }) {
239
228
  }
240
229
  }, [primaryUrl, cards.length, presentation, runtimeConfig.demo, runtimeConfig.demoPages, runtimeConfig.demoCardLabels]);
241
230
  useEffect(() => {
242
- if (primaryUrl && activatedTargetRef.current !== primaryUrl)
231
+ if (pageSurface)
232
+ return;
233
+ if (primaryUrl && activatedPrimaryUrlRef.current !== primaryUrl)
243
234
  return;
244
235
  const activeCardId = selectedCardId ?? focusedCardId ?? cards[0]?.id;
245
236
  const activeCard = cards.find((card) => card.id === activeCardId);
@@ -250,63 +241,31 @@ export function CanvasWorkspace({ primaryUrl }) {
250
241
  window.history.replaceState(window.history.state, "", editorUrl);
251
242
  }
252
243
  }, [cards, focusedCardId, primaryUrl, selectedCardId]);
253
- const sendPanModifier = useCallback((iframe, cardId, spaceHeld) => {
254
- iframe.contentWindow?.postMessage({
255
- type: "pan-modifier",
256
- protocolVersion: PROTOCOL_VERSION,
257
- projectId: PROJECT_ID,
258
- workspaceId: WORKSPACE_ID,
259
- cardId,
260
- spaceHeld,
261
- cursor: spaceHeld ? "drag" : rendererCursor,
262
- }, window.location.origin);
263
- }, [rendererCursor]);
264
- const sendInspectorInteractionState = useCallback((iframe, cardId, open, interactionsEnabled, cursor) => {
265
- iframe.contentWindow?.postMessage({
244
+ const sendInspectorInteractionState = useCallback((frame, open, interactionsEnabled, cursor) => {
245
+ frame.post({
266
246
  type: "inspector-interaction-state",
267
- protocolVersion: PROTOCOL_VERSION,
268
- projectId: PROJECT_ID,
269
- workspaceId: WORKSPACE_ID,
270
- cardId,
271
247
  open,
272
248
  interactionsEnabled,
273
249
  cursor,
274
- commentCursor: cursor === "comment" ? createCommentCursor(boardRef.current) : undefined,
275
- }, window.location.origin);
276
- }, []);
277
- const sendLinkTargetState = useCallback((iframe, cardId, openInCard) => {
278
- iframe.contentWindow?.postMessage({
279
- type: "link-target-state",
280
- protocolVersion: PROTOCOL_VERSION,
281
- projectId: PROJECT_ID,
282
- workspaceId: WORKSPACE_ID,
283
- cardId,
284
- openInCard,
285
- }, window.location.origin);
250
+ ...(cursor === "comment" ? { commentCursor: createCommentCursor(boardRef.current) } : {}),
251
+ });
286
252
  }, []);
287
- const sendBoardGestureState = useCallback((iframe, cardId, enabled) => {
288
- iframe.contentWindow?.postMessage({
289
- type: "board-gesture-state",
290
- protocolVersion: PROTOCOL_VERSION,
291
- projectId: PROJECT_ID,
292
- workspaceId: WORKSPACE_ID,
293
- cardId,
294
- enabled,
295
- panScroll: enabled && interactionTool !== "select",
296
- }, window.location.origin);
253
+ const sendBoardGestureState = useCallback((frame, enabled) => {
254
+ frame.post({ type: "board-gesture-state", enabled, panScroll: enabled && interactionTool !== "select" });
297
255
  }, [interactionTool]);
298
256
  useEffect(() => {
299
257
  for (const [cardId, iframe] of getRegisteredFrames()) {
300
- sendInspectorInteractionState(iframe, cardId, inspectorOpen, rendererInteractionsEnabled, rendererCursor);
301
- sendBoardGestureState(iframe, cardId, presentation === "canvas");
302
- 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 });
303
262
  }
304
- }, [inspectorOpen, linksOpenInCards, presentation, rendererCursor, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState, sendLinkTargetState]);
263
+ }, [inspectorOpen, linksOpenInCards, presentation, rendererCursor, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState]);
305
264
  const broadcastPanModifier = useCallback((spaceHeld) => {
306
265
  for (const [cardId, iframe] of getRegisteredFrames()) {
307
- sendPanModifier(iframe, cardId, spaceHeld);
266
+ connectFrame(cardId, iframe).post({ type: "pan-modifier", spaceHeld });
308
267
  }
309
- }, [sendPanModifier]);
268
+ }, []);
310
269
  const zoomAtPointer = useCallback((point, deltaY) => {
311
270
  const board = boardRef.current;
312
271
  if (!board)
@@ -367,7 +326,7 @@ export function CanvasWorkspace({ primaryUrl }) {
367
326
  if (nextTool === "sketch") {
368
327
  if (!sketchEnabled)
369
328
  return;
370
- const hostElement = getActiveCanvasFrame();
329
+ const hostElement = canvasFrameHost(getActiveCanvasFrame());
371
330
  if (!hostElement)
372
331
  return;
373
332
  stopPanMode();
@@ -433,6 +392,8 @@ export function CanvasWorkspace({ primaryUrl }) {
433
392
  return () => window.removeEventListener("keydown", onKeyDown, true);
434
393
  }, [handleToolChange, mode, sketchAvailable]);
435
394
  const handleZoomStep = useCallback((direction) => {
395
+ if (sketchActive)
396
+ return;
436
397
  if (presentation === "focus") {
437
398
  if (direction < 0) {
438
399
  batchCanvasChanges(() => {
@@ -457,7 +418,7 @@ export function CanvasWorkspace({ primaryUrl }) {
457
418
  if (nextZoom === null)
458
419
  return;
459
420
  setBoardCamera({ ...getBoardCamera(), zoom: nextZoom });
460
- }, [presentation]);
421
+ }, [presentation, sketchActive]);
461
422
  const showCanvas = useCallback(() => {
462
423
  handleZoomStep(-1);
463
424
  }, [handleZoomStep]);
@@ -496,7 +457,7 @@ export function CanvasWorkspace({ primaryUrl }) {
496
457
  broadcastPanModifier(false);
497
458
  }, [broadcastPanModifier, endPanning, presentation]);
498
459
  useEffect(() => {
499
- const stopChanges = subscribeChanges(() => {
460
+ const stopChanges = subscribeActiveDraftChanges(() => {
500
461
  projectToAllReadyCards();
501
462
  });
502
463
  const stopPreview = subscribeOriginalPreview(() => {
@@ -508,7 +469,8 @@ export function CanvasWorkspace({ primaryUrl }) {
508
469
  };
509
470
  }, []);
510
471
  useEffect(() => {
511
- return subscribeCanvasRendererMessages(({ cardId, iframe, identity, message: msg }) => {
472
+ return subscribeCanvasRendererMessages(({ frame, message: msg }) => {
473
+ const { identity } = frame;
512
474
  if (msg.type === "renderer-hello")
513
475
  return;
514
476
  if (msg.type === "app-interaction-modifier") {
@@ -524,13 +486,11 @@ export function CanvasWorkspace({ primaryUrl }) {
524
486
  }
525
487
  return;
526
488
  }
527
- const frame = { cardId, iframe };
528
489
  const syncActiveFrameUrl = (url) => {
529
490
  if (getCanvasCards().some((card) => card.id === frame.cardId && iterationId(card.content)))
530
491
  return;
531
492
  const activeCardId = getSelectedCardId() ?? getFocusedCardId() ?? getCanvasCards()[0]?.id;
532
- if (activeCardId === frame.cardId || getCanvasCards().length === 1) {
533
- activatedTargetRef.current = url;
493
+ if (!pageSurface && (activeCardId === frame.cardId || getCanvasCards().length === 1)) {
534
494
  window.history.replaceState(window.history.state, "", createNudgeUiEditorUrl(url));
535
495
  }
536
496
  updateCardUrl(frame.cardId, url);
@@ -541,7 +501,7 @@ export function CanvasWorkspace({ primaryUrl }) {
541
501
  // the page without inspector changes; skip conflict verification.
542
502
  if (isOriginalPreviewActive())
543
503
  return;
544
- const frameDocument = frame.iframe.contentDocument;
504
+ const frameDocument = frame.document();
545
505
  const plan = getCanonicalFrameProjection(frame.cardId, msg.revision);
546
506
  const draft = getDraftForCard(frame.cardId);
547
507
  if (frameDocument && plan && draft && isCanvasCanonicalProjectionRevisionCurrent(frameDocument, msg.revision)) {
@@ -591,10 +551,10 @@ export function CanvasWorkspace({ primaryUrl }) {
591
551
  syncActiveFrameUrl(msg.url);
592
552
  // A card can finish loading after Space was pressed on the controller.
593
553
  // Seed it with the current modifier state before its first pointer event.
594
- sendPanModifier(frame.iframe, frame.cardId, spaceHeldRef.current);
595
- sendInspectorInteractionState(frame.iframe, frame.cardId, inspectorOpen, rendererInteractionsEnabled, spaceHeldRef.current ? "drag" : rendererCursor);
596
- sendBoardGestureState(frame.iframe, frame.cardId, presentation === "canvas");
597
- 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 });
598
558
  }
599
559
  if (msg.type === "pan-modifier") {
600
560
  if (presentation !== "canvas")
@@ -607,34 +567,25 @@ export function CanvasWorkspace({ primaryUrl }) {
607
567
  return;
608
568
  }
609
569
  if (msg.type === "wheel-pan") {
610
- if (presentation === "canvas" && interactionTool !== "select")
570
+ if (presentation === "canvas" && !sketchActive && interactionTool !== "select")
611
571
  panCanvasByWheel(msg.deltaX, msg.deltaY);
612
572
  return;
613
573
  }
614
574
  if (msg.type === "zoom") {
615
- if (presentation !== "canvas")
575
+ if (presentation !== "canvas" || sketchActive)
616
576
  return;
617
- const iframeRect = frame.iframe.getBoundingClientRect();
618
- const point = iframePointToClientPoint(iframeRect, msg.point, getBoardCamera().zoom);
619
- zoomAtPointer(point, msg.deltaY);
577
+ zoomAtPointer(toViewportPoint(frame.viewport(), msg.point), msg.deltaY);
620
578
  return;
621
579
  }
580
+ // The pan moves the frame, so every pan point maps through the viewport captured at pan start.
622
581
  const pointInBoard = (point) => {
623
582
  const context = framePanContextRef.current;
624
- if (!context)
625
- return null;
626
- return {
627
- x: context.left + point.x * context.zoom,
628
- y: context.top + point.y * context.zoom,
629
- };
583
+ return context ? toViewportPoint(context, point) : null;
630
584
  };
631
585
  if (msg.type === "pan-start") {
632
- const iframeRect = frame.iframe.getBoundingClientRect();
633
- framePanContextRef.current = {
634
- left: iframeRect.left,
635
- top: iframeRect.top,
636
- zoom: getBoardCamera().zoom,
637
- };
586
+ if (sketchActive)
587
+ return;
588
+ framePanContextRef.current = frame.viewport();
638
589
  const point = pointInBoard(msg.point);
639
590
  if (point)
640
591
  startPanning(point);
@@ -652,7 +603,7 @@ export function CanvasWorkspace({ primaryUrl }) {
652
603
  return;
653
604
  }
654
605
  if (msg.type === "frame-metadata") {
655
- const frameDocument = frame.iframe.contentDocument;
606
+ const frameDocument = frame.document();
656
607
  if (frameDocument)
657
608
  disposeInlineTextEdit("route-disposed", frameDocument);
658
609
  if (msg.url)
@@ -683,11 +634,11 @@ export function CanvasWorkspace({ primaryUrl }) {
683
634
  activateIframeWorkspace(msg.url, { width: boardRef.current?.clientWidth ?? window.innerWidth, height: boardRef.current?.clientHeight ?? window.innerHeight }, { replaceActiveCard: true });
684
635
  return;
685
636
  }
686
- const frameDocument = frame.iframe.contentDocument;
637
+ const frameDocument = frame.document();
687
638
  if (frameDocument)
688
639
  disposeInlineTextEdit("route-disposed", frameDocument);
689
640
  });
690
- }, [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]);
691
642
  useEffect(() => {
692
643
  if (mode === "canvas" && !hasFitAllRan()) {
693
644
  if (!fitAllScheduledRef.current) {
@@ -778,8 +729,13 @@ export function CanvasWorkspace({ primaryUrl }) {
778
729
  function handleGlobalWheel(e) {
779
730
  const zoom = e.ctrlKey || e.metaKey;
780
731
  const board = boardRef.current;
781
- 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();
782
737
  return;
738
+ }
783
739
  const path = e.composedPath();
784
740
  // Comment pins overlay the board without being its DOM descendants.
785
741
  const overCommentPin = path.some((node) => node instanceof Element && node.matches(".comment-pin"));
@@ -797,10 +753,11 @@ export function CanvasWorkspace({ primaryUrl }) {
797
753
  window.addEventListener("wheel", handleGlobalWheel, { capture: true, passive: false });
798
754
  return () => window.removeEventListener("wheel", handleGlobalWheel, { capture: true });
799
755
  }, [mode, presentation, sketchActive, interactionTool, panCanvasByWheel, zoomAtPointer]);
800
- 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));
801
758
  if (mode !== "canvas")
802
759
  return null;
803
- 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: {
804
761
  transform: `translate(${camera.x}px, ${camera.y}px) scale(${camera.zoom})`,
805
762
  transformOrigin: "0 0",
806
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 {};