nudge-ui 0.2.2 → 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 (226) hide show
  1. package/README.md +164 -46
  2. package/dist/client.mjs +252 -61
  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 +38 -32
  7. package/dist/hosts/astro/integration.js +5 -1
  8. package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
  9. package/dist/hosts/astro/markdownIdentity.js +81 -0
  10. package/dist/hosts/next/manifest.js +1 -1
  11. package/dist/hosts/next/sidecar.js +5 -1
  12. package/dist/hosts/static/manifest.js +1 -1
  13. package/dist/hosts/static/server.js +3 -0
  14. package/dist/hosts/vite/vite.d.ts +3 -0
  15. package/dist/hosts/vite/vite.js +24 -4
  16. package/dist/inspector/agent/McpConnectionDialog.js +13 -19
  17. package/dist/inspector/agent/activity.d.ts +18 -0
  18. package/dist/inspector/agent/activity.js +70 -0
  19. package/dist/inspector/agent/client.d.ts +2 -0
  20. package/dist/inspector/agent/client.js +29 -1
  21. package/dist/inspector/agent/connectionStatus.js +1 -1
  22. package/dist/inspector/agent/httpTransport.js +9 -2
  23. package/dist/inspector/agent/protocol.d.ts +6 -3
  24. package/dist/inspector/agent/protocol.js +1 -1
  25. package/dist/inspector/agent/verification.d.ts +11 -1
  26. package/dist/inspector/agent/verification.js +53 -25
  27. package/dist/inspector/canvas/AgentActivityOverlay.d.ts +13 -0
  28. package/dist/inspector/canvas/AgentActivityOverlay.js +111 -0
  29. package/dist/inspector/canvas/CanvasCard.d.ts +9 -2
  30. package/dist/inspector/canvas/CanvasCard.js +133 -38
  31. package/dist/inspector/canvas/CanvasElementOverlay.js +152 -16
  32. package/dist/inspector/canvas/CanvasToolIcons.d.ts +10 -0
  33. package/dist/inspector/canvas/CanvasToolIcons.js +20 -0
  34. package/dist/inspector/canvas/CanvasToolbar.js +8 -4
  35. package/dist/inspector/canvas/CanvasWorkspace.js +225 -57
  36. package/dist/inspector/canvas/StaleChangeIndicator.js +3 -13
  37. package/dist/inspector/canvas/agentPresentation.d.ts +8 -8
  38. package/dist/inspector/canvas/agentPresentation.js +64 -52
  39. package/dist/inspector/canvas/canvasGestures.d.ts +1 -1
  40. package/dist/inspector/canvas/canvasGestures.js +1 -1
  41. package/dist/inspector/canvas/canvasStore.d.ts +46 -82
  42. package/dist/inspector/canvas/canvasStore.js +251 -284
  43. package/dist/inspector/canvas/frameContent.d.ts +21 -0
  44. package/dist/inspector/canvas/frameContent.js +44 -0
  45. package/dist/inspector/canvas/frameProtocol.d.ts +21 -4
  46. package/dist/inspector/canvas/frameProtocol.js +3 -3
  47. package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -1
  48. package/dist/inspector/canvas/keyboardShortcuts.js +4 -4
  49. package/dist/inspector/canvas/layout.d.ts +52 -0
  50. package/dist/inspector/canvas/layout.js +106 -0
  51. package/dist/inspector/canvas/projection.d.ts +15 -5
  52. package/dist/inspector/canvas/projection.js +69 -13
  53. package/dist/inspector/canvas/rendererBootstrap.js +32 -4
  54. package/dist/inspector/canvas/rendererElementSelector.js +47 -6
  55. package/dist/inspector/canvas/rendererStylesheet.js +2 -2
  56. package/dist/inspector/canvas/resizeHandleScale.js +1 -1
  57. package/dist/inspector/canvas/sessionStore.d.ts +11 -26
  58. package/dist/inspector/canvas/sessionStore.js +43 -183
  59. package/dist/inspector/canvas/staleChangeDetector.js +9 -3
  60. package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
  61. package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
  62. package/dist/inspector/canvas/viewStore.d.ts +38 -0
  63. package/dist/inspector/canvas/viewStore.js +104 -0
  64. package/dist/inspector/canvas/wheelPan.d.ts +8 -0
  65. package/dist/inspector/canvas/wheelPan.js +8 -0
  66. package/dist/inspector/changes/changesLog.d.ts +6 -5
  67. package/dist/inspector/changes/changesLog.js +40 -32
  68. package/dist/inspector/changes/draftChanges.d.ts +56 -0
  69. package/dist/inspector/changes/draftChanges.js +300 -0
  70. package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
  71. package/dist/inspector/changes/previewDiagnostics.js +11 -25
  72. package/dist/inspector/clientManifest.d.ts +1 -1
  73. package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
  74. package/dist/inspector/comments/CommentsOverlay.js +250 -0
  75. package/dist/inspector/comments/element.d.ts +6 -0
  76. package/dist/inspector/comments/element.js +47 -0
  77. package/dist/inspector/comments/store.d.ts +29 -0
  78. package/dist/inspector/comments/store.js +121 -0
  79. package/dist/inspector/comments/verification.d.ts +5 -0
  80. package/dist/inspector/comments/verification.js +52 -0
  81. package/dist/inspector/drafts/model.d.ts +35 -0
  82. package/dist/inspector/drafts/model.js +45 -0
  83. package/dist/inspector/drafts/store.d.ts +28 -0
  84. package/dist/inspector/drafts/store.js +231 -0
  85. package/dist/inspector/index.js +20 -27
  86. package/dist/inspector/inline-text/inlineTextEditor.js +7 -0
  87. package/dist/inspector/inspection/useBrowserCssInspection.js +15 -1
  88. package/dist/inspector/iterations/capture.d.ts +2 -0
  89. package/dist/inspector/iterations/capture.js +173 -0
  90. package/dist/inspector/iterations/client.d.ts +7 -0
  91. package/dist/inspector/iterations/client.js +52 -0
  92. package/dist/inspector/iterations/coordinator.d.ts +15 -0
  93. package/dist/inspector/iterations/coordinator.js +130 -0
  94. package/dist/inspector/iterations/prompt.d.ts +3 -0
  95. package/dist/inspector/iterations/prompt.js +26 -0
  96. package/dist/inspector/overlay/DomNavigator.js +21 -15
  97. package/dist/inspector/overlay/interactionStyles.js +3 -1
  98. package/dist/inspector/overlay/spacingGestures.d.ts +13 -1
  99. package/dist/inspector/overlay/spacingGestures.js +82 -5
  100. package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
  101. package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
  102. package/dist/inspector/projection/renderedInstance.d.ts +2 -1
  103. package/dist/inspector/projection/renderedInstance.js +9 -5
  104. package/dist/inspector/projection/structuralProjection.d.ts +2 -1
  105. package/dist/inspector/projection/structuralProjection.js +19 -15
  106. package/dist/inspector/projection/textProjection.d.ts +2 -1
  107. package/dist/inspector/projection/textProjection.js +8 -4
  108. package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
  109. package/dist/inspector/prompt/clipboardHandoff.d.ts +10 -18
  110. package/dist/inspector/prompt/clipboardHandoff.js +92 -143
  111. package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
  112. package/dist/inspector/prompt/generatePrompt.js +17 -16
  113. package/dist/inspector/runtime/runtimeConfig.d.ts +9 -0
  114. package/dist/inspector/runtime/runtimeConfig.js +24 -0
  115. package/dist/inspector/settings/SettingsDialog.js +21 -8
  116. package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
  117. package/dist/inspector/shell/AgentStatusButton.js +36 -0
  118. package/dist/inspector/shell/AppShell.js +4 -2
  119. package/dist/inspector/shell/ChangesLog.js +46 -26
  120. package/dist/inspector/shell/CopyPromptButton.js +138 -89
  121. package/dist/inspector/shell/EmptyState.js +9 -8
  122. package/dist/inspector/shell/InspectorShell.js +13 -21
  123. package/dist/inspector/shell/PeekOriginalButton.js +24 -23
  124. package/dist/inspector/shell/originalPreview.js +5 -5
  125. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  126. package/dist/inspector/shell/shortcuts.js +11 -0
  127. package/dist/inspector/sketch/SketchEditor.js +14 -14
  128. package/dist/inspector/sketch/SketchEntryButton.js +1 -1
  129. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
  130. package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
  131. package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
  132. package/dist/inspector/sketch/SketchOverlay.js +7 -7
  133. package/dist/inspector/sketch/SketchWorkspace.js +18 -12
  134. package/dist/inspector/sketch/interaction.d.ts +1 -0
  135. package/dist/inspector/sketch/interaction.js +8 -4
  136. package/dist/inspector/sketch/model.d.ts +5 -0
  137. package/dist/inspector/sketch/model.js +6 -2
  138. package/dist/inspector/sketch/raster.d.ts +1 -2
  139. package/dist/inspector/sketch/raster.js +68 -4
  140. package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
  141. package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
  142. package/dist/inspector/styleEditors/BorderEditor.js +49 -78
  143. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
  144. package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
  145. package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
  146. package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
  147. package/dist/inspector/styleEditors/ColorPicker.js +10 -4
  148. package/dist/inspector/styleEditors/GapField.js +3 -5
  149. package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
  150. package/dist/inspector/styleEditors/GridChildSection.js +18 -9
  151. package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
  152. package/dist/inspector/styleEditors/GridPicker.js +8 -2
  153. package/dist/inspector/styleEditors/GridSection.js +1 -1
  154. package/dist/inspector/styleEditors/GridValueField.js +19 -14
  155. package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
  156. package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
  157. package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
  158. package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
  159. package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
  160. package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
  161. package/dist/inspector/styleEditors/LayoutSection.js +32 -11
  162. package/dist/inspector/styleEditors/SpacingBox.js +7 -5
  163. package/dist/inspector/styleEditors/Typography.js +11 -3
  164. package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
  165. package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
  166. package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
  167. package/dist/inspector/styleEditors/gradientValue.js +114 -0
  168. package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
  169. package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
  170. package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
  171. package/dist/inspector/styleEditors/layoutValue.js +0 -42
  172. package/dist/inspector/styleEditors/nudgeValue.d.ts +2 -2
  173. package/dist/inspector/styleEditors/nudgeValue.js +36 -8
  174. package/dist/inspector/tokens/TokenField.d.ts +7 -0
  175. package/dist/inspector/tokens/TokenField.js +29 -21
  176. package/dist/inspector/tokens/TokenLabel.js +5 -14
  177. package/dist/inspector/tokens/editActions.d.ts +1 -1
  178. package/dist/inspector/tokens/editActions.js +1 -1
  179. package/dist/inspector/ui/AtRuleContext.js +4 -6
  180. package/dist/inspector/ui/Button.d.ts +6 -2
  181. package/dist/inspector/ui/Button.js +4 -3
  182. package/dist/inspector/ui/ControlSurface.d.ts +2 -2
  183. package/dist/inspector/ui/ControlSurface.js +4 -3
  184. package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
  185. package/dist/inspector/ui/InlineStyleWarning.js +13 -0
  186. package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
  187. package/dist/inspector/ui/InspectorPopover.js +4 -5
  188. package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
  189. package/dist/inspector/ui/PopoverListbox.js +8 -7
  190. package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
  191. package/dist/inspector/ui/SegmentedControl.js +2 -2
  192. package/dist/inspector/ui/Select.d.ts +6 -1
  193. package/dist/inspector/ui/Select.js +7 -11
  194. package/dist/inspector/ui/SideValuesField.js +1 -1
  195. package/dist/inspector/ui/Tooltip.d.ts +26 -0
  196. package/dist/inspector/ui/Tooltip.js +32 -0
  197. package/dist/inspector/ui/classNames.d.ts +2 -0
  198. package/dist/inspector/ui/classNames.js +4 -0
  199. package/dist/inspector/ui/customCursors.d.ts +23 -0
  200. package/dist/inspector/ui/customCursors.js +122 -0
  201. package/dist/inspector/ui/dragCursor.d.ts +0 -2
  202. package/dist/inspector/ui/dragCursor.js +2 -10
  203. package/dist/inspector/ui/portalContainer.d.ts +2 -0
  204. package/dist/inspector/ui/portalContainer.js +6 -0
  205. package/dist/inspector/ui/styles.js +29 -21
  206. package/dist/inspector/workspace/commands.d.ts +9 -0
  207. package/dist/inspector/workspace/commands.js +82 -0
  208. package/dist/inspector/workspace/controller.d.ts +4 -0
  209. package/dist/inspector/workspace/controller.js +66 -0
  210. package/dist/inspector/workspace/handoff.d.ts +42 -0
  211. package/dist/inspector/workspace/handoff.js +115 -0
  212. package/dist/inspector/workspace/timeline.d.ts +30 -0
  213. package/dist/inspector/workspace/timeline.js +108 -0
  214. package/dist/internal/agent-protocol.d.ts +19 -2
  215. package/dist/internal/agent-protocol.js +13 -0
  216. package/dist/nudge-ui.mjs +558 -311
  217. package/dist/project/artifacts.d.ts +13 -0
  218. package/dist/project/artifacts.js +253 -0
  219. package/dist/transport/artifacts.d.ts +8 -0
  220. package/dist/transport/artifacts.js +9 -0
  221. package/dist/transport/editor.js +3 -1
  222. package/dist/transport/routes.d.ts +2 -0
  223. package/dist/transport/routes.js +2 -0
  224. package/package.json +6 -2
  225. package/dist/inspector/changes/workspaceChanges.d.ts +0 -47
  226. package/dist/inspector/changes/workspaceChanges.js +0 -265
@@ -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, addCanvasCard, findCardByNormalizedUrl, selectCard, 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 /*\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";
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__divider {\n flex-shrink: 0;\n width: 1px;\n height: var(--control-height-small);\n margin-inline: var(--space-4);\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";
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,10 +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("design");
78
- const rendererInteractionsEnabled = interactionTool !== "select";
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;
79
105
  const linksOpenInCards = interactionTool === "select" && presentation === "canvas";
80
106
  const presentationCardId = selectedCardId ?? focusedCardId ?? cards[0]?.id ?? null;
107
+ const linkedFrameIds = getLinkedFrameIds(cards, selectedCardId);
81
108
  useLayoutEffect(() => {
82
109
  const board = boardRef.current;
83
110
  const cardElement = presentationCardId
@@ -157,7 +184,16 @@ export function CanvasWorkspace({ primaryUrl }) {
157
184
  width: board.clientWidth,
158
185
  height: board.clientHeight,
159
186
  };
160
- 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
+ }
161
197
  if (runtimeConfig.demo === true && primaryCard && !demoSeededRef.current) {
162
198
  demoSeededRef.current = true;
163
199
  const demoPages = runtimeConfig.demoPages ?? [];
@@ -201,7 +237,7 @@ export function CanvasWorkspace({ primaryUrl }) {
201
237
  }
202
238
  focusCard(primaryCard.id);
203
239
  }
204
- }, [primaryUrl, cards.length, runtimeConfig.demo, runtimeConfig.demoPages, runtimeConfig.demoCardLabels]);
240
+ }, [primaryUrl, cards.length, presentation, runtimeConfig.demo, runtimeConfig.demoPages, runtimeConfig.demoCardLabels]);
205
241
  useEffect(() => {
206
242
  if (primaryUrl && activatedTargetRef.current !== primaryUrl)
207
243
  return;
@@ -209,7 +245,7 @@ export function CanvasWorkspace({ primaryUrl }) {
209
245
  const activeCard = cards.find((card) => card.id === activeCardId);
210
246
  if (!activeCard)
211
247
  return;
212
- const editorUrl = createNudgeUiEditorUrl(activeCard.url);
248
+ const editorUrl = createNudgeUiEditorUrl(contentSourceUrl(activeCard.content));
213
249
  if (window.location.href !== editorUrl) {
214
250
  window.history.replaceState(window.history.state, "", editorUrl);
215
251
  }
@@ -222,9 +258,10 @@ export function CanvasWorkspace({ primaryUrl }) {
222
258
  workspaceId: WORKSPACE_ID,
223
259
  cardId,
224
260
  spaceHeld,
261
+ cursor: spaceHeld ? "drag" : rendererCursor,
225
262
  }, window.location.origin);
226
- }, []);
227
- const sendInspectorInteractionState = useCallback((iframe, cardId, open, interactionsEnabled) => {
263
+ }, [rendererCursor]);
264
+ const sendInspectorInteractionState = useCallback((iframe, cardId, open, interactionsEnabled, cursor) => {
228
265
  iframe.contentWindow?.postMessage({
229
266
  type: "inspector-interaction-state",
230
267
  protocolVersion: PROTOCOL_VERSION,
@@ -233,6 +270,8 @@ export function CanvasWorkspace({ primaryUrl }) {
233
270
  cardId,
234
271
  open,
235
272
  interactionsEnabled,
273
+ cursor,
274
+ commentCursor: cursor === "comment" ? createCommentCursor(boardRef.current) : undefined,
236
275
  }, window.location.origin);
237
276
  }, []);
238
277
  const sendLinkTargetState = useCallback((iframe, cardId, openInCard) => {
@@ -253,15 +292,16 @@ export function CanvasWorkspace({ primaryUrl }) {
253
292
  workspaceId: WORKSPACE_ID,
254
293
  cardId,
255
294
  enabled,
295
+ panScroll: enabled && interactionTool !== "select",
256
296
  }, window.location.origin);
257
- }, []);
297
+ }, [interactionTool]);
258
298
  useEffect(() => {
259
299
  for (const [cardId, iframe] of getRegisteredFrames()) {
260
- sendInspectorInteractionState(iframe, cardId, inspectorOpen, rendererInteractionsEnabled);
300
+ sendInspectorInteractionState(iframe, cardId, inspectorOpen, rendererInteractionsEnabled, rendererCursor);
261
301
  sendBoardGestureState(iframe, cardId, presentation === "canvas");
262
302
  sendLinkTargetState(iframe, cardId, linksOpenInCards);
263
303
  }
264
- }, [inspectorOpen, linksOpenInCards, presentation, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState, sendLinkTargetState]);
304
+ }, [inspectorOpen, linksOpenInCards, presentation, rendererCursor, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState, sendLinkTargetState]);
265
305
  const broadcastPanModifier = useCallback((spaceHeld) => {
266
306
  for (const [cardId, iframe] of getRegisteredFrames()) {
267
307
  sendPanModifier(iframe, cardId, spaceHeld);
@@ -273,6 +313,12 @@ export function CanvasWorkspace({ primaryUrl }) {
273
313
  return;
274
314
  setBoardCamera(zoomCameraAtPointer(getBoardCamera(), point, board.getBoundingClientRect(), deltaY));
275
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
+ }, []);
276
322
  const fitCanvasToBoard = useCallback(() => {
277
323
  const board = boardRef.current;
278
324
  fitAllCards(board ? { width: board.clientWidth, height: board.clientHeight } : undefined);
@@ -310,6 +356,14 @@ export function CanvasWorkspace({ primaryUrl }) {
310
356
  setBoardCursorClass("");
311
357
  }, [broadcastPanModifier, endPanning]);
312
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
+ }
313
367
  if (nextTool === "sketch") {
314
368
  if (!sketchEnabled)
315
369
  return;
@@ -317,27 +371,52 @@ export function CanvasWorkspace({ primaryUrl }) {
317
371
  if (!hostElement)
318
372
  return;
319
373
  stopPanMode();
374
+ setCommentToolActive(false);
320
375
  setInteractionTool(nextTool);
321
376
  startSketchCapture(hostElement);
322
377
  return;
323
378
  }
324
379
  if (sketchActive)
325
380
  cancelSketchInteraction();
326
- if (nextTool === "pan") {
327
- if (presentation === "focus") {
328
- const board = boardRef.current;
329
- setCanvasPresentation("canvas", board ? { width: board.clientWidth, height: board.clientHeight } : undefined);
330
- }
331
- setInteractionTool(nextTool);
332
- spaceHeldRef.current = true;
333
- broadcastPanModifier(true);
334
- if (!panningRef.current)
335
- setBoardCursorClass("is-grabbable");
336
- return;
337
- }
381
+ setCommentToolActive(nextTool === "comment");
338
382
  setInteractionTool(nextTool);
339
383
  stopPanMode();
340
- }, [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]);
341
420
  useEffect(() => {
342
421
  if (mode !== "canvas")
343
422
  return;
@@ -356,8 +435,21 @@ export function CanvasWorkspace({ primaryUrl }) {
356
435
  const handleZoomStep = useCallback((direction) => {
357
436
  if (presentation === "focus") {
358
437
  if (direction < 0) {
359
- const board = boardRef.current;
360
- 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
+ });
361
453
  }
362
454
  return;
363
455
  }
@@ -373,6 +465,23 @@ export function CanvasWorkspace({ primaryUrl }) {
373
465
  focusCard(cardId);
374
466
  setCanvasPresentation("focus");
375
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
+ }, []);
376
485
  useEffect(() => {
377
486
  if (!sketchActive && interactionTool === "sketch") {
378
487
  setInteractionTool("design");
@@ -402,6 +511,11 @@ export function CanvasWorkspace({ primaryUrl }) {
402
511
  return subscribeCanvasRendererMessages(({ cardId, iframe, identity, message: msg }) => {
403
512
  if (msg.type === "renderer-hello")
404
513
  return;
514
+ if (msg.type === "app-interaction-modifier") {
515
+ if (mode === "canvas")
516
+ setTemporaryAppInteraction(msg.held === true);
517
+ return;
518
+ }
405
519
  if (isKeyboardShortcutMessage(msg, identity)) {
406
520
  if (msg.phase === "keydown") {
407
521
  const nextTool = canvasToolForCode(msg.code);
@@ -412,11 +526,14 @@ export function CanvasWorkspace({ primaryUrl }) {
412
526
  }
413
527
  const frame = { cardId, iframe };
414
528
  const syncActiveFrameUrl = (url) => {
415
- updateCardUrl(frame.cardId, url);
416
- const activeCardId = getSelectedCardId() ?? getFocusedCardId() ?? getCanvasCards()[0]?.id;
417
- if (activeCardId !== frame.cardId && getCanvasCards().length !== 1)
529
+ if (getCanvasCards().some((card) => card.id === frame.cardId && iterationId(card.content)))
418
530
  return;
419
- 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);
420
537
  };
421
538
  if (isProjectionAppliedMessage(msg, identity)) {
422
539
  recordCanvasProjectionApplied(frame.cardId, msg.revision);
@@ -425,10 +542,12 @@ export function CanvasWorkspace({ primaryUrl }) {
425
542
  if (isOriginalPreviewActive())
426
543
  return;
427
544
  const frameDocument = frame.iframe.contentDocument;
428
- if (frameDocument && isCanvasCanonicalProjectionRevisionCurrent(frameDocument, msg.revision)) {
429
- 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);
430
549
  if (attempt) {
431
- for (const change of getChangesList()) {
550
+ for (const change of draft.contents.changes) {
432
551
  if (!isPreviewableChange(change))
433
552
  continue;
434
553
  const result = verifyManagedStyleProjection(change, null, frameDocument);
@@ -450,15 +569,21 @@ export function CanvasWorkspace({ primaryUrl }) {
450
569
  return;
451
570
  }
452
571
  if (isStructuralProjectionReportMessage(msg, identity)) {
453
- 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);
454
575
  return;
455
576
  }
456
577
  if (isRenderedInstanceProjectionReportMessage(msg, identity)) {
457
- 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);
458
581
  return;
459
582
  }
460
583
  if (isTextProjectionReportMessage(msg, identity)) {
461
- 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);
462
587
  return;
463
588
  }
464
589
  if (msg.type === "frame-ready") {
@@ -467,7 +592,7 @@ export function CanvasWorkspace({ primaryUrl }) {
467
592
  // A card can finish loading after Space was pressed on the controller.
468
593
  // Seed it with the current modifier state before its first pointer event.
469
594
  sendPanModifier(frame.iframe, frame.cardId, spaceHeldRef.current);
470
- sendInspectorInteractionState(frame.iframe, frame.cardId, inspectorOpen, rendererInteractionsEnabled);
595
+ sendInspectorInteractionState(frame.iframe, frame.cardId, inspectorOpen, rendererInteractionsEnabled, spaceHeldRef.current ? "drag" : rendererCursor);
471
596
  sendBoardGestureState(frame.iframe, frame.cardId, presentation === "canvas");
472
597
  sendLinkTargetState(frame.iframe, frame.cardId, linksOpenInCards);
473
598
  }
@@ -481,6 +606,11 @@ export function CanvasWorkspace({ primaryUrl }) {
481
606
  broadcastPanModifier(msg.spaceHeld);
482
607
  return;
483
608
  }
609
+ if (msg.type === "wheel-pan") {
610
+ if (presentation === "canvas" && interactionTool !== "select")
611
+ panCanvasByWheel(msg.deltaX, msg.deltaY);
612
+ return;
613
+ }
484
614
  if (msg.type === "zoom") {
485
615
  if (presentation !== "canvas")
486
616
  return;
@@ -531,19 +661,33 @@ export function CanvasWorkspace({ primaryUrl }) {
531
661
  }
532
662
  if (msg.type !== "navigation-intent")
533
663
  return;
664
+ if (getCanvasCards().some((card) => card.id === frame.cardId && iterationId(card.content)))
665
+ return;
534
666
  const normalized = normalizeUrl(msg.url);
535
667
  if (!normalized || normalized.origin !== window.location.origin)
536
668
  return;
537
- if (msg.openInCard === true) {
538
- const card = findCardByNormalizedUrl(normalized) ?? addCanvasCard(msg.url);
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);
539
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 });
540
684
  return;
541
685
  }
542
686
  const frameDocument = frame.iframe.contentDocument;
543
687
  if (frameDocument)
544
688
  disposeInlineTextEdit("route-disposed", frameDocument);
545
689
  });
546
- }, [broadcastPanModifier, endPanning, handleToolChange, inspectorOpen, linksOpenInCards, movePanning, presentation, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState, sendLinkTargetState, sendPanModifier, sketchAvailable, startPanning, zoomAtPointer]);
690
+ }, [broadcastPanModifier, endPanning, handleToolChange, interactionTool, mode, panCanvasByWheel, inspectorOpen, linksOpenInCards, movePanning, presentation, rendererCursor, rendererInteractionsEnabled, sendBoardGestureState, sendInspectorInteractionState, sendLinkTargetState, sendPanModifier, sketchAvailable, startPanning, zoomAtPointer]);
547
691
  useEffect(() => {
548
692
  if (mode === "canvas" && !hasFitAllRan()) {
549
693
  if (!fitAllScheduledRef.current) {
@@ -557,14 +701,16 @@ export function CanvasWorkspace({ primaryUrl }) {
557
701
  }, [mode, cards.length, inspectorOpen, fitCanvasToBoard]);
558
702
  useEffect(() => {
559
703
  function onKeyDown(e) {
560
- if (mode !== "canvas" || presentation !== "canvas" || sketchActive)
704
+ if (mode !== "canvas" || presentation !== "canvas" || sketchActive || interactionTool === "select")
561
705
  return;
562
706
  if ((e.key === "Delete" || e.key === "Backspace") && !isEditableEvent(e)) {
707
+ if (e.defaultPrevented || getSelectedElements().length > 0 || getCanvasCards().length < 2)
708
+ return;
563
709
  const selectedCardId = getSelectedCardId();
564
710
  if (!selectedCardId)
565
711
  return;
566
712
  e.preventDefault();
567
- removeCanvasCard(selectedCardId);
713
+ removeFrame(selectedCardId);
568
714
  return;
569
715
  }
570
716
  if (e.code === "Space" && !e.repeat) {
@@ -580,7 +726,7 @@ export function CanvasWorkspace({ primaryUrl }) {
580
726
  }
581
727
  function onKeyUp(e) {
582
728
  if (e.code === "Space") {
583
- spaceHeldRef.current = interactionTool === "pan";
729
+ spaceHeldRef.current = false;
584
730
  broadcastPanModifier(spaceHeldRef.current);
585
731
  if (!panningRef.current) {
586
732
  setBoardCursorClass(spaceHeldRef.current ? "is-grabbable" : "");
@@ -594,6 +740,15 @@ export function CanvasWorkspace({ primaryUrl }) {
594
740
  window.removeEventListener("keyup", onKeyUp, true);
595
741
  };
596
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]);
597
752
  const handleBoardPointerDown = useCallback((e) => {
598
753
  if (presentation !== "canvas" || sketchActive)
599
754
  return;
@@ -618,22 +773,35 @@ export function CanvasWorkspace({ primaryUrl }) {
618
773
  window.addEventListener("pointerup", onUp);
619
774
  }, [endPanning, movePanning, presentation, sketchActive, startPanning]);
620
775
  useEffect(() => {
621
- if (mode !== "canvas" || presentation !== "canvas" || sketchActive)
776
+ if (mode !== "canvas" || presentation !== "canvas")
622
777
  return;
623
778
  function handleGlobalWheel(e) {
624
- 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)))
625
787
  return;
626
788
  e.preventDefault();
627
789
  e.stopPropagation();
628
- 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
+ }
629
796
  }
630
797
  window.addEventListener("wheel", handleGlobalWheel, { capture: true, passive: false });
631
798
  return () => window.removeEventListener("wheel", handleGlobalWheel, { capture: true });
632
- }, [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));
633
801
  if (mode !== "canvas")
634
802
  return null;
635
- 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: {
636
804
  transform: `translate(${camera.x}px, ${camera.y}px) scale(${camera.zoom})`,
637
805
  transformOrigin: "0 0",
638
- }, "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] }));
639
807
  }
@@ -3,8 +3,6 @@ import { useSyncExternalStore } from "react";
3
3
  import { isPreviewableChange, isTokenChange } from "../changes/changesLog.js";
4
4
  import { changeKey } from "../changes/model.js";
5
5
  import { getAnyPreviewDiagnostic, getPreviewDiagnosticRevision, subscribePreviewDiagnostics, } from "../changes/previewDiagnostics.js";
6
- import { humanizeSelector } from "../tokens/catalog.js";
7
- import { getScopingSelectorPattern } from "../runtime/runtimeConfig.js";
8
6
  import { isVerificationPending } from "./staleChangeDetector.js";
9
7
  export function StaleChangeIndicator({ change }) {
10
8
  useSyncExternalStore(subscribePreviewDiagnostics, getPreviewDiagnosticRevision, getPreviewDiagnosticRevision);
@@ -21,23 +19,15 @@ export function StaleChangeIndicator({ change }) {
21
19
  return (_jsx("span", { className: "changes__diagnostic", "data-test": "preview-applied", "data-status": "applied", children: "Preview active" }));
22
20
  }
23
21
  if (result.reason === "target-missing") {
24
- // ADR-0011: host scoping markers are opaque structure and must not
25
- // surface as human-facing guidance; the raw selector stays in change
26
- // records for managed-rule targeting.
27
- const selectorLabel = humanizeSelector(change.selector, getScopingSelectorPattern())
28
- || change.selector;
29
- return (_jsx("span", { className: "changes__stale", "data-test": "stale-missing", title: `Selector: ${selectorLabel}`, children: "Source missing \u2014 this edit may be stale" }));
22
+ return (_jsx("span", { className: "changes__stale", "data-test": "stale-missing", children: "Source missing \u2014 this edit may be stale" }));
30
23
  }
31
24
  if (result.reason === "token-drift") {
32
- const currentValue = result.computedValue
33
- ? ` (current: ${result.computedValue})`
34
- : "";
35
25
  if (isTokenChange(change)) {
36
- return (_jsx("span", { className: "changes__stale", "data-test": "stale-token-drift", title: `Token ${change.tokenName} baseline has changed since this edit was made${currentValue}`, children: "Token baseline has changed" }));
26
+ return (_jsx("span", { className: "changes__stale", "data-test": "stale-token-drift", children: "Token baseline has changed" }));
37
27
  }
38
28
  return null;
39
29
  }
40
- return (_jsxs("span", { className: "changes__conflict", "data-test": "preview-conflict", title: `Computed: ${result.computedValue}`, children: ["Preview Blocked (", result.reason
30
+ return (_jsxs("span", { className: "changes__conflict", "data-test": "preview-conflict", children: ["Preview Blocked (", result.reason
41
31
  ? result.reason.replace(/-/g, " ")
42
32
  : "Unknown", ")"] }));
43
33
  }