nudge-ui 0.2.0 → 0.2.2

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 (231) hide show
  1. package/bin/nudge-ui.mjs +3 -1
  2. package/dist/cli/agentCommand.d.ts +8 -0
  3. package/dist/cli/agentCommand.js +59 -0
  4. package/dist/client.mjs +878 -11
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
  6. package/dist/hosts/astro/clientTransport.js +63 -11
  7. package/dist/hosts/astro/integration.d.ts +2 -2
  8. package/dist/hosts/astro/integration.js +4 -3
  9. package/dist/hosts/environment.d.ts +15 -0
  10. package/dist/hosts/environment.js +16 -0
  11. package/dist/hosts/next/sidecar.d.ts +4 -0
  12. package/dist/hosts/next/sidecar.js +56 -7
  13. package/dist/hosts/next/wrapper.d.ts +2 -0
  14. package/dist/hosts/next/wrapper.js +32 -3
  15. package/dist/hosts/projectBridge.d.ts +21 -0
  16. package/dist/hosts/projectBridge.js +98 -0
  17. package/dist/hosts/static/cli.d.ts +1 -1
  18. package/dist/hosts/static/cli.js +6 -1
  19. package/dist/hosts/static/manifest.d.ts +1 -1
  20. package/dist/hosts/static/manifest.js +3 -1
  21. package/dist/hosts/static/server.js +45 -3
  22. package/dist/hosts/vite/vite.d.ts +6 -1
  23. package/dist/hosts/vite/vite.js +82 -15
  24. package/dist/inspector/agent/McpConnectionDialog.d.ts +5 -4
  25. package/dist/inspector/agent/McpConnectionDialog.js +20 -37
  26. package/dist/inspector/agent/client.d.ts +16 -3
  27. package/dist/inspector/agent/client.js +114 -11
  28. package/dist/inspector/agent/connectionStatus.d.ts +3 -3
  29. package/dist/inspector/agent/connectionStatus.js +11 -4
  30. package/dist/inspector/agent/httpTransport.d.ts +8 -0
  31. package/dist/inspector/agent/httpTransport.js +78 -18
  32. package/dist/inspector/agent/protocol.d.ts +11 -3
  33. package/dist/inspector/agent/protocol.js +1 -1
  34. package/dist/inspector/agent/verification.d.ts +1 -1
  35. package/dist/inspector/agent/verification.js +69 -12
  36. package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
  37. package/dist/inspector/canvas/CanvasCard.js +181 -91
  38. package/dist/inspector/canvas/CanvasElementOverlay.js +288 -52
  39. package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
  40. package/dist/inspector/canvas/CanvasToolbar.js +24 -0
  41. package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
  42. package/dist/inspector/canvas/CanvasWorkspace.js +410 -72
  43. package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
  44. package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
  45. package/dist/inspector/canvas/canvasResize.d.ts +30 -0
  46. package/dist/inspector/canvas/canvasResize.js +52 -0
  47. package/dist/inspector/canvas/canvasStore.d.ts +30 -6
  48. package/dist/inspector/canvas/canvasStore.js +128 -61
  49. package/dist/inspector/canvas/frameProtocol.d.ts +82 -5
  50. package/dist/inspector/canvas/frameProtocol.js +63 -2
  51. package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -0
  52. package/dist/inspector/canvas/keyboardShortcuts.js +16 -0
  53. package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
  54. package/dist/inspector/canvas/linkEligibility.js +0 -29
  55. package/dist/inspector/canvas/projection.d.ts +7 -0
  56. package/dist/inspector/canvas/projection.js +118 -25
  57. package/dist/inspector/canvas/rendererBootstrap.js +42 -29
  58. package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
  59. package/dist/inspector/canvas/rendererElementSelector.js +385 -58
  60. package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
  61. package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
  62. package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
  63. package/dist/inspector/canvas/sessionStore.d.ts +1 -0
  64. package/dist/inspector/canvas/sessionStore.js +12 -4
  65. package/dist/inspector/canvas/staleChangeDetector.js +16 -14
  66. package/dist/inspector/changes/changesLog.js +31 -1
  67. package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
  68. package/dist/inspector/changes/managedStyleProjection.js +3 -3
  69. package/dist/inspector/changes/structuralTypes.d.ts +4 -0
  70. package/dist/inspector/changes/workspaceChanges.d.ts +4 -0
  71. package/dist/inspector/changes/workspaceChanges.js +29 -3
  72. package/dist/inspector/clientManifest.d.ts +7 -0
  73. package/dist/inspector/clientManifest.js +12 -0
  74. package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
  75. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
  76. package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
  77. package/dist/inspector/index.d.ts +2 -0
  78. package/dist/inspector/index.js +80 -32
  79. package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
  80. package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
  81. package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
  82. package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
  83. package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
  84. package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
  85. package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
  86. package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
  87. package/dist/inspector/inline-text/textEditChange.js +165 -0
  88. package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
  89. package/dist/inspector/inspection/selectionProperty.js +1 -0
  90. package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
  91. package/dist/inspector/overlay/DomNavigator.js +40 -0
  92. package/dist/inspector/overlay/InspectorOverlay.js +6 -2
  93. package/dist/inspector/overlay/clickPolicy.d.ts +18 -2
  94. package/dist/inspector/overlay/clickPolicy.js +34 -3
  95. package/dist/inspector/overlay/elementSelector.d.ts +1 -1
  96. package/dist/inspector/overlay/elementSelector.js +18 -7
  97. package/dist/inspector/overlay/selectedGeometry.js +21 -2
  98. package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
  99. package/dist/inspector/overlay/spacingGestures.js +351 -0
  100. package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
  101. package/dist/inspector/overlay/structuralGestures.js +12 -10
  102. package/dist/inspector/projection/managedStylesheet.js +4 -1
  103. package/dist/inspector/projection/renderedInstance.d.ts +2 -0
  104. package/dist/inspector/projection/renderedInstance.js +5 -1
  105. package/dist/inspector/projection/structuralProjection.d.ts +2 -0
  106. package/dist/inspector/projection/structuralProjection.js +85 -1
  107. package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
  108. package/dist/inspector/projection/textProjection.d.ts +2 -0
  109. package/dist/inspector/projection/textProjection.js +10 -4
  110. package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
  111. package/dist/inspector/prompt/clipboardHandoff.js +3 -3
  112. package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
  113. package/dist/inspector/prompt/generatePrompt.js +19 -2
  114. package/dist/inspector/runtime/editorShell.d.ts +2 -0
  115. package/dist/inspector/runtime/editorShell.js +4 -0
  116. package/dist/inspector/runtime/runtimeConfig.d.ts +5 -1
  117. package/dist/inspector/runtime/runtimeConfig.js +4 -0
  118. package/dist/inspector/selection/hierarchy.d.ts +6 -0
  119. package/dist/inspector/selection/hierarchy.js +8 -0
  120. package/dist/inspector/selection/selectionStore.js +2 -0
  121. package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
  122. package/dist/inspector/settings/SettingsDialog.js +12 -2
  123. package/dist/inspector/shell/AppShell.js +5 -1
  124. package/dist/inspector/shell/ChangesLog.js +26 -22
  125. package/dist/inspector/shell/CopyPromptButton.js +102 -19
  126. package/dist/inspector/shell/EmptyState.js +1 -1
  127. package/dist/inspector/shell/InspectorShell.d.ts +0 -1
  128. package/dist/inspector/shell/InspectorShell.js +38 -42
  129. package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
  130. package/dist/inspector/shell/PeekOriginalButton.js +57 -0
  131. package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
  132. package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
  133. package/dist/inspector/shell/originalPreview.d.ts +9 -0
  134. package/dist/inspector/shell/originalPreview.js +61 -0
  135. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  136. package/dist/inspector/shell/shortcuts.js +15 -0
  137. package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
  138. package/dist/inspector/sketch/SketchChanges.js +72 -0
  139. package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
  140. package/dist/inspector/sketch/SketchEditor.js +402 -0
  141. package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
  142. package/dist/inspector/sketch/SketchEntryButton.js +11 -0
  143. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
  144. package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
  145. package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
  146. package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
  147. package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
  148. package/dist/inspector/sketch/SketchOverlay.js +420 -0
  149. package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
  150. package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
  151. package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
  152. package/dist/inspector/sketch/SketchWorkspace.js +152 -0
  153. package/dist/inspector/sketch/capture.d.ts +55 -0
  154. package/dist/inspector/sketch/capture.js +350 -0
  155. package/dist/inspector/sketch/freehand.d.ts +13 -0
  156. package/dist/inspector/sketch/freehand.js +64 -0
  157. package/dist/inspector/sketch/geometry.d.ts +17 -0
  158. package/dist/inspector/sketch/geometry.js +98 -0
  159. package/dist/inspector/sketch/handoff.d.ts +21 -0
  160. package/dist/inspector/sketch/handoff.js +72 -0
  161. package/dist/inspector/sketch/interaction.d.ts +21 -0
  162. package/dist/inspector/sketch/interaction.js +98 -0
  163. package/dist/inspector/sketch/model.d.ts +104 -0
  164. package/dist/inspector/sketch/model.js +282 -0
  165. package/dist/inspector/sketch/persistence.d.ts +21 -0
  166. package/dist/inspector/sketch/persistence.js +205 -0
  167. package/dist/inspector/sketch/raster.d.ts +22 -0
  168. package/dist/inspector/sketch/raster.js +203 -0
  169. package/dist/inspector/sketch/sketchNote.d.ts +10 -0
  170. package/dist/inspector/sketch/sketchNote.js +30 -0
  171. package/dist/inspector/sketch/store.d.ts +47 -0
  172. package/dist/inspector/sketch/store.js +434 -0
  173. package/dist/inspector/styleEditors/BorderEditor.js +15 -19
  174. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
  175. package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
  176. package/dist/inspector/styleEditors/ColorPicker.js +11 -36
  177. package/dist/inspector/styleEditors/GapField.d.ts +3 -2
  178. package/dist/inspector/styleEditors/GapField.js +2 -2
  179. package/dist/inspector/styleEditors/GridSection.js +1 -1
  180. package/dist/inspector/styleEditors/LayoutSection.js +11 -8
  181. package/dist/inspector/styleEditors/SpacingBox.js +31 -33
  182. package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
  183. package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
  184. package/dist/inspector/styleEditors/layoutValue.js +3 -0
  185. package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
  186. package/dist/inspector/styleEditors/nudgeValue.js +93 -5
  187. package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
  188. package/dist/inspector/styleEditors/stylePresence.js +22 -0
  189. package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
  190. package/dist/inspector/styleEditors/textRelevance.js +35 -0
  191. package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
  192. package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
  193. package/dist/inspector/tokens/TokenField.js +130 -7
  194. package/dist/inspector/tokens/TokensPanel.js +2 -1
  195. package/dist/inspector/tokens/dragPointerLock.d.ts +8 -0
  196. package/dist/inspector/tokens/dragPointerLock.js +70 -0
  197. package/dist/inspector/tokens/editActions.d.ts +2 -0
  198. package/dist/inspector/tokens/editActions.js +2 -2
  199. package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
  200. package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
  201. package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
  202. package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
  203. package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
  204. package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
  205. package/dist/inspector/tokens/resolution.d.ts +3 -26
  206. package/dist/inspector/tokens/resolution.js +44 -735
  207. package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
  208. package/dist/inspector/tokens/tokenAvailability.js +204 -0
  209. package/dist/inspector/ui/Button.d.ts +1 -1
  210. package/dist/inspector/ui/Disclosure.d.ts +22 -0
  211. package/dist/inspector/ui/Disclosure.js +13 -0
  212. package/dist/inspector/ui/SideValuesField.js +2 -2
  213. package/dist/inspector/ui/StatusCallout.d.ts +4 -1
  214. package/dist/inspector/ui/StatusCallout.js +2 -2
  215. package/dist/inspector/ui/dragCursor.d.ts +8 -0
  216. package/dist/inspector/ui/dragCursor.js +21 -0
  217. package/dist/inspector/ui/styles.js +21 -17
  218. package/dist/internal/agent-protocol.d.ts +65 -1
  219. package/dist/internal/agent-protocol.js +183 -3
  220. package/dist/nudge-ui.mjs +276 -58
  221. package/dist/transport/editor.d.ts +47 -0
  222. package/dist/transport/editor.js +163 -0
  223. package/dist/transport/index.d.ts +1 -0
  224. package/dist/transport/index.js +1 -0
  225. package/dist/transport/routes.d.ts +10 -0
  226. package/dist/transport/routes.js +10 -0
  227. package/package.json +3 -1
  228. package/dist/inspector/shell/DomNavigation.d.ts +0 -8
  229. package/dist/inspector/shell/DomNavigation.js +0 -30
  230. package/dist/inspector/shell/panelLayout.d.ts +0 -2
  231. package/dist/inspector/shell/panelLayout.js +0 -29
@@ -4,9 +4,13 @@ import { getWorkspaceChanges } from "../changes/workspaceChanges.js";
4
4
  import { getChangesList, isComponentChange, isTextContentChange, isTokenChange, reconcileVerifiedWorkspaceChanges, } from "../changes/changesLog.js";
5
5
  import { verifyPreview } from "../projection/managedStylesheet.js";
6
6
  import { resolveRenderedInstance } from "../projection/renderedInstance.js";
7
- import { getStructuralChanges, } from "../projection/structuralProjection.js";
7
+ import { documentStateKey, getStructuralChanges, } from "../projection/structuralProjection.js";
8
8
  import { resolveTextProjectionTarget, resolveTextProjectionTextNode, } from "../projection/textProjection.js";
9
9
  import { applyHostWorkspaceProjection, compileWorkspaceProjection, } from "../projection/workspaceProjection.js";
10
+ import { isCanvasProjectionRevisionCurrent, projectWorkspaceSnapshotToDocument, } from "../canvas/projection.js";
11
+ import { getActiveCanvasDocument } from "../canvas/activeCanvasDocument.js";
12
+ import { getSelectedElement } from "../selection/selectionStore.js";
13
+ import { isEditorShellDocument } from "../runtime/editorShell.js";
10
14
  const dispatches = new Map();
11
15
  function stableJsonValue(value) {
12
16
  if (Array.isArray(value))
@@ -72,7 +76,20 @@ function verifyText(change, doc) {
72
76
  * verifies.
73
77
  */
74
78
  function verifyStructuralDelete(change, doc) {
75
- return resolveRenderedInstance(doc, change.target).status === "missing";
79
+ if (!change.route || !change.state || !doc.location)
80
+ return false;
81
+ let currentRoute;
82
+ try {
83
+ const url = new URL(doc.location.href);
84
+ url.hash = "";
85
+ currentRoute = url.href;
86
+ }
87
+ catch {
88
+ return false;
89
+ }
90
+ return currentRoute === change.route
91
+ && documentStateKey(doc) === change.state
92
+ && resolveRenderedInstance(doc, change.target).status === "missing";
76
93
  }
77
94
  /**
78
95
  * A move verifies when the element sits at its recorded destination without
@@ -142,11 +159,26 @@ export function getAgentDispatchSize(revision) {
142
159
  return 0;
143
160
  return snapshot.changes.length + snapshot.structuralChanges.length;
144
161
  }
145
- function afterBrowserPaint() {
146
- if (typeof requestAnimationFrame !== "function")
162
+ function afterBrowserPaint(doc) {
163
+ const ownerWindow = doc.defaultView;
164
+ const requestFrame = ownerWindow?.requestAnimationFrame.bind(ownerWindow);
165
+ if (!ownerWindow || !requestFrame)
147
166
  return Promise.resolve();
148
167
  return new Promise((resolve) => {
149
- requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
168
+ let settled = false;
169
+ const finish = () => {
170
+ if (settled)
171
+ return;
172
+ settled = true;
173
+ clearTimeout(timeout);
174
+ resolve();
175
+ };
176
+ const timeout = setTimeout(finish, 250);
177
+ requestFrame(() => {
178
+ if (doc.defaultView !== ownerWindow)
179
+ return finish();
180
+ requestFrame(finish);
181
+ });
150
182
  });
151
183
  }
152
184
  /**
@@ -154,7 +186,18 @@ function afterBrowserPaint() {
154
186
  * observable after removing Nudge's own preview. Newer edits and ambiguous
155
187
  * records are preserved.
156
188
  */
157
- export async function verifyAndReconcileHandoff(snapshot) {
189
+ export async function verifyAndReconcileHandoff(snapshot, doc = activeVerificationDocument()) {
190
+ if (!doc)
191
+ return 0;
192
+ const applyProjection = async (snapshot) => {
193
+ if (doc === document && !isEditorShellDocument(doc)) {
194
+ applyHostWorkspaceProjection(compileWorkspaceProjection(snapshot));
195
+ return "host";
196
+ }
197
+ if (doc === document)
198
+ return null;
199
+ return projectWorkspaceSnapshotToDocument(doc, snapshot);
200
+ };
158
201
  const sentByKey = new Map(snapshot.changes.map((change) => [changeKey(change), handoffChangeFingerprint(change)]));
159
202
  const current = getChangesList();
160
203
  const eligible = current.filter((change) => sentByKey.get(changeKey(change)) === handoffChangeFingerprint(change));
@@ -169,14 +212,19 @@ export async function verifyAndReconcileHandoff(snapshot) {
169
212
  const remainingStructural = currentStructural.filter((change) => !eligibleStructuralIds.has(change.id));
170
213
  try {
171
214
  const currentWorkspace = getWorkspaceChanges();
172
- applyHostWorkspaceProjection(compileWorkspaceProjection({
215
+ const previewRevision = await applyProjection({
173
216
  revision: currentWorkspace.revision,
174
217
  changes: remaining,
175
218
  structuralChanges: remainingStructural,
176
- }));
177
- await afterBrowserPaint();
178
- const verified = verifiedChangeKeys(eligible);
179
- const verifiedStructural = verifiedStructuralChangeIds(eligibleStructural, document);
219
+ });
220
+ if (previewRevision === null)
221
+ return 0;
222
+ await afterBrowserPaint(doc);
223
+ if (typeof previewRevision === "number"
224
+ && !isCanvasProjectionRevisionCurrent(doc, previewRevision))
225
+ return 0;
226
+ const verified = verifiedChangeKeys(eligible, doc);
227
+ const verifiedStructural = verifiedStructuralChangeIds(eligibleStructural, doc);
180
228
  // A user can make another inspector edit while the two-frame verification
181
229
  // window is open. Recheck byte identity immediately before reconciliation
182
230
  // so a newer value at the same key can never be removed.
@@ -184,15 +232,24 @@ export async function verifyAndReconcileHandoff(snapshot) {
184
232
  const stillVerified = new Set([...verified].filter((key) => currentByKey.get(key) === sentByKey.get(key)));
185
233
  const currentStructuralById = new Map(getStructuralChanges().map((change) => [change.id, handoffStructuralFingerprint(change)]));
186
234
  const stillVerifiedStructural = new Set([...verifiedStructural].filter((id) => currentStructuralById.get(id) === sentStructural.get(id)));
235
+ if (typeof previewRevision === "number"
236
+ && !isCanvasProjectionRevisionCurrent(doc, previewRevision))
237
+ return 0;
187
238
  return reconcileVerifiedWorkspaceChanges(stillVerified, stillVerifiedStructural);
188
239
  }
189
240
  finally {
190
241
  // Reconciliation reapplies the canonical set. If it was unable to write
191
242
  // (for example, a read-only Canvas lease), restore the untouched set here.
192
243
  const canonicalWorkspace = getWorkspaceChanges();
193
- applyHostWorkspaceProjection(compileWorkspaceProjection(canonicalWorkspace));
244
+ await applyProjection(canonicalWorkspace);
194
245
  }
195
246
  }
247
+ function activeVerificationDocument() {
248
+ const selectedDocument = getSelectedElement()?.domElement.ownerDocument;
249
+ if (selectedDocument && selectedDocument !== document)
250
+ return selectedDocument;
251
+ return getActiveCanvasDocument() ?? (isEditorShellDocument() ? null : document);
252
+ }
196
253
  /** Reconciles the captured records for one completed connected-agent request. */
197
254
  export async function verifyAndReconcileAgentDispatch(revision) {
198
255
  const snapshot = dispatches.get(revision);
@@ -1,10 +1,12 @@
1
1
  import { type ReactElement } from "react";
2
- import { type CanvasCard } from "./canvasStore.js";
2
+ import { type CanvasCard, type CanvasPresentation } from "./canvasStore.js";
3
3
  import type { InspectorSession } from "../session/sessionFactory.js";
4
4
  interface CanvasCardProps {
5
5
  card: CanvasCard;
6
- onEdit?: (card: CanvasCard) => void;
6
+ presentation?: CanvasPresentation;
7
+ presentationCard?: boolean;
8
+ onShowFocus?: (cardId: string) => void;
7
9
  documentOwner?: InspectorSession;
8
10
  }
9
- export declare function CanvasCard({ card, onEdit, documentOwner }: CanvasCardProps): ReactElement;
11
+ export declare function CanvasCard({ card, presentation, presentationCard, onShowFocus, documentOwner }: CanvasCardProps): ReactElement;
10
12
  export {};
@@ -1,11 +1,10 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useRef, useState } from "react";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
3
3
  import { CANVAS_RENDERER_ATTR } from "./roleDetection.js";
4
- import { removeCanvasCard, duplicateCard, updateCardTitle, updateCardUrl, resizeCard, setCardPosition, selectCard, getSelectedCardId, useSelectedCardId, useFocusedCardId, useBoardCamera } from "./canvasStore.js";
5
- import { IconRefresh, IconPlayerPlay, IconCopy, IconArrowsDiagonal } from "@tabler/icons-react";
6
- import { PROTOCOL_VERSION, isRendererMessageFor, } from "./frameProtocol.js";
4
+ import { removeCanvasCard, updateCardTitle, updateCardUrl, resizeCard, setCardPosition, selectCard, getSelectedCardId, useSelectedCardId, useFocusedCardId, useBoardCamera } from "./canvasStore.js";
5
+ import { IconRefresh, IconArrowsDiagonal, IconCornerLeftDown } from "@tabler/icons-react";
6
+ import { PROTOCOL_VERSION, } from "./frameProtocol.js";
7
7
  import { registerCardFrame, registerCardFrameSource, unregisterCardFrame, sendProjectionToCard, invalidateCanvasPreviewDocument, PROJECT_ID, WORKSPACE_ID } from "./projection.js";
8
- import { IconButton } from "../ui/IconButton.js";
9
8
  import { Button } from "../ui/Button.js";
10
9
  import { setSelectedElement } from "../selection/selectionStore.js";
11
10
  import { clearCanvasStructuralProjectionReports } from "../projection/structuralProjection.js";
@@ -15,11 +14,36 @@ import { getCanvasToolbarScale } from "./toolbarScale.js";
15
14
  import { getCanvasResizeHandleScale } from "./resizeHandleScale.js";
16
15
  import { disposeBrowserCssInspection } from "../inspection/browserCssInspectionRegistry.js";
17
16
  import { releaseDocumentProjection } from "../projection/structuralProjection.js";
18
- const MIN_CARD_WIDTH = 200;
19
- const MIN_CARD_HEIGHT = 150;
20
- export function CanvasCard({ card, onEdit, documentOwner }) {
17
+ import { startClipboardHandoffController } from "../prompt/clipboardHandoff.js";
18
+ import { disposeInlineTextEdit } from "../inline-text/inlineTextEditor.js";
19
+ import { configureNudgeUiRuntime, getNudgeUiRuntimeConfig } from "../runtime/runtimeConfig.js";
20
+ import { reconcileRuntimeWithDocumentStylesheets } from "../runtime/documentStylesheetOrder.js";
21
+ import { subscribeCanvasRendererMessages } from "./rendererMessageRouter.js";
22
+ import { SketchFrameOverlay } from "../sketch/SketchFrameOverlay.js";
23
+ import { getCanvasResizeEdges, resizeCanvasRect, } from "./canvasResize.js";
24
+ const RESIZE_HANDLES = [
25
+ { direction: "top-left", cursor: "nwse-resize", transformOrigin: "left top", label: "Resize card from the top-left corner" },
26
+ { direction: "top", cursor: "ns-resize", transformOrigin: "center top", label: "Resize card from the top edge" },
27
+ { direction: "top-right", cursor: "nesw-resize", transformOrigin: "right top", label: "Resize card from the top-right corner" },
28
+ { direction: "right", cursor: "ew-resize", transformOrigin: "right center", label: "Resize card from the right edge" },
29
+ { direction: "bottom-right", cursor: "nwse-resize", transformOrigin: "right bottom", label: "Resize card from the bottom-right corner" },
30
+ { direction: "bottom", cursor: "ns-resize", transformOrigin: "center bottom", label: "Resize card from the bottom edge" },
31
+ { direction: "bottom-left", cursor: "nesw-resize", transformOrigin: "left bottom", label: "Resize card from the bottom-left corner" },
32
+ { direction: "left", cursor: "ew-resize", transformOrigin: "left center", label: "Resize card from the left edge" },
33
+ ];
34
+ function resizeHandleTransform(direction, scale) {
35
+ if (direction === "top" || direction === "bottom")
36
+ return `scale(1, ${scale})`;
37
+ if (direction === "left" || direction === "right")
38
+ return `scale(${scale}, 1)`;
39
+ return `scale(${scale})`;
40
+ }
41
+ export function CanvasCard({ card, presentation = "canvas", presentationCard = true, onShowFocus, documentOwner }) {
21
42
  const iframeRef = useRef(null);
43
+ const initialUrlRef = useRef(card.navigationUrl ?? card.url);
44
+ const navigationUrlRef = useRef(card.navigationUrl);
22
45
  const documentSessionRef = useRef(null);
46
+ const frameRuntimeRef = useRef(null);
23
47
  const [loadState, setLoadState] = useState("loading");
24
48
  const [errorMessage, setErrorMessage] = useState(null);
25
49
  const camera = useBoardCamera();
@@ -27,12 +51,25 @@ export function CanvasCard({ card, onEdit, documentOwner }) {
27
51
  const focusedCardId = useFocusedCardId();
28
52
  const isSelected = selectedCardId === card.id;
29
53
  const isFocused = focusedCardId === card.id;
54
+ const isFocusPresentationCard = presentation === "focus" && presentationCard;
30
55
  const toolbarScale = getCanvasToolbarScale(camera.zoom);
31
56
  const resizeHandleScale = getCanvasResizeHandleScale(camera.zoom);
32
57
  const disposeDocumentSession = useCallback(() => {
33
58
  documentSessionRef.current?.session.dispose();
34
59
  documentSessionRef.current = null;
35
60
  }, []);
61
+ const loadFrame = useCallback((url) => {
62
+ const iframe = iframeRef.current;
63
+ if (!iframe)
64
+ return;
65
+ if (getSelectedCardId() === card.id)
66
+ setSelectedElement(null);
67
+ disposeDocumentSession();
68
+ invalidateCanvasPreviewDocument(card.id);
69
+ setLoadState("loading");
70
+ setErrorMessage(null);
71
+ iframe.setAttribute("src", url ?? iframe.src);
72
+ }, [card.id, disposeDocumentSession]);
36
73
  const bindDocumentSession = useCallback(() => {
37
74
  const frameDocument = iframeRef.current?.contentDocument;
38
75
  if (!documentOwner || !frameDocument)
@@ -43,6 +80,8 @@ export function CanvasCard({ card, onEdit, documentOwner }) {
43
80
  const session = documentOwner.createDocumentSession(frameDocument);
44
81
  session.registerCleanup(() => disposeBrowserCssInspection(frameDocument));
45
82
  session.registerCleanup(() => releaseDocumentProjection(frameDocument));
83
+ session.registerCleanup(() => disposeInlineTextEdit("frame-disposed", frameDocument));
84
+ session.registerCleanup(startClipboardHandoffController(frameDocument));
46
85
  documentSessionRef.current = { document: frameDocument, session };
47
86
  }, [disposeDocumentSession, documentOwner]);
48
87
  useEffect(() => {
@@ -57,27 +96,20 @@ export function CanvasCard({ card, onEdit, documentOwner }) {
57
96
  };
58
97
  }, [bindDocumentSession, card.id, disposeDocumentSession]);
59
98
  function handleReload() {
60
- if (iframeRef.current) {
61
- if (getSelectedCardId() === card.id)
62
- setSelectedElement(null);
63
- disposeDocumentSession();
64
- invalidateCanvasPreviewDocument(card.id);
65
- setLoadState("loading");
66
- setErrorMessage(null);
67
- const currentSrc = iframeRef.current.src;
68
- iframeRef.current.setAttribute("src", currentSrc);
69
- }
99
+ loadFrame();
70
100
  }
71
101
  function handleRemove() {
72
102
  if (getSelectedCardId() === card.id)
73
103
  setSelectedElement(null);
74
104
  removeCanvasCard(card.id);
75
105
  }
76
- function handleEdit() {
77
- onEdit?.(card);
78
- }
79
- function handleDuplicate() {
80
- duplicateCard(card.id);
106
+ function updateFrameTitle(title) {
107
+ if (!title)
108
+ return;
109
+ const runtime = getNudgeUiRuntimeConfig();
110
+ if (runtime.demo === true && (runtime.demoCardLabels?.length ?? 0) > 0)
111
+ return;
112
+ updateCardTitle(card.id, title);
81
113
  }
82
114
  /** The controller half of the handshake: announce workspace identity. */
83
115
  function sendParentReady() {
@@ -89,39 +121,44 @@ export function CanvasCard({ card, onEdit, documentOwner }) {
89
121
  cardId: card.id,
90
122
  }, window.location.origin);
91
123
  }
124
+ function adoptFrameRuntime(runtime) {
125
+ frameRuntimeRef.current = runtime;
126
+ if (getSelectedCardId() !== card.id)
127
+ return;
128
+ const frameDocument = iframeRef.current?.contentDocument;
129
+ if (!frameDocument)
130
+ return;
131
+ try {
132
+ configureNudgeUiRuntime(runtime);
133
+ const configured = getNudgeUiRuntimeConfig();
134
+ const reconciled = reconcileRuntimeWithDocumentStylesheets(configured, frameDocument);
135
+ if (reconciled !== configured)
136
+ configureNudgeUiRuntime(reconciled);
137
+ }
138
+ catch {
139
+ // Ignore malformed same-origin renderer metadata and keep the last valid runtime.
140
+ }
141
+ }
92
142
  useEffect(() => {
93
- function onMessage(event) {
94
- if (event.origin !== window.location.origin)
95
- return;
96
- if (event.source !== iframeRef.current?.contentWindow)
97
- return;
98
- const msg = event.data;
99
- if (!msg || typeof msg !== "object")
143
+ if (!isSelected || !frameRuntimeRef.current)
144
+ return;
145
+ adoptFrameRuntime(frameRuntimeRef.current);
146
+ }, [isSelected]);
147
+ useEffect(() => {
148
+ return subscribeCanvasRendererMessages(({ cardId, message: data }) => {
149
+ if (cardId !== card.id)
100
150
  return;
101
151
  // A renderer whose runtime finished booting after the iframe load event
102
152
  // asks for the identity announcement it may have missed.
103
- // SAFETY: Renderer messages arrive as unvalidated structured clones, so the discriminant must be read structurally.
104
- if (msg.type === "renderer-hello") {
105
- // SAFETY: The `type === "renderer-hello"` branch above selects exactly the hello payload shape.
106
- const hello = msg;
107
- if (hello.protocolVersion !== PROTOCOL_VERSION)
108
- return;
153
+ if (data.type === "renderer-hello") {
109
154
  sendParentReady();
110
155
  return;
111
156
  }
112
- if (!isRendererMessageFor(msg, {
113
- projectId: PROJECT_ID,
114
- workspaceId: WORKSPACE_ID,
115
- cardId: card.id,
116
- }))
117
- return;
118
- // SAFETY: isRendererMessageFor validated the frame identity and message shape above.
119
- const data = msg;
120
157
  if (data.type === "frame-ready") {
158
+ adoptFrameRuntime(data.runtime);
121
159
  setLoadState("ready");
122
160
  setErrorMessage(null);
123
- if (data.title)
124
- updateCardTitle(card.id, data.title);
161
+ updateFrameTitle(data.title);
125
162
  if (data.url)
126
163
  updateCardUrl(card.id, data.url);
127
164
  if (iframeRef.current) {
@@ -130,9 +167,12 @@ export function CanvasCard({ card, onEdit, documentOwner }) {
130
167
  }
131
168
  return;
132
169
  }
170
+ if (data.type === "frame-runtime") {
171
+ adoptFrameRuntime(data.runtime);
172
+ return;
173
+ }
133
174
  if (data.type === "frame-metadata") {
134
- if (data.title)
135
- updateCardTitle(card.id, data.title);
175
+ updateFrameTitle(data.title);
136
176
  if (data.url)
137
177
  updateCardUrl(card.id, data.url);
138
178
  return;
@@ -140,14 +180,15 @@ export function CanvasCard({ card, onEdit, documentOwner }) {
140
180
  if (data.type === "frame-error") {
141
181
  setLoadState("error");
142
182
  setErrorMessage(data.message || "Frame failed to load");
143
- return;
144
183
  }
145
- }
146
- window.addEventListener("message", onMessage);
147
- return () => {
148
- window.removeEventListener("message", onMessage);
149
- };
184
+ });
150
185
  }, [card.id]);
186
+ useLayoutEffect(() => {
187
+ if (!card.navigationUrl || navigationUrlRef.current === card.navigationUrl)
188
+ return;
189
+ navigationUrlRef.current = card.navigationUrl;
190
+ loadFrame(card.navigationUrl);
191
+ }, [card.navigationUrl, loadFrame]);
151
192
  useEffect(() => {
152
193
  if (loadState !== "loading")
153
194
  return;
@@ -182,6 +223,10 @@ export function CanvasCard({ card, onEdit, documentOwner }) {
182
223
  }, [bindDocumentSession, card.id, disposeDocumentSession]);
183
224
  const [isDragging, setIsDragging] = useState(false);
184
225
  const dragRef = useRef({ startX: 0, startY: 0, cardX: 0, cardY: 0 });
226
+ const resizeCleanupRef = useRef(null);
227
+ useEffect(() => {
228
+ return () => resizeCleanupRef.current?.();
229
+ }, []);
185
230
  const handleToolbarPointerDown = useCallback((e) => {
186
231
  // SAFETY: pointer events on the card target are HTMLElements in this DOM context.
187
232
  const target = e.target;
@@ -219,57 +264,102 @@ export function CanvasCard({ card, onEdit, documentOwner }) {
219
264
  setSelectedElement(null);
220
265
  selectCard(card.id);
221
266
  }, [card.id]);
222
- const handleResizeStart = useCallback((e) => {
267
+ const handleResizeStart = useCallback((e, direction) => {
223
268
  e.stopPropagation();
224
269
  e.preventDefault();
270
+ resizeCleanupRef.current?.();
271
+ const target = e.currentTarget;
272
+ const pointerId = e.pointerId;
225
273
  const startX = e.clientX;
226
274
  const startY = e.clientY;
227
- const startWidth = card.width;
228
- const startHeight = card.height;
229
- // SAFETY: pointerdown targets are HTMLElements in the card DOM.
230
- e.target.setPointerCapture(e.pointerId);
275
+ const start = {
276
+ x: card.x,
277
+ y: card.y,
278
+ width: card.width,
279
+ height: card.height,
280
+ };
231
281
  function onMove(ev) {
232
- const dx = (ev.clientX - startX) / camera.zoom;
233
- const dy = (ev.clientY - startY) / camera.zoom;
234
- const newWidth = Math.max(MIN_CARD_WIDTH, startWidth + dx);
235
- const newHeight = Math.max(MIN_CARD_HEIGHT, startHeight + dy);
236
- resizeCard(card.id, newWidth, newHeight);
282
+ if (ev.pointerId !== pointerId)
283
+ return;
284
+ const delta = {
285
+ x: (ev.clientX - startX) / camera.zoom,
286
+ y: (ev.clientY - startY) / camera.zoom,
287
+ };
288
+ const next = resizeCanvasRect(start, direction, delta);
289
+ resizeCard(card.id, next.width, next.height, { x: next.x, y: next.y });
237
290
  }
238
- function onUp() {
291
+ function cleanup() {
239
292
  window.removeEventListener("pointermove", onMove);
240
293
  window.removeEventListener("pointerup", onUp);
294
+ window.removeEventListener("pointercancel", onCancel);
295
+ target.removeEventListener("lostpointercapture", onLostPointerCapture);
296
+ if (target.hasPointerCapture?.(pointerId))
297
+ target.releasePointerCapture(pointerId);
298
+ if (resizeCleanupRef.current === cleanup)
299
+ resizeCleanupRef.current = null;
300
+ }
301
+ function onUp(ev) {
302
+ if (ev.pointerId !== pointerId)
303
+ return;
304
+ cleanup();
305
+ }
306
+ function onCancel(ev) {
307
+ if (ev.pointerId !== pointerId)
308
+ return;
309
+ cleanup();
310
+ }
311
+ function onLostPointerCapture(ev) {
312
+ if (ev.pointerId !== pointerId)
313
+ return;
314
+ cleanup();
241
315
  }
316
+ resizeCleanupRef.current = cleanup;
317
+ // SAFETY: the resize handle is an HTMLDivElement in this DOM context.
318
+ target.setPointerCapture?.(pointerId);
242
319
  window.addEventListener("pointermove", onMove);
243
320
  window.addEventListener("pointerup", onUp);
244
- }, [card.id, card.width, card.height, camera.zoom]);
245
- function handleResizeKeyDown(event) {
246
- const step = event.shiftKey ? 40 : 20;
247
- if (event.key === "ArrowRight")
248
- resizeCard(card.id, card.width + step, card.height);
249
- else if (event.key === "ArrowLeft")
250
- resizeCard(card.id, Math.max(MIN_CARD_WIDTH, card.width - step), card.height);
251
- else if (event.key === "ArrowDown")
252
- resizeCard(card.id, card.width, card.height + step);
253
- else if (event.key === "ArrowUp")
254
- resizeCard(card.id, card.width, Math.max(MIN_CARD_HEIGHT, card.height - step));
255
- else
321
+ window.addEventListener("pointercancel", onCancel);
322
+ target.addEventListener("lostpointercapture", onLostPointerCapture);
323
+ }, [card, camera.zoom]);
324
+ function handleResizeKeyDown(event, direction) {
325
+ const isArrowKey = event.key === "ArrowRight"
326
+ || event.key === "ArrowLeft"
327
+ || event.key === "ArrowDown"
328
+ || event.key === "ArrowUp";
329
+ if (!isArrowKey)
256
330
  return;
257
331
  event.preventDefault();
332
+ event.stopPropagation();
333
+ const step = event.shiftKey ? 40 : 20;
334
+ const delta = {
335
+ x: event.key === "ArrowRight" ? step : event.key === "ArrowLeft" ? -step : 0,
336
+ y: event.key === "ArrowDown" ? step : event.key === "ArrowUp" ? -step : 0,
337
+ };
338
+ const edges = getCanvasResizeEdges(direction);
339
+ if ((delta.x !== 0 && edges.horizontal === null) || (delta.y !== 0 && edges.vertical === null))
340
+ return;
341
+ const next = resizeCanvasRect(card, direction, delta);
342
+ resizeCard(card.id, next.width, next.height, { x: next.x, y: next.y });
258
343
  }
259
- return (_jsxs("div", { className: `canvas-card${isDragging ? " is-dragging" : ""}${isSelected ? " is-selected" : ""}${isFocused ? " is-focused" : ""}`, "data-card-id": card.id, style: {
344
+ return (_jsxs("div", { className: `canvas-card${isDragging ? " is-dragging" : ""}${isSelected ? " is-selected" : ""}${isFocused ? " is-focused" : ""}${isFocusPresentationCard ? " is-focus-presentation" : ""}${presentationCard ? " is-presentation-card" : ""}`, "data-card-id": card.id, style: {
260
345
  position: "absolute",
261
- left: card.x,
262
- top: card.y,
263
- width: card.width,
264
- height: card.height,
265
- }, onPointerDown: handleCardPointerDown, children: [_jsxs("div", { className: "canvas-card__toolbar", onPointerDown: handleToolbarPointerDown, children: [_jsx("div", { className: "canvas-card__drag-surface", "data-test": `canvas-card-drag-${card.id}`, children: _jsxs("span", { className: "canvas-card__dimensions", "data-test": `canvas-card-dimensions-${card.id}`, style: {
346
+ visibility: presentation === "focus" && !presentationCard ? "hidden" : undefined,
347
+ pointerEvents: presentation === "focus" && !presentationCard ? "none" : undefined,
348
+ left: isFocusPresentationCard ? 0 : card.x,
349
+ top: isFocusPresentationCard ? 0 : card.y,
350
+ width: isFocusPresentationCard ? "100%" : card.width,
351
+ height: isFocusPresentationCard ? "100%" : card.height,
352
+ }, onPointerDown: presentation === "canvas" ? handleCardPointerDown : undefined, children: [presentation === "canvas" ? _jsx("div", { className: "canvas-card__toolbar", onPointerDown: handleToolbarPointerDown, children: _jsxs("div", { className: "canvas-card__drag-surface", "data-test": `canvas-card-drag-${card.id}`, children: [onShowFocus ? (_jsxs(Button, { title: "Focus", variant: "primary", size: "default", "data-test": `canvas-card-focus-${card.id}`, style: {
266
353
  transform: `scale(${toolbarScale})`,
267
354
  transformOrigin: "left bottom",
268
- }, children: [Math.round(card.width), " \u00D7 ", Math.round(card.height), " px"] }) }), _jsxs("div", { className: "canvas-card__actions", style: {
269
- transform: `scale(${toolbarScale})`,
270
- transformOrigin: "right bottom",
271
- }, children: [_jsxs(Button, { variant: "secondary", size: "default", "data-test": `canvas-card-preview-${card.id}`, onClick: handleEdit, children: [_jsx(IconPlayerPlay, { size: 14, stroke: 1.8, "aria-hidden": "true" }), "Page view"] }), _jsx(IconButton, { label: "Duplicate card", variant: "secondary", size: "default", "data-test": `canvas-card-duplicate-${card.id}`, onClick: handleDuplicate, children: _jsx(IconCopy, { size: 14, stroke: 1.8, "aria-hidden": "true" }) }), _jsx(IconButton, { label: "Reload card", variant: "secondary", size: "default", "data-test": `canvas-card-reload-${card.id}`, onClick: handleReload, children: _jsx(IconRefresh, { size: 14, stroke: 1.8, "aria-hidden": "true" }) })] })] }), _jsxs("div", { className: "canvas-card__frame", children: [loadState === "loading" ? (_jsx("div", { className: "canvas-card__loading", "data-test": `canvas-card-loading-${card.id}`, children: "Loading preview..." })) : loadState === "error" ? (_jsxs("div", { className: "canvas-card__error", "data-test": `canvas-card-error-${card.id}`, children: [_jsx("p", { children: errorMessage || "Frame could not be loaded" }), _jsxs("div", { className: "canvas-card__error-actions", children: [_jsx(Button, { size: "compact", variant: "secondary", onClick: handleReload, children: "Retry" }), _jsx(Button, { size: "compact", variant: "danger", onClick: handleRemove, children: "Remove" })] })] })) : null, _jsx("iframe", { ref: iframeRef, className: "canvas-card__iframe", src: card.url, title: card.title || card.url, [CANVAS_RENDERER_ATTR]: "", sandbox: "allow-scripts allow-same-origin allow-forms allow-popups", "data-test": `canvas-card-iframe-${card.id}` })] }), _jsx("div", { className: "canvas-card__resize-handle", "data-test": `canvas-card-resize-${card.id}`, style: {
272
- transform: `scale(${resizeHandleScale})`,
273
- transformOrigin: "right bottom",
274
- }, onPointerDown: handleResizeStart, onKeyDown: handleResizeKeyDown, role: "button", "aria-label": "Resize card", tabIndex: 0, children: _jsx(IconArrowsDiagonal, { size: 12, "aria-hidden": "true" }) })] }));
355
+ }, onClick: () => onShowFocus(card.id), children: [_jsx(IconCornerLeftDown, { size: "var(--icon-size-small)", stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }), "Focus"] })) : null, _jsx("span", { className: "canvas-card__dimensions", "data-test": `canvas-card-dimensions-${card.id}`, style: {
356
+ transform: `translateX(-50%) scale(${toolbarScale})`,
357
+ transformOrigin: "center bottom",
358
+ }, children: card.title || `${Math.round(card.width)} × ${Math.round(card.height)} px` })] }) }) : null, _jsxs("div", { className: "canvas-card__frame", children: [loadState === "loading" ? (_jsx("div", { className: "canvas-card__loading", "data-test": `canvas-card-loading-${card.id}`, children: "Loading preview..." })) : loadState === "error" ? (_jsxs("div", { className: "canvas-card__error", "data-test": `canvas-card-error-${card.id}`, children: [_jsx("p", { children: errorMessage || "Frame could not be loaded" }), _jsxs("div", { className: "canvas-card__error-actions", children: [_jsx(Button, { size: "compact", variant: "secondary", onClick: handleReload, children: "Retry" }), _jsx(Button, { size: "compact", variant: "danger", onClick: handleRemove, children: "Remove" })] })] })) : null, _jsx("iframe", { ref: iframeRef, className: "canvas-card__iframe", src: initialUrlRef.current, title: card.title || card.url, [CANVAS_RENDERER_ATTR]: "", sandbox: "allow-scripts allow-same-origin allow-forms allow-popups", "data-test": `canvas-card-iframe-${card.id}` }), _jsx(SketchFrameOverlay, { iframe: iframeRef.current, cardUrl: card.url, ready: loadState === "ready" })] }), presentation === "canvas" ? RESIZE_HANDLES.map((handle) => (_jsx("div", { className: `canvas-card__resize-handle canvas-card__resize-handle--${handle.direction}`, "data-test": `canvas-card-resize-${card.id}${handle.direction === "bottom-right" ? "" : `-${handle.direction}`}`, "data-resize-direction": handle.direction, style: {
359
+ // Keep the edge's long axis aligned with the zoomed card. Only
360
+ // the thickness needs inverse scaling to remain screen-sized.
361
+ transform: resizeHandleTransform(handle.direction, resizeHandleScale),
362
+ transformOrigin: handle.transformOrigin,
363
+ cursor: handle.cursor,
364
+ }, onPointerDown: (event) => handleResizeStart(event, handle.direction), onKeyDown: (event) => handleResizeKeyDown(event, handle.direction), role: "button", "aria-label": handle.direction === "bottom-right" ? "Resize card" : handle.label, tabIndex: 0, children: handle.direction === "bottom-right" ? _jsx(IconArrowsDiagonal, { size: 12, "aria-hidden": "true" }) : null }, handle.direction))) : null] }));
275
365
  }