nudge-ui 0.2.0 → 0.2.1

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 (201) 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 +872 -12
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
  6. package/dist/hosts/astro/clientTransport.js +60 -11
  7. package/dist/hosts/next/sidecar.d.ts +4 -0
  8. package/dist/hosts/next/sidecar.js +49 -6
  9. package/dist/hosts/next/wrapper.js +30 -2
  10. package/dist/hosts/projectBridge.d.ts +20 -0
  11. package/dist/hosts/projectBridge.js +97 -0
  12. package/dist/hosts/static/cli.d.ts +1 -1
  13. package/dist/hosts/static/cli.js +6 -1
  14. package/dist/hosts/static/manifest.d.ts +1 -1
  15. package/dist/hosts/static/manifest.js +1 -1
  16. package/dist/hosts/static/server.js +44 -3
  17. package/dist/hosts/vite/vite.d.ts +5 -1
  18. package/dist/hosts/vite/vite.js +78 -14
  19. package/dist/inspector/agent/McpConnectionDialog.d.ts +2 -2
  20. package/dist/inspector/agent/McpConnectionDialog.js +11 -35
  21. package/dist/inspector/agent/client.d.ts +10 -3
  22. package/dist/inspector/agent/client.js +80 -9
  23. package/dist/inspector/agent/connectionStatus.js +3 -3
  24. package/dist/inspector/agent/httpTransport.d.ts +2 -0
  25. package/dist/inspector/agent/httpTransport.js +37 -17
  26. package/dist/inspector/agent/protocol.d.ts +8 -3
  27. package/dist/inspector/agent/protocol.js +1 -1
  28. package/dist/inspector/agent/verification.d.ts +1 -1
  29. package/dist/inspector/agent/verification.js +69 -12
  30. package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
  31. package/dist/inspector/canvas/CanvasCard.js +181 -91
  32. package/dist/inspector/canvas/CanvasElementOverlay.js +246 -52
  33. package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
  34. package/dist/inspector/canvas/CanvasToolbar.js +24 -0
  35. package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
  36. package/dist/inspector/canvas/CanvasWorkspace.js +392 -73
  37. package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
  38. package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
  39. package/dist/inspector/canvas/canvasResize.d.ts +30 -0
  40. package/dist/inspector/canvas/canvasResize.js +52 -0
  41. package/dist/inspector/canvas/canvasStore.d.ts +30 -6
  42. package/dist/inspector/canvas/canvasStore.js +128 -61
  43. package/dist/inspector/canvas/frameProtocol.d.ts +69 -5
  44. package/dist/inspector/canvas/frameProtocol.js +56 -2
  45. package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -0
  46. package/dist/inspector/canvas/keyboardShortcuts.js +14 -0
  47. package/dist/inspector/canvas/projection.d.ts +7 -0
  48. package/dist/inspector/canvas/projection.js +118 -25
  49. package/dist/inspector/canvas/rendererBootstrap.js +29 -29
  50. package/dist/inspector/canvas/rendererElementSelector.js +341 -30
  51. package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
  52. package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
  53. package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
  54. package/dist/inspector/canvas/sessionStore.d.ts +1 -0
  55. package/dist/inspector/canvas/sessionStore.js +12 -4
  56. package/dist/inspector/canvas/staleChangeDetector.js +16 -14
  57. package/dist/inspector/changes/changesLog.js +31 -1
  58. package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
  59. package/dist/inspector/changes/managedStyleProjection.js +3 -3
  60. package/dist/inspector/changes/structuralTypes.d.ts +4 -0
  61. package/dist/inspector/clientManifest.d.ts +5 -0
  62. package/dist/inspector/clientManifest.js +11 -0
  63. package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
  64. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
  65. package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
  66. package/dist/inspector/index.d.ts +2 -0
  67. package/dist/inspector/index.js +74 -24
  68. package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
  69. package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
  70. package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
  71. package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
  72. package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
  73. package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
  74. package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
  75. package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
  76. package/dist/inspector/inline-text/textEditChange.js +165 -0
  77. package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
  78. package/dist/inspector/inspection/selectionProperty.js +1 -0
  79. package/dist/inspector/overlay/selectedGeometry.js +21 -2
  80. package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
  81. package/dist/inspector/overlay/spacingGestures.js +353 -0
  82. package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
  83. package/dist/inspector/overlay/structuralGestures.js +12 -10
  84. package/dist/inspector/projection/managedStylesheet.js +4 -1
  85. package/dist/inspector/projection/renderedInstance.d.ts +2 -0
  86. package/dist/inspector/projection/renderedInstance.js +5 -1
  87. package/dist/inspector/projection/structuralProjection.d.ts +2 -0
  88. package/dist/inspector/projection/structuralProjection.js +85 -1
  89. package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
  90. package/dist/inspector/projection/textProjection.d.ts +2 -0
  91. package/dist/inspector/projection/textProjection.js +10 -4
  92. package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
  93. package/dist/inspector/prompt/clipboardHandoff.js +3 -3
  94. package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
  95. package/dist/inspector/prompt/generatePrompt.js +19 -2
  96. package/dist/inspector/runtime/editorShell.d.ts +2 -0
  97. package/dist/inspector/runtime/editorShell.js +4 -0
  98. package/dist/inspector/runtime/runtimeConfig.d.ts +4 -0
  99. package/dist/inspector/runtime/runtimeConfig.js +4 -0
  100. package/dist/inspector/selection/selectionStore.js +2 -0
  101. package/dist/inspector/settings/SettingsDialog.js +10 -0
  102. package/dist/inspector/shell/AppShell.js +5 -1
  103. package/dist/inspector/shell/ChangesLog.js +8 -4
  104. package/dist/inspector/shell/CopyPromptButton.js +95 -19
  105. package/dist/inspector/shell/EmptyState.js +1 -1
  106. package/dist/inspector/shell/InspectorShell.d.ts +0 -1
  107. package/dist/inspector/shell/InspectorShell.js +37 -38
  108. package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
  109. package/dist/inspector/shell/PeekOriginalButton.js +57 -0
  110. package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
  111. package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
  112. package/dist/inspector/shell/originalPreview.d.ts +9 -0
  113. package/dist/inspector/shell/originalPreview.js +61 -0
  114. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  115. package/dist/inspector/shell/shortcuts.js +15 -0
  116. package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
  117. package/dist/inspector/sketch/SketchChanges.js +72 -0
  118. package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
  119. package/dist/inspector/sketch/SketchEditor.js +402 -0
  120. package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
  121. package/dist/inspector/sketch/SketchEntryButton.js +11 -0
  122. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
  123. package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
  124. package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
  125. package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
  126. package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
  127. package/dist/inspector/sketch/SketchOverlay.js +420 -0
  128. package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
  129. package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
  130. package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
  131. package/dist/inspector/sketch/SketchWorkspace.js +152 -0
  132. package/dist/inspector/sketch/capture.d.ts +55 -0
  133. package/dist/inspector/sketch/capture.js +350 -0
  134. package/dist/inspector/sketch/freehand.d.ts +13 -0
  135. package/dist/inspector/sketch/freehand.js +64 -0
  136. package/dist/inspector/sketch/geometry.d.ts +17 -0
  137. package/dist/inspector/sketch/geometry.js +98 -0
  138. package/dist/inspector/sketch/handoff.d.ts +21 -0
  139. package/dist/inspector/sketch/handoff.js +72 -0
  140. package/dist/inspector/sketch/interaction.d.ts +21 -0
  141. package/dist/inspector/sketch/interaction.js +98 -0
  142. package/dist/inspector/sketch/model.d.ts +104 -0
  143. package/dist/inspector/sketch/model.js +282 -0
  144. package/dist/inspector/sketch/persistence.d.ts +21 -0
  145. package/dist/inspector/sketch/persistence.js +205 -0
  146. package/dist/inspector/sketch/raster.d.ts +22 -0
  147. package/dist/inspector/sketch/raster.js +203 -0
  148. package/dist/inspector/sketch/sketchNote.d.ts +10 -0
  149. package/dist/inspector/sketch/sketchNote.js +30 -0
  150. package/dist/inspector/sketch/store.d.ts +47 -0
  151. package/dist/inspector/sketch/store.js +409 -0
  152. package/dist/inspector/styleEditors/BorderEditor.js +15 -19
  153. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
  154. package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
  155. package/dist/inspector/styleEditors/ColorPicker.js +11 -36
  156. package/dist/inspector/styleEditors/GapField.d.ts +3 -2
  157. package/dist/inspector/styleEditors/GapField.js +2 -2
  158. package/dist/inspector/styleEditors/GridSection.js +1 -1
  159. package/dist/inspector/styleEditors/LayoutSection.js +11 -8
  160. package/dist/inspector/styleEditors/SpacingBox.js +23 -30
  161. package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
  162. package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
  163. package/dist/inspector/styleEditors/layoutValue.js +3 -0
  164. package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
  165. package/dist/inspector/styleEditors/nudgeValue.js +91 -5
  166. package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
  167. package/dist/inspector/styleEditors/stylePresence.js +22 -0
  168. package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
  169. package/dist/inspector/styleEditors/textRelevance.js +35 -0
  170. package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
  171. package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
  172. package/dist/inspector/tokens/TokenField.js +111 -7
  173. package/dist/inspector/tokens/editActions.d.ts +2 -0
  174. package/dist/inspector/tokens/editActions.js +2 -2
  175. package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
  176. package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
  177. package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
  178. package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
  179. package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
  180. package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
  181. package/dist/inspector/tokens/resolution.d.ts +3 -26
  182. package/dist/inspector/tokens/resolution.js +44 -735
  183. package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
  184. package/dist/inspector/tokens/tokenAvailability.js +204 -0
  185. package/dist/inspector/ui/Button.d.ts +1 -1
  186. package/dist/inspector/ui/SideValuesField.js +2 -2
  187. package/dist/inspector/ui/StatusCallout.d.ts +4 -1
  188. package/dist/inspector/ui/StatusCallout.js +2 -2
  189. package/dist/inspector/ui/styles.js +13 -11
  190. package/dist/internal/agent-protocol.d.ts +65 -1
  191. package/dist/internal/agent-protocol.js +183 -3
  192. package/dist/nudge-ui.mjs +261 -57
  193. package/dist/transport/editor.d.ts +28 -0
  194. package/dist/transport/editor.js +132 -0
  195. package/dist/transport/index.d.ts +1 -0
  196. package/dist/transport/index.js +1 -0
  197. package/dist/transport/routes.d.ts +7 -0
  198. package/dist/transport/routes.js +7 -0
  199. package/package.json +3 -1
  200. package/dist/inspector/shell/panelLayout.d.ts +0 -2
  201. package/dist/inspector/shell/panelLayout.js +0 -29
@@ -1,16 +1,19 @@
1
- import { PROTOCOL_VERSION, getRendererIdentity, sendToParent, } from "./frameProtocol.js";
1
+ import { PROTOCOL_VERSION, getRendererIdentity, sendToParent, isKeyboardShortcutCode, } from "./frameProtocol.js";
2
2
  import { findClosestAnchor, isEligibleNavigation, hasDifferentRoute, shouldPreserveNativeLinkActivation, } from "./linkEligibility.js";
3
3
  import { readBorderWidths, readMargins } from "../overlay/overlayGeometry.js";
4
4
  import { installInteractionStyles } from "../overlay/interactionStyles.js";
5
5
  import { createFrameThrottle } from "../overlay/frameThrottle.js";
6
6
  import { createCidIndex } from "./rendererCidIndex.js";
7
7
  import { isNudgeUiDev } from "../runtime/devFlag.js";
8
- import { isEditableEvent } from "../shell/shortcuts.js";
8
+ import { isEditableEvent, isInspectorToggleShortcut, isSendPromptShortcut } from "../shell/shortcuts.js";
9
9
  import { resolveSelectionTarget, selectionTargetMode } from "../selection/selectionTarget.js";
10
10
  import { escapeCssString } from "../projection/cssEscapes.js";
11
11
  import { blockApplicationClick, isApplicationActivationClick } from "../overlay/clickPolicy.js";
12
+ import { EMPTY_TEXT_PROJECTION_ATTR } from "../projection/textProjection.js";
13
+ import { getSpacingAffordanceAtPoint, toSpacingDescriptor, } from "../overlay/spacingGestures.js";
12
14
  const REACT_FIBER_KEY = /^__reactFiber\$/;
13
15
  const REACT_INTERNAL_KEY = /^__reactInternalInstance\$/;
16
+ const HOVER_SCROLL_SETTLE_DELAY = 150;
14
17
  function findFiber(el) {
15
18
  const keys = Object.keys(el);
16
19
  for (const key of keys) {
@@ -84,15 +87,63 @@ export function installRendererElementSelector() {
84
87
  const removeInteractionStyles = installInteractionStyles();
85
88
  const listenerCleanup = [];
86
89
  let disposed = false;
90
+ let interactionsSuspended = false;
91
+ let scrolling = false;
92
+ let scrollSettleTimer = null;
93
+ let activeHoverElement = null;
94
+ let lastHoverPoint = null;
87
95
  function trackListener(target, type, listener, options) {
88
96
  const eventListener = (event) => listener(event);
89
97
  target.addEventListener(type, eventListener, options);
90
98
  listenerCleanup.push(() => target.removeEventListener(type, eventListener, options));
91
99
  }
92
100
  const cidIndex = createCidIndex(document);
93
- const hoverUpdate = createFrameThrottle((pending) => {
94
- if (disposed)
95
- return;
101
+ const initialCursor = document.documentElement.style.getPropertyValue("cursor");
102
+ const initialCursorPriority = document.documentElement.style.getPropertyPriority("cursor");
103
+ let appliedRootCursor = null;
104
+ let spacingCursor = null;
105
+ function clearSpacingCursor() {
106
+ const active = spacingCursor;
107
+ spacingCursor = null;
108
+ if (active
109
+ && active.element.style.getPropertyValue("cursor") === active.appliedValue
110
+ && active.element.style.getPropertyPriority("cursor") === "important") {
111
+ if (active.previousValue) {
112
+ active.element.style.setProperty("cursor", active.previousValue, active.previousPriority);
113
+ }
114
+ else {
115
+ active.element.style.removeProperty("cursor");
116
+ }
117
+ }
118
+ if (appliedRootCursor !== null
119
+ && document.documentElement.style.getPropertyValue("cursor") === appliedRootCursor) {
120
+ if (initialCursor) {
121
+ document.documentElement.style.setProperty("cursor", initialCursor, initialCursorPriority);
122
+ }
123
+ else {
124
+ document.documentElement.style.removeProperty("cursor");
125
+ }
126
+ }
127
+ appliedRootCursor = null;
128
+ }
129
+ function setSpacingCursor(element, cursor) {
130
+ if (spacingCursor?.element !== element) {
131
+ clearSpacingCursor();
132
+ spacingCursor = {
133
+ element,
134
+ previousValue: element.style.getPropertyValue("cursor"),
135
+ previousPriority: element.style.getPropertyPriority("cursor"),
136
+ appliedValue: cursor,
137
+ };
138
+ }
139
+ else {
140
+ spacingCursor.appliedValue = cursor;
141
+ }
142
+ element.style.setProperty("cursor", cursor, "important");
143
+ document.documentElement.style.setProperty("cursor", cursor);
144
+ appliedRootCursor = cursor;
145
+ }
146
+ function reportHover(pending) {
96
147
  const { element, clear } = pending;
97
148
  const identity = getRendererIdentity();
98
149
  if (!identity)
@@ -101,6 +152,17 @@ export function installRendererElementSelector() {
101
152
  const selector = buildSelector(element);
102
153
  const src = element.getAttribute("data-src") ?? "";
103
154
  const rect = clear ? null : element.getBoundingClientRect();
155
+ const rawSpacing = !clear && pending.point
156
+ ? getSpacingAffordanceAtPoint(document, pending.point.x, pending.point.y)
157
+ : null;
158
+ const spacing = rawSpacing && rawSpacing.element === element ? rawSpacing : null;
159
+ if (spacing) {
160
+ setSpacingCursor(spacing.element, spacing.cursor);
161
+ }
162
+ else {
163
+ clearSpacingCursor();
164
+ }
165
+ activeHoverElement = clear ? null : element;
104
166
  const msg = {
105
167
  type: "element-hover",
106
168
  protocolVersion: PROTOCOL_VERSION,
@@ -118,10 +180,49 @@ export function installRendererElementSelector() {
118
180
  : null,
119
181
  margins: rect ? readMargins(element) : null,
120
182
  borders: rect ? readBorderWidths(element) : null,
183
+ point: pending.point,
184
+ spacing: spacing ? toSpacingDescriptor(spacing) : null,
121
185
  ...identity,
122
186
  };
123
187
  sendToParent(msg);
188
+ }
189
+ const hoverUpdate = createFrameThrottle((pending) => {
190
+ if (disposed || scrolling)
191
+ return;
192
+ reportHover(pending);
124
193
  });
194
+ function clearActiveHover() {
195
+ hoverUpdate.cancel();
196
+ clearSpacingCursor();
197
+ const element = activeHoverElement;
198
+ activeHoverElement = null;
199
+ if (element)
200
+ reportHover({ element, clear: true, point: null });
201
+ }
202
+ function resumeHoverAfterScroll() {
203
+ scrollSettleTimer = null;
204
+ scrolling = false;
205
+ if (disposed || interactionsSuspended || !lastHoverPoint)
206
+ return;
207
+ const target = document.elementFromPoint?.(lastHoverPoint.x, lastHoverPoint.y);
208
+ const element = resolveSelectionTarget(target, lastHoverPoint.mode);
209
+ if (!element)
210
+ return;
211
+ hoverUpdate.schedule({
212
+ element,
213
+ clear: false,
214
+ point: { x: lastHoverPoint.x, y: lastHoverPoint.y },
215
+ });
216
+ }
217
+ function suppressHoverDuringScroll() {
218
+ if (disposed || interactionsSuspended)
219
+ return;
220
+ scrolling = true;
221
+ clearActiveHover();
222
+ if (scrollSettleTimer !== null)
223
+ window.clearTimeout(scrollSettleTimer);
224
+ scrollSettleTimer = window.setTimeout(resumeHoverAfterScroll, HOVER_SCROLL_SETTLE_DELAY);
225
+ }
125
226
  let measurePointerOverPage = false;
126
227
  let measureAltKey = false;
127
228
  function updateMeasureState(altKey, pointerOverPage) {
@@ -144,6 +245,8 @@ export function installRendererElementSelector() {
144
245
  sendToParent(msg);
145
246
  }
146
247
  trackListener(document, "mouseover", (event) => {
248
+ if (interactionsSuspended)
249
+ return;
147
250
  updateMeasureState(event.altKey, true);
148
251
  const target = event.target;
149
252
  if (!(target instanceof Element))
@@ -151,11 +254,66 @@ export function installRendererElementSelector() {
151
254
  const el = resolveSelectionTarget(target, selectionTargetMode(event));
152
255
  if (!el)
153
256
  return;
154
- hoverUpdate.schedule({ element: el, clear: false });
257
+ lastHoverPoint = { x: event.clientX, y: event.clientY, mode: selectionTargetMode(event) };
258
+ if (scrolling)
259
+ return;
260
+ hoverUpdate.schedule({ element: el, clear: false, point: { x: event.clientX, y: event.clientY } });
155
261
  }, true);
262
+ trackListener(window, "scroll", suppressHoverDuringScroll, true);
156
263
  let pendingDrag = null;
157
264
  let dragging = false;
265
+ let lastDragPoint = null;
158
266
  let lastSelected = null;
267
+ trackListener(window, "nudge-ui:open", () => {
268
+ const identity = getRendererIdentity();
269
+ if (!identity)
270
+ return;
271
+ const message = {
272
+ type: "inspector-open-request",
273
+ protocolVersion: PROTOCOL_VERSION,
274
+ ...identity,
275
+ };
276
+ sendToParent(message);
277
+ });
278
+ trackListener(window, "message", (event) => {
279
+ if (event.origin !== window.location.origin || event.source !== window.parent)
280
+ return;
281
+ const identity = getRendererIdentity();
282
+ const message = event.data;
283
+ if (!identity || !message
284
+ || message.protocolVersion !== PROTOCOL_VERSION
285
+ || message.projectId !== identity.projectId
286
+ || message.workspaceId !== identity.workspaceId
287
+ || message.cardId !== identity.cardId)
288
+ return;
289
+ if (message.type === "measure-modifier" && typeof message.altKey === "boolean") {
290
+ updateMeasureState(message.altKey, measurePointerOverPage);
291
+ return;
292
+ }
293
+ if (message.type !== "inspector-interaction-state" || typeof message.open !== "boolean")
294
+ return;
295
+ const interactionsEnabled = message.interactionsEnabled === undefined
296
+ ? message.open
297
+ : message.interactionsEnabled === true;
298
+ interactionsSuspended = !interactionsEnabled;
299
+ if (message.open) {
300
+ document.documentElement.setAttribute("data-nudge-ui-panel", "open");
301
+ }
302
+ else {
303
+ document.documentElement.removeAttribute("data-nudge-ui-panel");
304
+ }
305
+ if (interactionsSuspended) {
306
+ cancelActiveDrag();
307
+ hoverUpdate.cancel();
308
+ clearSpacingCursor();
309
+ if (scrollSettleTimer !== null)
310
+ window.clearTimeout(scrollSettleTimer);
311
+ scrollSettleTimer = null;
312
+ scrolling = false;
313
+ activeHoverElement = null;
314
+ updateMeasureState(false, false);
315
+ }
316
+ });
159
317
  const dragMoveUpdate = createFrameThrottle((point) => {
160
318
  if (disposed)
161
319
  return;
@@ -165,18 +323,86 @@ export function installRendererElementSelector() {
165
323
  const msg = { type: "element-drag-move", protocolVersion: PROTOCOL_VERSION, point, ...identity };
166
324
  sendToParent(msg);
167
325
  });
326
+ function sendDragEnd(point, cancelled) {
327
+ if (!dragging || !point)
328
+ return;
329
+ const identity = getRendererIdentity();
330
+ if (!identity)
331
+ return;
332
+ const msg = {
333
+ type: "element-drag-end",
334
+ protocolVersion: PROTOCOL_VERSION,
335
+ point,
336
+ ...(cancelled ? { cancelled: true } : {}),
337
+ ...identity,
338
+ };
339
+ sendToParent(msg);
340
+ }
341
+ function cancelActiveDrag() {
342
+ dragMoveUpdate.cancel();
343
+ sendDragEnd(lastDragPoint, true);
344
+ pendingDrag = null;
345
+ dragging = false;
346
+ }
168
347
  trackListener(document, "mousedown", (event) => {
348
+ if (interactionsSuspended)
349
+ return;
169
350
  if (event.button !== 0)
170
351
  return;
171
352
  const element = resolveSelectionTarget(event.target, selectionTargetMode(event));
172
353
  if (!element)
173
354
  return;
355
+ const spacingAffordance = getSpacingAffordanceAtPoint(document, event.clientX, event.clientY);
356
+ const spacing = spacingAffordance?.element === element
357
+ ? toSpacingDescriptor(spacingAffordance)
358
+ : null;
359
+ const identity = getRendererIdentity();
360
+ // A spacing drag owns the pointer gesture. Do not start an inline-text
361
+ // attempt for the same down event; padding and gaps are intentionally not
362
+ // editable text targets.
363
+ if (identity && !spacing) {
364
+ const emptyProjectionId = event.target instanceof Element
365
+ ? event.target.closest(`[${EMPTY_TEXT_PROJECTION_ATTR}]`)?.getAttribute(EMPTY_TEXT_PROJECTION_ATTR) ?? undefined
366
+ : undefined;
367
+ const message = {
368
+ type: "inline-text-intent",
369
+ protocolVersion: PROTOCOL_VERSION,
370
+ intent: emptyProjectionId && event.detail >= 2 ? "double-click" : "pointer-down",
371
+ cid: element.getAttribute("data-cid"),
372
+ src: element.getAttribute("data-src") ?? "",
373
+ elementId: cidIndex.elementId(element),
374
+ point: { x: event.clientX, y: event.clientY },
375
+ clickCount: event.detail,
376
+ emptyProjectionId,
377
+ ...identity,
378
+ };
379
+ sendToParent(message);
380
+ }
174
381
  lastSelected = element;
175
- pendingDrag = { element, point: { x: event.clientX, y: event.clientY } };
382
+ pendingDrag = {
383
+ element,
384
+ point: { x: event.clientX, y: event.clientY },
385
+ spacing,
386
+ };
387
+ lastDragPoint = { x: event.clientX, y: event.clientY };
176
388
  }, true);
177
389
  trackListener(document, "mousemove", (event) => {
178
- if (!pendingDrag)
390
+ if (!pendingDrag) {
391
+ if (interactionsSuspended)
392
+ return;
393
+ const target = event.target;
394
+ if (target instanceof Element) {
395
+ const mode = selectionTargetMode(event);
396
+ const element = resolveSelectionTarget(target, mode);
397
+ if (element) {
398
+ lastHoverPoint = { x: event.clientX, y: event.clientY, mode };
399
+ if (scrolling)
400
+ return;
401
+ hoverUpdate.schedule({ element, clear: false, point: { x: event.clientX, y: event.clientY } });
402
+ }
403
+ }
179
404
  return;
405
+ }
180
406
  if (!dragging && Math.hypot(event.clientX - pendingDrag.point.x, event.clientY - pendingDrag.point.y) < 6)
181
407
  return;
182
408
  const identity = getRendererIdentity();
@@ -184,12 +410,16 @@ export function installRendererElementSelector() {
184
410
  return;
185
411
  event.preventDefault();
186
412
  const point = { x: event.clientX, y: event.clientY };
413
+ lastDragPoint = point;
187
414
  if (!dragging) {
188
415
  dragging = true;
189
416
  const msg = {
190
417
  type: "element-drag-start", protocolVersion: PROTOCOL_VERSION,
191
418
  cid: pendingDrag.element.getAttribute("data-cid"), src: pendingDrag.element.getAttribute("data-src") ?? "",
192
- elementId: cidIndex.elementId(pendingDrag.element), point, ...identity,
419
+ elementId: cidIndex.elementId(pendingDrag.element), point,
420
+ startPoint: pendingDrag.point,
421
+ spacing: pendingDrag.spacing,
422
+ ...identity,
193
423
  };
194
424
  sendToParent(msg);
195
425
  }
@@ -200,25 +430,68 @@ export function installRendererElementSelector() {
200
430
  function finishDrag(event) {
201
431
  dragMoveUpdate.cancel();
202
432
  if (dragging) {
203
- const identity = getRendererIdentity();
204
- if (identity) {
205
- event.preventDefault();
206
- const msg = {
207
- type: "element-drag-end", protocolVersion: PROTOCOL_VERSION,
208
- point: { x: event.clientX, y: event.clientY }, ...identity,
209
- };
210
- sendToParent(msg);
211
- }
433
+ event.preventDefault();
434
+ const point = { x: event.clientX, y: event.clientY };
435
+ lastDragPoint = point;
436
+ sendDragEnd(point, false);
212
437
  }
213
438
  pendingDrag = null;
214
439
  dragging = false;
215
440
  }
216
441
  trackListener(document, "mouseup", finishDrag, true);
442
+ function forwardKeyboardShortcut(event, phase) {
443
+ const code = isKeyboardShortcutCode(event.code) ? event.code : null;
444
+ if (!code)
445
+ return false;
446
+ if (code === "KeyS") {
447
+ if (phase !== "keydown" || !isSendPromptShortcut(event))
448
+ return false;
449
+ }
450
+ else if (code !== "Backslash" && interactionsSuspended) {
451
+ return false;
452
+ }
453
+ if (phase === "keydown") {
454
+ if (code !== "KeyS" && (event.repeat || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || isEditableEvent(event)))
455
+ return false;
456
+ }
457
+ else if (code !== "Backslash") {
458
+ return false;
459
+ }
460
+ const identity = getRendererIdentity();
461
+ if (!identity)
462
+ return false;
463
+ const message = {
464
+ type: "keyboard-shortcut",
465
+ protocolVersion: PROTOCOL_VERSION,
466
+ phase,
467
+ code,
468
+ ...identity,
469
+ };
470
+ sendToParent(message);
471
+ return phase === "keydown";
472
+ }
217
473
  trackListener(document, "keydown", (event) => {
218
- if (event.key === "Alt")
219
- updateMeasureState(true, measurePointerOverPage);
220
474
  if (isEditableEvent(event))
221
475
  return;
476
+ if (forwardKeyboardShortcut(event, "keydown"))
477
+ event.preventDefault();
478
+ if (isInspectorToggleShortcut(event)) {
479
+ const identity = getRendererIdentity();
480
+ if (!identity)
481
+ return;
482
+ event.preventDefault();
483
+ const msg = {
484
+ type: "inspector-toggle-request",
485
+ protocolVersion: PROTOCOL_VERSION,
486
+ ...identity,
487
+ };
488
+ sendToParent(msg);
489
+ return;
490
+ }
491
+ if (interactionsSuspended)
492
+ return;
493
+ if (event.key === "Alt")
494
+ updateMeasureState(true, measurePointerOverPage);
222
495
  const scrollKey = event.code === "Space"
223
496
  || event.key === "ArrowUp"
224
497
  || event.key === "ArrowDown"
@@ -242,6 +515,8 @@ export function installRendererElementSelector() {
242
515
  }
243
516
  if (event.key === "Escape" || event.key === "Esc") {
244
517
  event.preventDefault();
518
+ cancelActiveDrag();
519
+ clearSpacingCursor();
245
520
  lastSelected = null;
246
521
  const identity = getRendererIdentity();
247
522
  if (!identity)
@@ -280,20 +555,27 @@ export function installRendererElementSelector() {
280
555
  sendToParent(msg);
281
556
  }, true);
282
557
  trackListener(document, "keyup", (event) => {
558
+ forwardKeyboardShortcut(event, "keyup");
283
559
  if (event.key === "Alt")
284
560
  updateMeasureState(false, measurePointerOverPage);
285
561
  }, true);
286
562
  trackListener(window, "blur", () => {
563
+ cancelActiveDrag();
287
564
  updateMeasureState(false, false);
288
565
  });
289
566
  trackListener(document, "mouseout", (event) => {
567
+ if (interactionsSuspended)
568
+ return;
290
569
  const target = event.target;
291
570
  if (!(target instanceof HTMLElement))
292
571
  return;
293
572
  const related = event.relatedTarget;
294
573
  if (!(related instanceof Node) || !document.contains(related)) {
295
574
  updateMeasureState(event.altKey, false);
575
+ lastHoverPoint = null;
296
576
  }
577
+ if (scrolling)
578
+ return;
297
579
  if (related instanceof Node && (target.contains(related) || target === related))
298
580
  return;
299
581
  if (!(target instanceof Element))
@@ -303,19 +585,42 @@ export function installRendererElementSelector() {
303
585
  return;
304
586
  if (resolveSelectionTarget(related, selectionTargetMode(event)))
305
587
  return;
306
- hoverUpdate.schedule({ element: el, clear: true });
588
+ clearSpacingCursor();
589
+ hoverUpdate.schedule({ element: el, clear: true, point: null });
590
+ }, true);
591
+ trackListener(document, "dblclick", (event) => {
592
+ if (interactionsSuspended)
593
+ return;
594
+ const element = resolveSelectionTarget(event.target, selectionTargetMode(event));
595
+ const identity = getRendererIdentity();
596
+ if (!element || !identity)
597
+ return;
598
+ blockApplicationClick(event);
599
+ const message = {
600
+ type: "inline-text-intent",
601
+ protocolVersion: PROTOCOL_VERSION,
602
+ intent: "double-click",
603
+ cid: element.getAttribute("data-cid"),
604
+ src: element.getAttribute("data-src") ?? "",
605
+ elementId: cidIndex.elementId(element),
606
+ point: { x: event.clientX, y: event.clientY },
607
+ emptyProjectionId: event.target instanceof Element
608
+ ? event.target.closest(`[${EMPTY_TEXT_PROJECTION_ATTR}]`)?.getAttribute(EMPTY_TEXT_PROJECTION_ATTR) ?? undefined
609
+ : undefined,
610
+ ...identity,
611
+ };
612
+ sendToParent(message);
307
613
  }, true);
308
614
  trackListener(document, "click", (event) => {
615
+ if (interactionsSuspended)
616
+ return;
309
617
  const target = event.target;
310
618
  if (!(target instanceof Element))
311
619
  return;
312
620
  // Defer to navigation-intent when the user clicked a navigable same-origin
313
- // anchor that points to a different route. In canvas mode, anchor clicks
314
- // should create (or focus) a sibling card rather than selecting the anchor
315
- // itself. The navigation-intent listener is registered AFTER this one, so
316
- // we must NOT stop propagation here. Left to its own default the click would
317
- // perform a full-frame navigation inside the iframe; the navigation-intent
318
- // handler calls preventDefault once it has gathered the anchor href.
621
+ // anchor that points to a different route. The later listener reports the
622
+ // intent to the controller while leaving native or framework navigation in
623
+ // charge of the live application document.
319
624
  const anchor = findClosestAnchor(target);
320
625
  if (anchor && isEligibleNavigation(anchor, event) && hasDifferentRoute(anchor)) {
321
626
  return;
@@ -361,18 +666,24 @@ export function installRendererElementSelector() {
361
666
  return () => {
362
667
  if (disposed)
363
668
  return;
669
+ cancelActiveDrag();
364
670
  disposed = true;
365
671
  installed = false;
366
672
  hoverUpdate.cancel();
367
- dragMoveUpdate.cancel();
673
+ if (scrollSettleTimer !== null)
674
+ window.clearTimeout(scrollSettleTimer);
675
+ scrollSettleTimer = null;
368
676
  for (const cleanup of listenerCleanup)
369
677
  cleanup();
370
678
  listenerCleanup.length = 0;
371
679
  removeInteractionStyles();
372
- pendingDrag = null;
373
- dragging = false;
374
680
  lastSelected = null;
681
+ activeHoverElement = null;
682
+ lastHoverPoint = null;
683
+ scrolling = false;
375
684
  measurePointerOverPage = false;
376
685
  measureAltKey = false;
686
+ document.documentElement.removeAttribute("data-nudge-ui-panel");
687
+ clearSpacingCursor();
377
688
  };
378
689
  }
@@ -0,0 +1,11 @@
1
+ import { type FrameIdentity, type FrameProtocolMessage } from "./frameProtocol.js";
2
+ export interface CanvasRendererMessage {
3
+ cardId: string;
4
+ iframe: HTMLIFrameElement;
5
+ identity: FrameIdentity;
6
+ message: FrameProtocolMessage;
7
+ }
8
+ type CanvasRendererMessageListener = (event: CanvasRendererMessage) => void;
9
+ /** Subscribes to same-origin messages from registered Canvas renderer frames. */
10
+ export declare function subscribeCanvasRendererMessages(listener: CanvasRendererMessageListener): () => void;
11
+ export {};
@@ -0,0 +1,43 @@
1
+ import { isRendererMessageFor, PROTOCOL_VERSION, } from "./frameProtocol.js";
2
+ import { findCanvasFrameBySource, PROJECT_ID, WORKSPACE_ID } from "./projection.js";
3
+ const listeners = new Set();
4
+ function routeRendererMessage(event) {
5
+ if (event.origin !== window.location.origin || !event.data || typeof event.data !== "object")
6
+ return;
7
+ const frame = findCanvasFrameBySource(event.source);
8
+ if (!frame)
9
+ return;
10
+ const identity = {
11
+ projectId: PROJECT_ID,
12
+ workspaceId: WORKSPACE_ID,
13
+ cardId: frame.cardId,
14
+ };
15
+ // SAFETY: The object guard above makes the optional discriminator fields safe to inspect.
16
+ const candidate = event.data;
17
+ if (candidate.type === "renderer-hello") {
18
+ if (candidate.protocolVersion !== PROTOCOL_VERSION)
19
+ return;
20
+ }
21
+ else if (!isRendererMessageFor(event.data, identity)) {
22
+ return;
23
+ }
24
+ const routed = {
25
+ ...frame,
26
+ identity,
27
+ // SAFETY: Subscribers narrow the discriminated protocol union before reading message-specific fields.
28
+ message: event.data,
29
+ };
30
+ for (const listener of listeners)
31
+ listener(routed);
32
+ }
33
+ /** Subscribes to same-origin messages from registered Canvas renderer frames. */
34
+ export function subscribeCanvasRendererMessages(listener) {
35
+ if (listeners.size === 0)
36
+ window.addEventListener("message", routeRendererMessage);
37
+ listeners.add(listener);
38
+ return () => {
39
+ listeners.delete(listener);
40
+ if (listeners.size === 0)
41
+ window.removeEventListener("message", routeRendererMessage);
42
+ };
43
+ }
@@ -1,6 +1,7 @@
1
1
  import { getSelectedElements, setSelectedElement, toggleSelectedElement } from "../selection/selectionStore.js";
2
2
  import { selectCard } from "./canvasStore.js";
3
3
  import { RENDERER_ELEMENT_ID_ATTR } from "./rendererCidIndex.js";
4
+ import { inspectComponentTargets } from "../componentSemantics/index.js";
4
5
  function isHtmlElementInDocument(node, doc) {
5
6
  const frameWindow = doc.defaultView;
6
7
  return frameWindow !== null && node instanceof frameWindow.HTMLElement;
@@ -26,7 +27,9 @@ function findClickedElement(doc, elementId, cid, src) {
26
27
  if (candidates.length !== 1)
27
28
  return null;
28
29
  const candidate = candidates[0] ?? null;
29
- if (!candidate || candidate.getAttribute("data-cid") !== cid || candidate.getAttribute("data-src") !== src)
30
+ if (!candidate
31
+ || candidate.getAttribute("data-cid") !== cid
32
+ || (candidate.getAttribute("data-src") ?? "") !== src)
30
33
  return null;
31
34
  return isHtmlElementInDocument(candidate, doc) ? candidate : null;
32
35
  }
@@ -38,6 +41,7 @@ export function handleElementClick(msg, iframe, cardId) {
38
41
  selectCard(cardId);
39
42
  return;
40
43
  }
44
+ const componentTargets = inspectComponentTargets(el);
41
45
  const selected = {
42
46
  cid: msg.cid,
43
47
  src: el.getAttribute("data-src") ?? msg.src,
@@ -46,7 +50,7 @@ export function handleElementClick(msg, iframe, cardId) {
46
50
  line: msg.line,
47
51
  column: 0,
48
52
  domElement: el,
49
- componentTargets: [],
53
+ componentTargets,
50
54
  };
51
55
  // setSelectedElement must run before any side effect that could reload the
52
56
  // host page, otherwise the selection would be discarded by the unload.
@@ -38,6 +38,7 @@ export interface DurableSession {
38
38
  y: number;
39
39
  zoom: number;
40
40
  };
41
+ focusedCardId?: string | null;
41
42
  changes: SerializableChange[];
42
43
  structuralChanges: StructuralChange[];
43
44
  clipboardHandoff: ClipboardHandoffSnapshot | null;
@@ -1,11 +1,10 @@
1
1
  import { getWorkspaceChanges } from "../changes/workspaceChanges.js";
2
2
  import { loadWorkspaceChanges } from "../changes/changesLog.js";
3
3
  import { deserializeChange, isSerializableChange, serializeChange, } from "../changes/codecs.js";
4
- import { getCanvasMode, getCanvasCards, getCanvasComparisonGroups, getBoardCamera, setBoardCamera, hydrateCanvasStore, removeCanvasCard, } from "./canvasStore.js";
4
+ import { getCanvasMode, getCanvasCards, getCanvasComparisonGroups, getFocusedCardId, getBoardCamera, setBoardCamera, hydrateCanvasStore, removeCanvasCard, } from "./canvasStore.js";
5
5
  import { applyRules } from "../projection/managedStylesheet.js";
6
6
  import { clearWorkspace as clearWorkspaceLog } from "../changes/changesLog.js";
7
7
  import { removeManagedSheet } from "../projection/managedStylesheet.js";
8
- import { clearInspectorLayout } from "../shell/panelLayout.js";
9
8
  import { setSelectedElement } from "../selection/selectionStore.js";
10
9
  import { canWriteWorkspace } from "./workspaceLease.js";
11
10
  import { isStructuralChange, resetStructuralDeleteProjection, } from "../projection/structuralProjection.js";
@@ -121,6 +120,7 @@ function buildSession() {
121
120
  })),
122
121
  })),
123
122
  camera: { x: camera.x, y: camera.y, zoom: camera.zoom },
123
+ focusedCardId: getFocusedCardId(),
124
124
  changes: serializableChanges,
125
125
  structuralChanges: workspace.structuralChanges.map((change) => ({ ...change })),
126
126
  clipboardHandoff: getClipboardHandoffSnapshot(),
@@ -330,7 +330,16 @@ export function hydrateSession() {
330
330
  y: cameraRaw.y,
331
331
  zoom: cameraRaw.zoom,
332
332
  };
333
- hydrateCanvasStore(s.mode, serializableCards, camera, serializableComparisonGroups);
333
+ const focusedCardId = s.focusedCardId === undefined || s.focusedCardId === null
334
+ ? null
335
+ : typeof s.focusedCardId === "string" && cardsById.has(s.focusedCardId)
336
+ ? s.focusedCardId
337
+ : undefined;
338
+ if (focusedCardId === undefined) {
339
+ safeDiscard();
340
+ return { restored: false, changeCount: 0 };
341
+ }
342
+ hydrateCanvasStore(s.mode, serializableCards, camera, serializableComparisonGroups, focusedCardId);
334
343
  // Structural intent and instance evidence become visible atomically. The
335
344
  // projection layer preserves structural-first document application order.
336
345
  loadWorkspaceChanges(deserializedChanges, structuralChanges);
@@ -366,7 +375,6 @@ export function clearSession() {
366
375
  resetStructuralDeleteProjection();
367
376
  removeManagedSheet();
368
377
  setSelectedElement(null);
369
- clearInspectorLayout();
370
378
  for (const card of getCanvasCards()) {
371
379
  removeCanvasCard(card.id);
372
380
  }