nudge-ui 0.2.1 → 0.2.3

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 (258) hide show
  1. package/README.md +164 -46
  2. package/dist/client.mjs +252 -54
  3. package/dist/css/value-semantics/colorSemantics.d.ts +4 -0
  4. package/dist/css/value-semantics/colorSemantics.js +17 -0
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -0
  6. package/dist/hosts/astro/clientTransport.js +41 -32
  7. package/dist/hosts/astro/integration.d.ts +2 -2
  8. package/dist/hosts/astro/integration.js +9 -4
  9. package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
  10. package/dist/hosts/astro/markdownIdentity.js +81 -0
  11. package/dist/hosts/environment.d.ts +15 -0
  12. package/dist/hosts/environment.js +16 -0
  13. package/dist/hosts/next/manifest.js +1 -1
  14. package/dist/hosts/next/sidecar.js +12 -2
  15. package/dist/hosts/next/wrapper.d.ts +2 -0
  16. package/dist/hosts/next/wrapper.js +2 -1
  17. package/dist/hosts/projectBridge.d.ts +1 -0
  18. package/dist/hosts/projectBridge.js +1 -0
  19. package/dist/hosts/static/manifest.js +3 -1
  20. package/dist/hosts/static/server.js +4 -0
  21. package/dist/hosts/vite/vite.d.ts +4 -0
  22. package/dist/hosts/vite/vite.js +28 -5
  23. package/dist/inspector/agent/McpConnectionDialog.d.ts +3 -2
  24. package/dist/inspector/agent/McpConnectionDialog.js +23 -22
  25. package/dist/inspector/agent/activity.d.ts +18 -0
  26. package/dist/inspector/agent/activity.js +70 -0
  27. package/dist/inspector/agent/client.d.ts +8 -0
  28. package/dist/inspector/agent/client.js +65 -5
  29. package/dist/inspector/agent/connectionStatus.d.ts +3 -3
  30. package/dist/inspector/agent/connectionStatus.js +9 -2
  31. package/dist/inspector/agent/httpTransport.d.ts +6 -0
  32. package/dist/inspector/agent/httpTransport.js +50 -3
  33. package/dist/inspector/agent/protocol.d.ts +9 -3
  34. package/dist/inspector/agent/protocol.js +1 -1
  35. package/dist/inspector/agent/verification.d.ts +11 -1
  36. package/dist/inspector/agent/verification.js +53 -25
  37. package/dist/inspector/canvas/AgentActivityOverlay.d.ts +13 -0
  38. package/dist/inspector/canvas/AgentActivityOverlay.js +111 -0
  39. package/dist/inspector/canvas/CanvasCard.d.ts +9 -2
  40. package/dist/inspector/canvas/CanvasCard.js +133 -38
  41. package/dist/inspector/canvas/CanvasElementOverlay.js +200 -22
  42. package/dist/inspector/canvas/CanvasToolIcons.d.ts +10 -0
  43. package/dist/inspector/canvas/CanvasToolIcons.js +20 -0
  44. package/dist/inspector/canvas/CanvasToolbar.js +8 -4
  45. package/dist/inspector/canvas/CanvasWorkspace.js +243 -56
  46. package/dist/inspector/canvas/StaleChangeIndicator.js +3 -13
  47. package/dist/inspector/canvas/agentPresentation.d.ts +8 -8
  48. package/dist/inspector/canvas/agentPresentation.js +64 -52
  49. package/dist/inspector/canvas/canvasGestures.d.ts +1 -1
  50. package/dist/inspector/canvas/canvasGestures.js +1 -1
  51. package/dist/inspector/canvas/canvasStore.d.ts +46 -82
  52. package/dist/inspector/canvas/canvasStore.js +251 -284
  53. package/dist/inspector/canvas/frameContent.d.ts +21 -0
  54. package/dist/inspector/canvas/frameContent.js +44 -0
  55. package/dist/inspector/canvas/frameProtocol.d.ts +34 -4
  56. package/dist/inspector/canvas/frameProtocol.js +9 -2
  57. package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -1
  58. package/dist/inspector/canvas/keyboardShortcuts.js +5 -3
  59. package/dist/inspector/canvas/layout.d.ts +52 -0
  60. package/dist/inspector/canvas/layout.js +106 -0
  61. package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
  62. package/dist/inspector/canvas/linkEligibility.js +0 -29
  63. package/dist/inspector/canvas/projection.d.ts +15 -5
  64. package/dist/inspector/canvas/projection.js +69 -13
  65. package/dist/inspector/canvas/rendererBootstrap.js +45 -4
  66. package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
  67. package/dist/inspector/canvas/rendererElementSelector.js +102 -45
  68. package/dist/inspector/canvas/rendererStylesheet.js +2 -2
  69. package/dist/inspector/canvas/resizeHandleScale.js +1 -1
  70. package/dist/inspector/canvas/sessionStore.d.ts +11 -26
  71. package/dist/inspector/canvas/sessionStore.js +43 -183
  72. package/dist/inspector/canvas/staleChangeDetector.js +9 -3
  73. package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
  74. package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
  75. package/dist/inspector/canvas/viewStore.d.ts +38 -0
  76. package/dist/inspector/canvas/viewStore.js +104 -0
  77. package/dist/inspector/canvas/wheelPan.d.ts +8 -0
  78. package/dist/inspector/canvas/wheelPan.js +8 -0
  79. package/dist/inspector/changes/changesLog.d.ts +6 -5
  80. package/dist/inspector/changes/changesLog.js +40 -32
  81. package/dist/inspector/changes/draftChanges.d.ts +56 -0
  82. package/dist/inspector/changes/draftChanges.js +300 -0
  83. package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
  84. package/dist/inspector/changes/previewDiagnostics.js +11 -25
  85. package/dist/inspector/clientManifest.d.ts +3 -1
  86. package/dist/inspector/clientManifest.js +1 -0
  87. package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
  88. package/dist/inspector/comments/CommentsOverlay.js +250 -0
  89. package/dist/inspector/comments/element.d.ts +6 -0
  90. package/dist/inspector/comments/element.js +47 -0
  91. package/dist/inspector/comments/store.d.ts +29 -0
  92. package/dist/inspector/comments/store.js +121 -0
  93. package/dist/inspector/comments/verification.d.ts +5 -0
  94. package/dist/inspector/comments/verification.js +52 -0
  95. package/dist/inspector/drafts/model.d.ts +35 -0
  96. package/dist/inspector/drafts/model.js +45 -0
  97. package/dist/inspector/drafts/store.d.ts +28 -0
  98. package/dist/inspector/drafts/store.js +231 -0
  99. package/dist/inspector/index.js +26 -35
  100. package/dist/inspector/inline-text/inlineTextEditor.js +7 -0
  101. package/dist/inspector/inspection/useBrowserCssInspection.js +15 -1
  102. package/dist/inspector/iterations/capture.d.ts +2 -0
  103. package/dist/inspector/iterations/capture.js +173 -0
  104. package/dist/inspector/iterations/client.d.ts +7 -0
  105. package/dist/inspector/iterations/client.js +52 -0
  106. package/dist/inspector/iterations/coordinator.d.ts +15 -0
  107. package/dist/inspector/iterations/coordinator.js +130 -0
  108. package/dist/inspector/iterations/prompt.d.ts +3 -0
  109. package/dist/inspector/iterations/prompt.js +26 -0
  110. package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
  111. package/dist/inspector/overlay/DomNavigator.js +46 -0
  112. package/dist/inspector/overlay/InspectorOverlay.js +6 -2
  113. package/dist/inspector/overlay/clickPolicy.d.ts +18 -2
  114. package/dist/inspector/overlay/clickPolicy.js +34 -3
  115. package/dist/inspector/overlay/elementSelector.d.ts +1 -1
  116. package/dist/inspector/overlay/elementSelector.js +18 -7
  117. package/dist/inspector/overlay/interactionStyles.js +3 -1
  118. package/dist/inspector/overlay/spacingGestures.d.ts +15 -3
  119. package/dist/inspector/overlay/spacingGestures.js +88 -13
  120. package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
  121. package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
  122. package/dist/inspector/projection/renderedInstance.d.ts +2 -1
  123. package/dist/inspector/projection/renderedInstance.js +9 -5
  124. package/dist/inspector/projection/structuralProjection.d.ts +2 -1
  125. package/dist/inspector/projection/structuralProjection.js +19 -15
  126. package/dist/inspector/projection/textProjection.d.ts +2 -1
  127. package/dist/inspector/projection/textProjection.js +8 -4
  128. package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
  129. package/dist/inspector/prompt/clipboardHandoff.d.ts +10 -18
  130. package/dist/inspector/prompt/clipboardHandoff.js +92 -143
  131. package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
  132. package/dist/inspector/prompt/generatePrompt.js +17 -16
  133. package/dist/inspector/runtime/runtimeConfig.d.ts +10 -1
  134. package/dist/inspector/runtime/runtimeConfig.js +24 -0
  135. package/dist/inspector/selection/hierarchy.d.ts +6 -0
  136. package/dist/inspector/selection/hierarchy.js +8 -0
  137. package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
  138. package/dist/inspector/settings/SettingsDialog.js +22 -9
  139. package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
  140. package/dist/inspector/shell/AgentStatusButton.js +36 -0
  141. package/dist/inspector/shell/AppShell.js +4 -2
  142. package/dist/inspector/shell/ChangesLog.js +47 -27
  143. package/dist/inspector/shell/CopyPromptButton.js +146 -90
  144. package/dist/inspector/shell/EmptyState.js +9 -8
  145. package/dist/inspector/shell/InspectorShell.js +14 -25
  146. package/dist/inspector/shell/PeekOriginalButton.js +24 -23
  147. package/dist/inspector/shell/originalPreview.js +5 -5
  148. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  149. package/dist/inspector/shell/shortcuts.js +11 -0
  150. package/dist/inspector/sketch/SketchEditor.js +14 -14
  151. package/dist/inspector/sketch/SketchEntryButton.js +1 -1
  152. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
  153. package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
  154. package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
  155. package/dist/inspector/sketch/SketchOverlay.js +7 -7
  156. package/dist/inspector/sketch/SketchWorkspace.js +18 -12
  157. package/dist/inspector/sketch/interaction.d.ts +1 -0
  158. package/dist/inspector/sketch/interaction.js +8 -4
  159. package/dist/inspector/sketch/model.d.ts +5 -0
  160. package/dist/inspector/sketch/model.js +6 -2
  161. package/dist/inspector/sketch/raster.d.ts +1 -2
  162. package/dist/inspector/sketch/raster.js +68 -4
  163. package/dist/inspector/sketch/store.js +48 -23
  164. package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
  165. package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
  166. package/dist/inspector/styleEditors/BorderEditor.js +49 -78
  167. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
  168. package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
  169. package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
  170. package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
  171. package/dist/inspector/styleEditors/ColorPicker.js +10 -4
  172. package/dist/inspector/styleEditors/GapField.js +3 -5
  173. package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
  174. package/dist/inspector/styleEditors/GridChildSection.js +18 -9
  175. package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
  176. package/dist/inspector/styleEditors/GridPicker.js +8 -2
  177. package/dist/inspector/styleEditors/GridSection.js +1 -1
  178. package/dist/inspector/styleEditors/GridValueField.js +19 -14
  179. package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
  180. package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
  181. package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
  182. package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
  183. package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
  184. package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
  185. package/dist/inspector/styleEditors/LayoutSection.js +32 -11
  186. package/dist/inspector/styleEditors/SpacingBox.js +16 -9
  187. package/dist/inspector/styleEditors/Typography.js +11 -3
  188. package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
  189. package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
  190. package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
  191. package/dist/inspector/styleEditors/gradientValue.js +114 -0
  192. package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
  193. package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
  194. package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
  195. package/dist/inspector/styleEditors/layoutValue.js +0 -42
  196. package/dist/inspector/styleEditors/nudgeValue.d.ts +4 -4
  197. package/dist/inspector/styleEditors/nudgeValue.js +41 -11
  198. package/dist/inspector/tokens/TokenField.d.ts +7 -0
  199. package/dist/inspector/tokens/TokenField.js +49 -22
  200. package/dist/inspector/tokens/TokenLabel.js +5 -14
  201. package/dist/inspector/tokens/TokensPanel.js +2 -1
  202. package/dist/inspector/tokens/dragPointerLock.d.ts +8 -0
  203. package/dist/inspector/tokens/dragPointerLock.js +70 -0
  204. package/dist/inspector/tokens/editActions.d.ts +1 -1
  205. package/dist/inspector/tokens/editActions.js +1 -1
  206. package/dist/inspector/ui/AtRuleContext.js +4 -6
  207. package/dist/inspector/ui/Button.d.ts +6 -2
  208. package/dist/inspector/ui/Button.js +4 -3
  209. package/dist/inspector/ui/ControlSurface.d.ts +2 -2
  210. package/dist/inspector/ui/ControlSurface.js +4 -3
  211. package/dist/inspector/ui/Disclosure.d.ts +22 -0
  212. package/dist/inspector/ui/Disclosure.js +13 -0
  213. package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
  214. package/dist/inspector/ui/InlineStyleWarning.js +13 -0
  215. package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
  216. package/dist/inspector/ui/InspectorPopover.js +4 -5
  217. package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
  218. package/dist/inspector/ui/PopoverListbox.js +8 -7
  219. package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
  220. package/dist/inspector/ui/SegmentedControl.js +2 -2
  221. package/dist/inspector/ui/Select.d.ts +6 -1
  222. package/dist/inspector/ui/Select.js +7 -11
  223. package/dist/inspector/ui/SideValuesField.js +1 -1
  224. package/dist/inspector/ui/Tooltip.d.ts +26 -0
  225. package/dist/inspector/ui/Tooltip.js +32 -0
  226. package/dist/inspector/ui/classNames.d.ts +2 -0
  227. package/dist/inspector/ui/classNames.js +4 -0
  228. package/dist/inspector/ui/customCursors.d.ts +23 -0
  229. package/dist/inspector/ui/customCursors.js +122 -0
  230. package/dist/inspector/ui/dragCursor.d.ts +6 -0
  231. package/dist/inspector/ui/dragCursor.js +13 -0
  232. package/dist/inspector/ui/portalContainer.d.ts +2 -0
  233. package/dist/inspector/ui/portalContainer.js +6 -0
  234. package/dist/inspector/ui/styles.js +34 -24
  235. package/dist/inspector/workspace/commands.d.ts +9 -0
  236. package/dist/inspector/workspace/commands.js +82 -0
  237. package/dist/inspector/workspace/controller.d.ts +4 -0
  238. package/dist/inspector/workspace/controller.js +66 -0
  239. package/dist/inspector/workspace/handoff.d.ts +42 -0
  240. package/dist/inspector/workspace/handoff.js +115 -0
  241. package/dist/inspector/workspace/timeline.d.ts +30 -0
  242. package/dist/inspector/workspace/timeline.js +108 -0
  243. package/dist/internal/agent-protocol.d.ts +19 -2
  244. package/dist/internal/agent-protocol.js +13 -0
  245. package/dist/nudge-ui.mjs +560 -299
  246. package/dist/project/artifacts.d.ts +13 -0
  247. package/dist/project/artifacts.js +253 -0
  248. package/dist/transport/artifacts.d.ts +8 -0
  249. package/dist/transport/artifacts.js +9 -0
  250. package/dist/transport/editor.d.ts +20 -1
  251. package/dist/transport/editor.js +44 -11
  252. package/dist/transport/routes.d.ts +6 -1
  253. package/dist/transport/routes.js +6 -1
  254. package/package.json +6 -2
  255. package/dist/inspector/changes/workspaceChanges.d.ts +0 -43
  256. package/dist/inspector/changes/workspaceChanges.js +0 -239
  257. package/dist/inspector/shell/DomNavigation.d.ts +0 -8
  258. package/dist/inspector/shell/DomNavigation.js +0 -30
@@ -1,10 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { getCanonicalFrameProjection } from "./projection.js";
3
+ import { getDraftForCard } from "../drafts/store.js";
4
+ import { iterationId, contentSourceUrl, getLinkedFrameIds } from "./frameContent.js";
2
5
  import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
3
6
  import { IconCornerLeftUp } from "@tabler/icons-react";
4
- import { useCanvasCards, activateIframeWorkspace, removeCanvasCard, getSelectedCardId, getFocusedCardId, getCanvasCards, setBoardCamera, getBoardCamera, fitAllCards, setCanvasPresentation, focusCard, hasFitAllRan, useBoardCamera, useCanvasPresentation, useCanvasPresentationTransitioning, useSelectedCardId, useFocusedCardId, resizeCard, setCardPosition, updateCardTitle, CARD_GAP, updateCardUrl, } from "./canvasStore.js";
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
+ import { setBoardCamera, getBoardCamera, setCanvasPresentation, hasFitAllRan, useBoardCamera, useCanvasPresentation, useCanvasPresentationTransitioning, useCanvasLayoutTransitioning, useTemporaryAppInteraction, setTemporaryAppInteraction, } from "./viewStore.js";
5
9
  import { CanvasCard } from "./CanvasCard.js";
10
+ import { useCanvasLayoutAnimation } from "./useCanvasLayoutAnimation.js";
6
11
  import { Button } from "../ui/Button.js";
7
- import { useCanvasMode } from "./canvasStore.js";
12
+ import { useCanvasMode } from "./viewStore.js";
8
13
  import { subscribeChanges } from "../changes/changesLog.js";
9
14
  import { subscribeOriginalPreview, isOriginalPreviewActive } from "../shell/originalPreview.js";
10
15
  import { recordCanvasStructuralProjectionReports } from "../projection/structuralProjection.js";
@@ -12,31 +17,37 @@ import { recordCanvasRenderedInstanceProjectionReports } from "../projection/ren
12
17
  import { recordCanvasTextProjectionReports } from "../projection/textProjection.js";
13
18
  import { getChangesList, isPreviewableChange } from "../changes/changesLog.js";
14
19
  import { changeKey } from "../changes/model.js";
15
- import { getWorkspaceChanges } from "../changes/workspaceChanges.js";
20
+ import { getActiveDraftChanges } from "../changes/draftChanges.js";
16
21
  import { beginPreviewAttempt, publishPreviewDiagnostic } from "../changes/previewDiagnostics.js";
17
22
  import { verifyManagedStyleProjection } from "../changes/managedStyleProjection.js";
18
23
  import { getCanvasPreviewDocument, getRegisteredFrames, isCanvasCanonicalProjectionRevisionCurrent, PROJECT_ID, projectToAllReadyCards, recordCanvasProjectionApplied, WORKSPACE_ID, } from "./projection.js";
19
- import { normalizeUrl } from "./normalizeUrl.js";
24
+ import { normalizeUrl, normalizedUrlKey } from "./normalizeUrl.js";
20
25
  import { createNudgeUiEditorUrl } from "../../transport/editor.js";
21
26
  import { disposeInlineTextEdit } from "../inline-text/inlineTextEditor.js";
22
27
  import { PROTOCOL_VERSION, isProjectionAppliedMessage, isKeyboardShortcutMessage, isRenderedInstanceProjectionReportMessage, isTextProjectionReportMessage, isStructuralProjectionReportMessage, } from "./frameProtocol.js";
28
+ import { wheelPanDelta } from "./wheelPan.js";
23
29
  import { iframePointToClientPoint, zoomCameraAtPointer } from "./canvasGestures.js";
24
- 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 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";
25
- 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-8));\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(--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 .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(--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__toolbar {\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-start;\n z-index: 1;\n cursor: grab;\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n transition: opacity 120ms ease;\n}\n\n.canvas-card:hover .canvas-card__toolbar,\n.canvas-card:focus-within .canvas-card__toolbar,\n.canvas-card.is-selected .canvas-card__toolbar,\n.canvas-card.is-focused .canvas-card__toolbar {\n opacity: 1;\n visibility: visible;\n pointer-events: auto;\n}\n\n.canvas-card.is-dragging .canvas-card__toolbar {\n cursor: grabbing;\n}\n\n.canvas-card__drag-surface {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--space-8);\n flex: 1 1 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__dimensions {\n position: absolute;\n left: 50%;\n bottom: 0;\n color: var(--text-secondary);\n font-size: var(--font-size-base);\n font-variant-numeric: tabular-nums;\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";
26
- 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\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";
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";
31
+ 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
+ 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";
27
33
  import { useInspectorOpen } from "../shell/openStore.js";
28
34
  import { isEditableEvent } from "../shell/shortcuts.js";
29
35
  import { acknowledgeAgentRendererReady } from "./agentPresentation.js";
30
36
  import { useInspectorSession } from "../session/sessionContext.js";
37
+ import { isNudgeUiFeatureEnabled } from "../runtime/runtimeConfig.js";
31
38
  import { useNudgeUiRuntimeConfig } from "../runtime/useRuntimeConfig.js";
32
39
  import { subscribeCanvasRendererMessages } from "./rendererMessageRouter.js";
33
40
  import { adjacentCanvasZoomLevel, CanvasToolbar, } from "./CanvasToolbar.js";
34
- import { canvasToolForEvent, canvasToolForCode } from "./keyboardShortcuts.js";
35
- 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__tool[data-active=\"true\"] {\n background: var(--accent-surface);\n color: var(--accent);\n}\n";
41
+ 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";
36
43
  import { getActiveCanvasFrame } from "./activeCanvasDocument.js";
37
44
  import { startSketchCapture } from "../sketch/SketchWorkspace.js";
38
45
  import { cancelSketchInteraction, useSketchInteractionActive } from "../sketch/interaction.js";
39
46
  import { isNudgeUiDev } from "../runtime/devFlag.js";
47
+ import { createCommentCursor, DESIGN_SELECT_CURSOR, PAN_CURSOR } from "../ui/customCursors.js";
48
+ import { addLinkedFrame, createIteration as createWorkspaceIteration, removeFrame } from "../workspace/commands.js";
49
+ import { getSelectedElements, setSelectedElement } from "../selection/selectionStore.js";
50
+ import { setCommentToolActive } from "../comments/store.js";
40
51
  const WORKSPACE_STYLES = [foundationStyles, canvasWorkspaceStyles, canvasCardStyles, canvasToolbarStyles].join("\n");
41
52
  const PRESENTATION_TRANSITION_MS = 200;
42
53
  function snapshotRect(rect) {
@@ -60,8 +71,10 @@ export function CanvasWorkspace({ primaryUrl }) {
60
71
  const focusedCardId = useFocusedCardId();
61
72
  const sketchActive = useSketchInteractionActive();
62
73
  const sketchEnabled = isNudgeUiDev() && runtimeConfig.demo !== true;
74
+ const canvasIterationsEnabled = isNudgeUiFeatureEnabled(runtimeConfig.features, "canvasIterations");
63
75
  const sketchAvailable = sketchEnabled && getActiveCanvasFrame() !== null;
64
76
  const boardRef = useRef(null);
77
+ useCanvasLayoutAnimation(boardRef, cards, camera, presentation);
65
78
  const panningRef = useRef(false);
66
79
  const panOriginRef = useRef({ x: 0, y: 0 });
67
80
  const cameraAtPanStartRef = useRef({ x: 0, y: 0 });
@@ -74,8 +87,24 @@ export function CanvasWorkspace({ primaryUrl }) {
74
87
  const focusCardRectRef = useRef(null);
75
88
  const canvasEntryAnimationRef = useRef(null);
76
89
  const [boardCursorClass, setBoardCursorClass] = useState("");
77
- const [interactionTool, setInteractionTool] = useState("move");
90
+ const [selectedTool, setInteractionTool] = useState("design");
91
+ const temporaryAppInteraction = useTemporaryAppInteraction();
92
+ const interactionTool = temporaryAppInteraction ? "select" : selectedTool;
93
+ const layoutTransitioning = useCanvasLayoutTransitioning();
94
+ const rendererInteractionsEnabled = interactionTool !== "select" && !layoutTransitioning;
95
+ const rendererCursor = interactionTool === "comment" ? "comment" : "design";
96
+ const boardCursor = boardCursorClass === "is-grabbing"
97
+ ? "grabbing"
98
+ : boardCursorClass !== ""
99
+ ? PAN_CURSOR
100
+ : interactionTool === "comment"
101
+ ? createCommentCursor(boardRef.current)
102
+ : interactionTool === "design"
103
+ ? DESIGN_SELECT_CURSOR
104
+ : undefined;
105
+ const linksOpenInCards = interactionTool === "select" && presentation === "canvas";
78
106
  const presentationCardId = selectedCardId ?? focusedCardId ?? cards[0]?.id ?? null;
107
+ const linkedFrameIds = getLinkedFrameIds(cards, selectedCardId);
79
108
  useLayoutEffect(() => {
80
109
  const board = boardRef.current;
81
110
  const cardElement = presentationCardId
@@ -155,7 +184,16 @@ export function CanvasWorkspace({ primaryUrl }) {
155
184
  width: board.clientWidth,
156
185
  height: board.clientHeight,
157
186
  };
158
- const primaryCard = activateIframeWorkspace(primaryUrl, viewport);
187
+ const primaryCard = activateIframeWorkspace(primaryUrl, viewport, {
188
+ replaceActiveCard: presentation === "focus" && runtimeConfig.demo !== true,
189
+ preserveIterationFocus: true,
190
+ });
191
+ if (primaryCard && cards.length === 0 && runtimeConfig.demo !== true) {
192
+ setBoardCamera({
193
+ x: 40, y: 120,
194
+ zoom: Math.max(0.25, Math.min(0.9, (viewport.width - 80) / primaryCard.width, (viewport.height - 160) / primaryCard.height)),
195
+ });
196
+ }
159
197
  if (runtimeConfig.demo === true && primaryCard && !demoSeededRef.current) {
160
198
  demoSeededRef.current = true;
161
199
  const demoPages = runtimeConfig.demoPages ?? [];
@@ -199,7 +237,7 @@ export function CanvasWorkspace({ primaryUrl }) {
199
237
  }
200
238
  focusCard(primaryCard.id);
201
239
  }
202
- }, [primaryUrl, cards.length, runtimeConfig.demo, runtimeConfig.demoPages, runtimeConfig.demoCardLabels]);
240
+ }, [primaryUrl, cards.length, presentation, runtimeConfig.demo, runtimeConfig.demoPages, runtimeConfig.demoCardLabels]);
203
241
  useEffect(() => {
204
242
  if (primaryUrl && activatedTargetRef.current !== primaryUrl)
205
243
  return;
@@ -207,7 +245,7 @@ export function CanvasWorkspace({ primaryUrl }) {
207
245
  const activeCard = cards.find((card) => card.id === activeCardId);
208
246
  if (!activeCard)
209
247
  return;
210
- const editorUrl = createNudgeUiEditorUrl(activeCard.url);
248
+ const editorUrl = createNudgeUiEditorUrl(contentSourceUrl(activeCard.content));
211
249
  if (window.location.href !== editorUrl) {
212
250
  window.history.replaceState(window.history.state, "", editorUrl);
213
251
  }
@@ -220,9 +258,10 @@ export function CanvasWorkspace({ primaryUrl }) {
220
258
  workspaceId: WORKSPACE_ID,
221
259
  cardId,
222
260
  spaceHeld,
261
+ cursor: spaceHeld ? "drag" : rendererCursor,
223
262
  }, window.location.origin);
224
- }, []);
225
- const sendInspectorInteractionState = useCallback((iframe, cardId, open) => {
263
+ }, [rendererCursor]);
264
+ const sendInspectorInteractionState = useCallback((iframe, cardId, open, interactionsEnabled, cursor) => {
226
265
  iframe.contentWindow?.postMessage({
227
266
  type: "inspector-interaction-state",
228
267
  protocolVersion: PROTOCOL_VERSION,
@@ -230,7 +269,19 @@ export function CanvasWorkspace({ primaryUrl }) {
230
269
  workspaceId: WORKSPACE_ID,
231
270
  cardId,
232
271
  open,
233
- interactionsEnabled: true,
272
+ interactionsEnabled,
273
+ 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,
234
285
  }, window.location.origin);
235
286
  }, []);
236
287
  const sendBoardGestureState = useCallback((iframe, cardId, enabled) => {
@@ -241,14 +292,16 @@ export function CanvasWorkspace({ primaryUrl }) {
241
292
  workspaceId: WORKSPACE_ID,
242
293
  cardId,
243
294
  enabled,
295
+ panScroll: enabled && interactionTool !== "select",
244
296
  }, window.location.origin);
245
- }, []);
297
+ }, [interactionTool]);
246
298
  useEffect(() => {
247
299
  for (const [cardId, iframe] of getRegisteredFrames()) {
248
- sendInspectorInteractionState(iframe, cardId, inspectorOpen);
300
+ sendInspectorInteractionState(iframe, cardId, inspectorOpen, rendererInteractionsEnabled, rendererCursor);
249
301
  sendBoardGestureState(iframe, cardId, presentation === "canvas");
302
+ sendLinkTargetState(iframe, cardId, linksOpenInCards);
250
303
  }
251
- }, [inspectorOpen, presentation, sendBoardGestureState, sendInspectorInteractionState]);
304
+ }, [inspectorOpen, linksOpenInCards, presentation, rendererCursor, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState, sendLinkTargetState]);
252
305
  const broadcastPanModifier = useCallback((spaceHeld) => {
253
306
  for (const [cardId, iframe] of getRegisteredFrames()) {
254
307
  sendPanModifier(iframe, cardId, spaceHeld);
@@ -260,6 +313,12 @@ export function CanvasWorkspace({ primaryUrl }) {
260
313
  return;
261
314
  setBoardCamera(zoomCameraAtPointer(getBoardCamera(), point, board.getBoundingClientRect(), deltaY));
262
315
  }, []);
316
+ const panCanvasByWheel = useCallback((deltaX, deltaY) => {
317
+ if (!Number.isFinite(deltaX) || !Number.isFinite(deltaY))
318
+ return;
319
+ const camera = getBoardCamera();
320
+ setBoardCamera({ ...camera, x: camera.x - deltaX, y: camera.y - deltaY });
321
+ }, []);
263
322
  const fitCanvasToBoard = useCallback(() => {
264
323
  const board = boardRef.current;
265
324
  fitAllCards(board ? { width: board.clientWidth, height: board.clientHeight } : undefined);
@@ -297,6 +356,14 @@ export function CanvasWorkspace({ primaryUrl }) {
297
356
  setBoardCursorClass("");
298
357
  }, [broadcastPanModifier, endPanning]);
299
358
  const handleToolChange = useCallback((nextTool) => {
359
+ if (nextTool === "select" && selectedTool === "select") {
360
+ setInteractionTool("design");
361
+ stopPanMode();
362
+ return;
363
+ }
364
+ if (nextTool === "select") {
365
+ setSelectedElement(null);
366
+ }
300
367
  if (nextTool === "sketch") {
301
368
  if (!sketchEnabled)
302
369
  return;
@@ -304,27 +371,52 @@ export function CanvasWorkspace({ primaryUrl }) {
304
371
  if (!hostElement)
305
372
  return;
306
373
  stopPanMode();
374
+ setCommentToolActive(false);
307
375
  setInteractionTool(nextTool);
308
376
  startSketchCapture(hostElement);
309
377
  return;
310
378
  }
311
379
  if (sketchActive)
312
380
  cancelSketchInteraction();
313
- if (nextTool === "pan") {
314
- if (presentation === "focus") {
315
- const board = boardRef.current;
316
- setCanvasPresentation("canvas", board ? { width: board.clientWidth, height: board.clientHeight } : undefined);
317
- }
318
- setInteractionTool(nextTool);
319
- spaceHeldRef.current = true;
320
- broadcastPanModifier(true);
321
- if (!panningRef.current)
322
- setBoardCursorClass("is-grabbable");
323
- return;
324
- }
381
+ setCommentToolActive(nextTool === "comment");
325
382
  setInteractionTool(nextTool);
326
383
  stopPanMode();
327
- }, [broadcastPanModifier, presentation, sketchActive, sketchEnabled, stopPanMode]);
384
+ }, [selectedTool, sketchActive, sketchEnabled, stopPanMode]);
385
+ useEffect(() => () => setCommentToolActive(false), []);
386
+ useEffect(() => {
387
+ setCommentToolActive(interactionTool === "comment");
388
+ if (temporaryAppInteraction)
389
+ stopPanMode();
390
+ }, [interactionTool, temporaryAppInteraction, stopPanMode]);
391
+ useEffect(() => {
392
+ const onKeyDown = (event) => {
393
+ if (mode === "canvas" && event.code === APP_INTERACTION_KEY && !event.repeat && !isEditableEvent(event))
394
+ setTemporaryAppInteraction(true);
395
+ };
396
+ const onKeyUp = (event) => {
397
+ if (event.code === APP_INTERACTION_KEY)
398
+ setTemporaryAppInteraction(false);
399
+ };
400
+ const onBlur = () => {
401
+ if (!document.hasFocus())
402
+ setTemporaryAppInteraction(false);
403
+ };
404
+ const onVisibility = () => {
405
+ if (document.visibilityState === "hidden")
406
+ setTemporaryAppInteraction(false);
407
+ };
408
+ window.addEventListener("keydown", onKeyDown, true);
409
+ window.addEventListener("keyup", onKeyUp, true);
410
+ window.addEventListener("blur", onBlur);
411
+ document.addEventListener("visibilitychange", onVisibility);
412
+ return () => {
413
+ window.removeEventListener("keydown", onKeyDown, true);
414
+ window.removeEventListener("keyup", onKeyUp, true);
415
+ window.removeEventListener("blur", onBlur);
416
+ document.removeEventListener("visibilitychange", onVisibility);
417
+ setTemporaryAppInteraction(false);
418
+ };
419
+ }, [mode]);
328
420
  useEffect(() => {
329
421
  if (mode !== "canvas")
330
422
  return;
@@ -343,8 +435,21 @@ export function CanvasWorkspace({ primaryUrl }) {
343
435
  const handleZoomStep = useCallback((direction) => {
344
436
  if (presentation === "focus") {
345
437
  if (direction < 0) {
346
- const board = boardRef.current;
347
- setCanvasPresentation("canvas", board ? { width: board.clientWidth, height: board.clientHeight } : undefined);
438
+ batchCanvasChanges(() => {
439
+ const board = boardRef.current;
440
+ const cards = getCanvasCards();
441
+ const activeId = getSelectedCardId() ?? getFocusedCardId();
442
+ const card = cards.find((candidate) => candidate.id === activeId) ?? cards[0];
443
+ if (board && card) {
444
+ const zoom = 0.9;
445
+ setBoardCamera({
446
+ x: board.clientWidth / 2 - (card.x + card.width / 2) * zoom,
447
+ y: board.clientHeight / 2 - (card.y + card.height / 2) * zoom,
448
+ zoom,
449
+ });
450
+ }
451
+ setCanvasPresentation("canvas");
452
+ });
348
453
  }
349
454
  return;
350
455
  }
@@ -360,9 +465,26 @@ export function CanvasWorkspace({ primaryUrl }) {
360
465
  focusCard(cardId);
361
466
  setCanvasPresentation("focus");
362
467
  }, []);
468
+ const duplicateDesign = useCallback((cardId) => {
469
+ const duplicate = addLinkedFrame(cardId);
470
+ if (!duplicate)
471
+ return;
472
+ setSelectedElement(null);
473
+ setCanvasPresentation("canvas");
474
+ const board = boardRef.current;
475
+ focusCanvasCards([duplicate.id], board ? { width: board.clientWidth, height: board.clientHeight } : undefined);
476
+ }, []);
477
+ const createIteration = useCallback(async (cardId, position) => {
478
+ const iteration = await createWorkspaceIteration(cardId, position);
479
+ setSelectedElement(null);
480
+ const board = boardRef.current;
481
+ if (board && !position)
482
+ focusCanvasCards([iteration.id], { width: board.clientWidth, height: board.clientHeight });
483
+ return iteration.id;
484
+ }, []);
363
485
  useEffect(() => {
364
486
  if (!sketchActive && interactionTool === "sketch") {
365
- setInteractionTool("move");
487
+ setInteractionTool("design");
366
488
  }
367
489
  }, [interactionTool, sketchActive]);
368
490
  useLayoutEffect(() => {
@@ -389,6 +511,11 @@ export function CanvasWorkspace({ primaryUrl }) {
389
511
  return subscribeCanvasRendererMessages(({ cardId, iframe, identity, message: msg }) => {
390
512
  if (msg.type === "renderer-hello")
391
513
  return;
514
+ if (msg.type === "app-interaction-modifier") {
515
+ if (mode === "canvas")
516
+ setTemporaryAppInteraction(msg.held === true);
517
+ return;
518
+ }
392
519
  if (isKeyboardShortcutMessage(msg, identity)) {
393
520
  if (msg.phase === "keydown") {
394
521
  const nextTool = canvasToolForCode(msg.code);
@@ -399,11 +526,14 @@ export function CanvasWorkspace({ primaryUrl }) {
399
526
  }
400
527
  const frame = { cardId, iframe };
401
528
  const syncActiveFrameUrl = (url) => {
402
- updateCardUrl(frame.cardId, url);
403
- const activeCardId = getSelectedCardId() ?? getFocusedCardId() ?? getCanvasCards()[0]?.id;
404
- if (activeCardId !== frame.cardId && getCanvasCards().length !== 1)
529
+ if (getCanvasCards().some((card) => card.id === frame.cardId && iterationId(card.content)))
405
530
  return;
406
- window.history.replaceState(window.history.state, "", createNudgeUiEditorUrl(url));
531
+ const activeCardId = getSelectedCardId() ?? getFocusedCardId() ?? getCanvasCards()[0]?.id;
532
+ if (activeCardId === frame.cardId || getCanvasCards().length === 1) {
533
+ activatedTargetRef.current = url;
534
+ window.history.replaceState(window.history.state, "", createNudgeUiEditorUrl(url));
535
+ }
536
+ updateCardUrl(frame.cardId, url);
407
537
  };
408
538
  if (isProjectionAppliedMessage(msg, identity)) {
409
539
  recordCanvasProjectionApplied(frame.cardId, msg.revision);
@@ -412,10 +542,12 @@ export function CanvasWorkspace({ primaryUrl }) {
412
542
  if (isOriginalPreviewActive())
413
543
  return;
414
544
  const frameDocument = frame.iframe.contentDocument;
415
- if (frameDocument && isCanvasCanonicalProjectionRevisionCurrent(frameDocument, msg.revision)) {
416
- const attempt = beginPreviewAttempt(getCanvasPreviewDocument(frame.cardId), getWorkspaceChanges().revision);
545
+ const plan = getCanonicalFrameProjection(frame.cardId, msg.revision);
546
+ const draft = getDraftForCard(frame.cardId);
547
+ if (frameDocument && plan && draft && isCanvasCanonicalProjectionRevisionCurrent(frameDocument, msg.revision)) {
548
+ const attempt = beginPreviewAttempt(getCanvasPreviewDocument(frame.cardId), plan.draftRevision);
417
549
  if (attempt) {
418
- for (const change of getChangesList()) {
550
+ for (const change of draft.contents.changes) {
419
551
  if (!isPreviewableChange(change))
420
552
  continue;
421
553
  const result = verifyManagedStyleProjection(change, null, frameDocument);
@@ -437,15 +569,21 @@ export function CanvasWorkspace({ primaryUrl }) {
437
569
  return;
438
570
  }
439
571
  if (isStructuralProjectionReportMessage(msg, identity)) {
440
- recordCanvasStructuralProjectionReports(frame.cardId, msg.revision, msg.reports);
572
+ const plan = getCanonicalFrameProjection(frame.cardId, msg.revision);
573
+ if (plan)
574
+ recordCanvasStructuralProjectionReports(frame.cardId, msg.revision, msg.reports, plan);
441
575
  return;
442
576
  }
443
577
  if (isRenderedInstanceProjectionReportMessage(msg, identity)) {
444
- recordCanvasRenderedInstanceProjectionReports(frame.cardId, msg.revision, msg.reports);
578
+ const plan = getCanonicalFrameProjection(frame.cardId, msg.revision);
579
+ if (plan)
580
+ recordCanvasRenderedInstanceProjectionReports(frame.cardId, msg.revision, msg.reports, plan);
445
581
  return;
446
582
  }
447
583
  if (isTextProjectionReportMessage(msg, identity)) {
448
- recordCanvasTextProjectionReports(frame.cardId, msg.revision, msg.reports);
584
+ const plan = getCanonicalFrameProjection(frame.cardId, msg.revision);
585
+ if (plan)
586
+ recordCanvasTextProjectionReports(frame.cardId, msg.revision, msg.reports, plan);
449
587
  return;
450
588
  }
451
589
  if (msg.type === "frame-ready") {
@@ -454,8 +592,9 @@ export function CanvasWorkspace({ primaryUrl }) {
454
592
  // A card can finish loading after Space was pressed on the controller.
455
593
  // Seed it with the current modifier state before its first pointer event.
456
594
  sendPanModifier(frame.iframe, frame.cardId, spaceHeldRef.current);
457
- sendInspectorInteractionState(frame.iframe, frame.cardId, inspectorOpen);
595
+ sendInspectorInteractionState(frame.iframe, frame.cardId, inspectorOpen, rendererInteractionsEnabled, spaceHeldRef.current ? "drag" : rendererCursor);
458
596
  sendBoardGestureState(frame.iframe, frame.cardId, presentation === "canvas");
597
+ sendLinkTargetState(frame.iframe, frame.cardId, linksOpenInCards);
459
598
  }
460
599
  if (msg.type === "pan-modifier") {
461
600
  if (presentation !== "canvas")
@@ -467,6 +606,11 @@ export function CanvasWorkspace({ primaryUrl }) {
467
606
  broadcastPanModifier(msg.spaceHeld);
468
607
  return;
469
608
  }
609
+ if (msg.type === "wheel-pan") {
610
+ if (presentation === "canvas" && interactionTool !== "select")
611
+ panCanvasByWheel(msg.deltaX, msg.deltaY);
612
+ return;
613
+ }
470
614
  if (msg.type === "zoom") {
471
615
  if (presentation !== "canvas")
472
616
  return;
@@ -517,14 +661,33 @@ export function CanvasWorkspace({ primaryUrl }) {
517
661
  }
518
662
  if (msg.type !== "navigation-intent")
519
663
  return;
664
+ if (getCanvasCards().some((card) => card.id === frame.cardId && iterationId(card.content)))
665
+ return;
520
666
  const normalized = normalizeUrl(msg.url);
521
667
  if (!normalized || normalized.origin !== window.location.origin)
522
668
  return;
669
+ if (msg.openInCard === true && presentation === "canvas") {
670
+ const card = getCanvasCards().find((candidate) => {
671
+ const route = !iterationId(candidate.content) && normalizeUrl(contentSourceUrl(candidate.content));
672
+ return route && normalizedUrlKey(route) === normalizedUrlKey(normalized);
673
+ }) ?? addCanvasCard(msg.url);
674
+ selectCard(card.id);
675
+ const board = boardRef.current;
676
+ if (board)
677
+ focusCanvasCards([card.id], { width: board.clientWidth, height: board.clientHeight });
678
+ return;
679
+ }
680
+ if (msg.openInCard === true) {
681
+ // A delayed renderer message must not create a new frame after entering focus mode.
682
+ selectCard(frame.cardId);
683
+ activateIframeWorkspace(msg.url, { width: boardRef.current?.clientWidth ?? window.innerWidth, height: boardRef.current?.clientHeight ?? window.innerHeight }, { replaceActiveCard: true });
684
+ return;
685
+ }
523
686
  const frameDocument = frame.iframe.contentDocument;
524
687
  if (frameDocument)
525
688
  disposeInlineTextEdit("route-disposed", frameDocument);
526
689
  });
527
- }, [broadcastPanModifier, endPanning, handleToolChange, inspectorOpen, movePanning, presentation, sendBoardGestureState, sendInspectorInteractionState, sendPanModifier, sketchAvailable, startPanning, zoomAtPointer]);
690
+ }, [broadcastPanModifier, endPanning, handleToolChange, interactionTool, mode, panCanvasByWheel, inspectorOpen, linksOpenInCards, movePanning, presentation, rendererCursor, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState, sendLinkTargetState, sendPanModifier, sketchAvailable, startPanning, zoomAtPointer]);
528
691
  useEffect(() => {
529
692
  if (mode === "canvas" && !hasFitAllRan()) {
530
693
  if (!fitAllScheduledRef.current) {
@@ -538,14 +701,16 @@ export function CanvasWorkspace({ primaryUrl }) {
538
701
  }, [mode, cards.length, inspectorOpen, fitCanvasToBoard]);
539
702
  useEffect(() => {
540
703
  function onKeyDown(e) {
541
- if (mode !== "canvas" || presentation !== "canvas" || sketchActive)
704
+ if (mode !== "canvas" || presentation !== "canvas" || sketchActive || interactionTool === "select")
542
705
  return;
543
706
  if ((e.key === "Delete" || e.key === "Backspace") && !isEditableEvent(e)) {
707
+ if (e.defaultPrevented || getSelectedElements().length > 0 || getCanvasCards().length < 2)
708
+ return;
544
709
  const selectedCardId = getSelectedCardId();
545
710
  if (!selectedCardId)
546
711
  return;
547
712
  e.preventDefault();
548
- removeCanvasCard(selectedCardId);
713
+ removeFrame(selectedCardId);
549
714
  return;
550
715
  }
551
716
  if (e.code === "Space" && !e.repeat) {
@@ -561,7 +726,7 @@ export function CanvasWorkspace({ primaryUrl }) {
561
726
  }
562
727
  function onKeyUp(e) {
563
728
  if (e.code === "Space") {
564
- spaceHeldRef.current = interactionTool === "pan";
729
+ spaceHeldRef.current = false;
565
730
  broadcastPanModifier(spaceHeldRef.current);
566
731
  if (!panningRef.current) {
567
732
  setBoardCursorClass(spaceHeldRef.current ? "is-grabbable" : "");
@@ -575,6 +740,15 @@ export function CanvasWorkspace({ primaryUrl }) {
575
740
  window.removeEventListener("keyup", onKeyUp, true);
576
741
  };
577
742
  }, [broadcastPanModifier, interactionTool, mode, presentation, sketchActive]);
743
+ const handleBoardClick = useCallback((event) => {
744
+ if (presentation !== "canvas" || sketchActive || spaceHeldRef.current || panningRef.current)
745
+ return;
746
+ if (event.target === event.currentTarget
747
+ || (event.target instanceof HTMLElement && event.target.dataset.test === "canvas-board-content")) {
748
+ setSelectedElement(null);
749
+ deselectCard();
750
+ }
751
+ }, [presentation, sketchActive]);
578
752
  const handleBoardPointerDown = useCallback((e) => {
579
753
  if (presentation !== "canvas" || sketchActive)
580
754
  return;
@@ -599,22 +773,35 @@ export function CanvasWorkspace({ primaryUrl }) {
599
773
  window.addEventListener("pointerup", onUp);
600
774
  }, [endPanning, movePanning, presentation, sketchActive, startPanning]);
601
775
  useEffect(() => {
602
- if (mode !== "canvas" || presentation !== "canvas" || sketchActive)
776
+ if (mode !== "canvas" || presentation !== "canvas")
603
777
  return;
604
778
  function handleGlobalWheel(e) {
605
- if (!e.ctrlKey && !e.metaKey)
779
+ const zoom = e.ctrlKey || e.metaKey;
780
+ const board = boardRef.current;
781
+ if (zoom && sketchActive)
782
+ return;
783
+ const path = e.composedPath();
784
+ // Comment pins overlay the board without being its DOM descendants.
785
+ const overCommentPin = path.some((node) => node instanceof Element && node.matches(".comment-pin"));
786
+ if (!zoom && (interactionTool === "select" || !board || (!path.includes(board) && !overCommentPin)))
606
787
  return;
607
788
  e.preventDefault();
608
789
  e.stopPropagation();
609
- zoomAtPointer({ x: e.clientX, y: e.clientY }, e.deltaY);
790
+ if (zoom)
791
+ zoomAtPointer({ x: e.clientX, y: e.clientY }, e.deltaY);
792
+ else if (board) {
793
+ const delta = wheelPanDelta(e, { width: board.clientWidth, height: board.clientHeight });
794
+ panCanvasByWheel(delta.x, delta.y);
795
+ }
610
796
  }
611
797
  window.addEventListener("wheel", handleGlobalWheel, { capture: true, passive: false });
612
798
  return () => window.removeEventListener("wheel", handleGlobalWheel, { capture: true });
613
- }, [mode, presentation, sketchActive, zoomAtPointer]);
799
+ }, [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));
614
801
  if (mode !== "canvas")
615
802
  return null;
616
- 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}`.trim(), "data-test": "canvas-board", ref: boardRef, onPointerDown: handleBoardPointerDown, 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: {
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: {
617
804
  transform: `translate(${camera.x}px, ${camera.y}px) scale(${camera.zoom})`,
618
805
  transformOrigin: "0 0",
619
- }, "data-test": "canvas-board-content", children: cards.map((card) => (_jsx(CanvasCard, { card: card, presentation: presentation, presentationCard: card.id === presentationCardId, onShowFocus: showFocus, documentOwner: inspectorSession }, card.id))) }), !sketchActive ? (_jsx(CanvasToolbar, { tool: interactionTool, sketchEnabled: sketchAvailable, onToolChange: handleToolChange })) : null] }) }), 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] }));
806
+ }, "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] }));
620
807
  }