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
@@ -0,0 +1,351 @@
1
+ import { getElementComputedStyle } from "../runtime/domRealm.js";
2
+ const SPACING_DRAG_TARGET_SIZE = 16;
3
+ export const SPACING_GUIDE_HANDLE_LENGTH = 40;
4
+ const SPACING_SHIFT_STEP = 8;
5
+ const EPSILON = 0.01;
6
+ function rightOf(rect) {
7
+ return Number.isFinite(rect.right) ? rect.right : rect.left + rect.width;
8
+ }
9
+ function bottomOf(rect) {
10
+ return Number.isFinite(rect.bottom) ? rect.bottom : rect.top + rect.height;
11
+ }
12
+ function boxEdges(element) {
13
+ const rect = element.getBoundingClientRect();
14
+ return {
15
+ left: rect.left,
16
+ top: rect.top,
17
+ right: rightOf(rect),
18
+ bottom: bottomOf(rect),
19
+ };
20
+ }
21
+ // Computed style resolves to px in browsers, so drag math normalizes to px.
22
+ // An authored rem/% is intentionally rewritten as px on commit.
23
+ function numberValue(value) {
24
+ const parsed = Number.parseFloat(value);
25
+ return Number.isFinite(parsed) ? Math.max(0, parsed) : 0;
26
+ }
27
+ function boxModel(element) {
28
+ const style = getElementComputedStyle(element);
29
+ const padding = {
30
+ top: numberValue(style.paddingTop),
31
+ right: numberValue(style.paddingRight),
32
+ bottom: numberValue(style.paddingBottom),
33
+ left: numberValue(style.paddingLeft),
34
+ };
35
+ const border = {
36
+ top: numberValue(style.borderTopWidth),
37
+ right: numberValue(style.borderRightWidth),
38
+ bottom: numberValue(style.borderBottomWidth),
39
+ left: numberValue(style.borderLeftWidth),
40
+ };
41
+ const outer = boxEdges(element);
42
+ const inner = {
43
+ left: outer.left + border.left,
44
+ top: outer.top + border.top,
45
+ right: outer.right - border.right,
46
+ bottom: outer.bottom - border.bottom,
47
+ };
48
+ const content = {
49
+ left: inner.left + padding.left,
50
+ top: inner.top + padding.top,
51
+ right: inner.right - padding.right,
52
+ bottom: inner.bottom - padding.bottom,
53
+ };
54
+ return { outer, inner, content, padding };
55
+ }
56
+ function containsPoint(rect, x, y) {
57
+ return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
58
+ }
59
+ function containsRectPoint(rect, x, y) {
60
+ return x >= rect.left
61
+ && x <= rect.left + rect.width
62
+ && y >= rect.top
63
+ && y <= rect.top + rect.height;
64
+ }
65
+ function guideHit(guide, orientation) {
66
+ if (orientation === "horizontal") {
67
+ const height = Math.max(SPACING_DRAG_TARGET_SIZE, guide.height);
68
+ const width = Math.min(SPACING_GUIDE_HANDLE_LENGTH, guide.width);
69
+ return {
70
+ left: guide.left + (guide.width - width) / 2,
71
+ top: guide.top - (height - guide.height) / 2,
72
+ width,
73
+ height,
74
+ };
75
+ }
76
+ const width = Math.max(SPACING_DRAG_TARGET_SIZE, guide.width);
77
+ const height = Math.min(SPACING_GUIDE_HANDLE_LENGTH, guide.height);
78
+ return {
79
+ left: guide.left - (width - guide.width) / 2,
80
+ top: guide.top + (guide.height - height) / 2,
81
+ width,
82
+ height,
83
+ };
84
+ }
85
+ const PADDING_CONFIG = {
86
+ top: { axis: "horizontal", dragAxis: "y", direction: 1 },
87
+ right: { axis: "vertical", dragAxis: "x", direction: 1 },
88
+ bottom: { axis: "horizontal", dragAxis: "y", direction: 1 },
89
+ left: { axis: "vertical", dragAxis: "x", direction: 1 },
90
+ };
91
+ function paddingAffordanceForSide(element, side, model = boxModel(element)) {
92
+ const { inner, content, padding } = model;
93
+ const value = padding[side];
94
+ if (value <= EPSILON)
95
+ return null;
96
+ const config = PADDING_CONFIG[side];
97
+ const width = Math.max(0, inner.right - inner.left);
98
+ const height = Math.max(0, inner.bottom - inner.top);
99
+ const guide = config.axis === "horizontal"
100
+ ? {
101
+ left: inner.left,
102
+ top: (side === "top" ? inner.top + value / 2 : inner.bottom - value / 2) - 1,
103
+ width,
104
+ height: 2,
105
+ }
106
+ : {
107
+ left: (side === "left" ? inner.left + value / 2 : inner.right - value / 2) - 1,
108
+ top: inner.top,
109
+ width: 2,
110
+ height,
111
+ };
112
+ const area = config.axis === "horizontal"
113
+ ? {
114
+ left: inner.left,
115
+ top: side === "top" ? inner.top : content.bottom,
116
+ width,
117
+ height: Math.max(0, (side === "top" ? content.top : inner.bottom) - (side === "top" ? inner.top : content.bottom)),
118
+ }
119
+ : {
120
+ left: side === "left" ? inner.left : content.right,
121
+ top: inner.top,
122
+ width: Math.max(0, (side === "left" ? content.left : inner.right) - (side === "left" ? inner.left : content.right)),
123
+ height,
124
+ };
125
+ const hit = guideHit(guide, config.axis);
126
+ return {
127
+ kind: "padding",
128
+ property: `padding-${side}`,
129
+ side,
130
+ axis: config.axis,
131
+ dragAxis: config.dragAxis,
132
+ value,
133
+ cursor: config.axis === "horizontal" ? "ns-resize" : "ew-resize",
134
+ guide,
135
+ affectedGuides: [guide],
136
+ affectedAreas: [area],
137
+ hit,
138
+ direction: config.direction,
139
+ element,
140
+ };
141
+ }
142
+ function paddingAffordanceAtPoint(element, x, y) {
143
+ const model = boxModel(element);
144
+ if (!containsPoint(model.outer, x, y))
145
+ return null;
146
+ const sides = ["top", "right", "bottom", "left"];
147
+ for (const side of sides) {
148
+ const affordance = paddingAffordanceForSide(element, side, model);
149
+ if (affordance && containsRectPoint(affordance.hit, x, y))
150
+ return affordance;
151
+ }
152
+ return null;
153
+ }
154
+ function overlap(startA, endA, startB, endB) {
155
+ return Math.min(endA, endB) - Math.max(startA, startB) > EPSILON;
156
+ }
157
+ function layoutChildren(element) {
158
+ const HTMLElementConstructor = element.ownerDocument.defaultView?.HTMLElement;
159
+ if (!HTMLElementConstructor)
160
+ return [];
161
+ return Array.from(element.children)
162
+ .filter((child) => child instanceof HTMLElementConstructor)
163
+ .map((child) => ({ element: child, rect: boxEdges(child) }));
164
+ }
165
+ function gapSegments(element) {
166
+ const style = getElementComputedStyle(element);
167
+ if (style.display !== "flex"
168
+ && style.display !== "inline-flex"
169
+ && style.display !== "grid"
170
+ && style.display !== "inline-grid")
171
+ return [];
172
+ const children = layoutChildren(element);
173
+ const segments = [];
174
+ const byLeft = [...children].sort((left, right) => left.rect.left - right.rect.left);
175
+ const byTop = [...children].sort((top, bottom) => top.rect.top - bottom.rect.top);
176
+ for (const left of byLeft) {
177
+ const next = byLeft.find((right) => right.rect.left >= left.rect.right - EPSILON
178
+ && overlap(left.rect.top, left.rect.bottom, right.rect.top, right.rect.bottom));
179
+ if (!next)
180
+ continue;
181
+ segments.push({
182
+ property: "column-gap",
183
+ axis: "horizontal",
184
+ start: left.rect.right,
185
+ end: next.rect.left,
186
+ crossStart: Math.max(left.rect.top, next.rect.top),
187
+ crossEnd: Math.min(left.rect.bottom, next.rect.bottom),
188
+ });
189
+ }
190
+ for (const top of byTop) {
191
+ const next = byTop.find((bottom) => bottom.rect.top >= top.rect.bottom - EPSILON
192
+ && overlap(top.rect.left, top.rect.right, bottom.rect.left, bottom.rect.right));
193
+ if (!next)
194
+ continue;
195
+ segments.push({
196
+ property: "row-gap",
197
+ axis: "vertical",
198
+ start: top.rect.bottom,
199
+ end: next.rect.top,
200
+ crossStart: Math.max(top.rect.left, next.rect.left),
201
+ crossEnd: Math.min(top.rect.right, next.rect.right),
202
+ });
203
+ }
204
+ return segments;
205
+ }
206
+ function gapAffordanceAtPoint(element, x, y, property) {
207
+ const segments = gapSegments(element);
208
+ if (segments.length === 0)
209
+ return null;
210
+ const style = getElementComputedStyle(element);
211
+ const candidate = segments.find((segment) => {
212
+ if (property && segment.property !== property)
213
+ return false;
214
+ return containsRectPoint(gapDragHit(segment), x, y);
215
+ });
216
+ if (!candidate)
217
+ return null;
218
+ const value = gapValue(style, candidate.property);
219
+ const guide = gapGuide(candidate);
220
+ const matchingSegments = segments.filter((segment) => segment.property === candidate.property);
221
+ const hit = gapDragHit(candidate);
222
+ return {
223
+ kind: "gap",
224
+ property: candidate.property,
225
+ side: null,
226
+ axis: candidate.axis,
227
+ dragAxis: candidate.axis === "horizontal" ? "x" : "y",
228
+ value,
229
+ cursor: candidate.axis === "horizontal" ? "ew-resize" : "ns-resize",
230
+ guide,
231
+ affectedGuides: matchingSegments.map(gapGuide),
232
+ affectedAreas: matchingSegments.map(gapArea),
233
+ hit,
234
+ // Gap size is absolute screen space; dragging along +axis always grows it.
235
+ direction: 1,
236
+ element,
237
+ };
238
+ }
239
+ function gapGuide(segment) {
240
+ const midpoint = (segment.start + segment.end) / 2;
241
+ const crossSize = Math.max(0, segment.crossEnd - segment.crossStart);
242
+ return segment.axis === "horizontal"
243
+ ? { left: midpoint - 1, top: segment.crossStart, width: 2, height: crossSize }
244
+ : { left: segment.crossStart, top: midpoint - 1, width: crossSize, height: 2 };
245
+ }
246
+ function gapArea(segment) {
247
+ const crossSize = Math.max(0, segment.crossEnd - segment.crossStart);
248
+ return segment.axis === "horizontal"
249
+ ? {
250
+ left: segment.start,
251
+ top: segment.crossStart,
252
+ width: Math.max(0, segment.end - segment.start),
253
+ height: crossSize,
254
+ }
255
+ : {
256
+ left: segment.crossStart,
257
+ top: segment.start,
258
+ width: crossSize,
259
+ height: Math.max(0, segment.end - segment.start),
260
+ };
261
+ }
262
+ function gapDragHit(segment) {
263
+ const orientation = segment.axis === "horizontal" ? "vertical" : "horizontal";
264
+ return guideHit(gapGuide(segment), orientation);
265
+ }
266
+ function gapValue(style, property) {
267
+ const direct = style.getPropertyValue(property)
268
+ || (property === "row-gap" ? style.rowGap : style.columnGap);
269
+ if (direct.trim())
270
+ return numberValue(direct);
271
+ // jsdom and a few older browser CSSOM implementations expose `gap` but not
272
+ // the longhands through getPropertyValue(). Keep the gesture model aligned
273
+ // with the browser's shorthand fallback in those realms.
274
+ const shorthand = style.getPropertyValue("gap") || style.gap;
275
+ const values = shorthand.trim().split(/\s+/).filter(Boolean);
276
+ if (values.length === 0)
277
+ return 0;
278
+ if (values.length === 1)
279
+ return numberValue(values[0]);
280
+ return numberValue(property === "row-gap" ? values[0] : values[1]);
281
+ }
282
+ function trackedElementsAtPoint(doc, x, y) {
283
+ if (typeof doc.elementFromPoint !== "function")
284
+ return [];
285
+ const target = doc.elementFromPoint(x, y);
286
+ if (!target)
287
+ return [];
288
+ const HTMLElementConstructor = doc.defaultView?.HTMLElement;
289
+ if (!HTMLElementConstructor)
290
+ return [];
291
+ const result = [];
292
+ let element = target;
293
+ while (element) {
294
+ if (element instanceof HTMLElementConstructor && element.hasAttribute("data-cid")) {
295
+ result.push(element);
296
+ }
297
+ element = element.parentElement;
298
+ }
299
+ return result;
300
+ }
301
+ /** Finds the spacing target under a document-local pointer location. */
302
+ export function getSpacingAffordanceAtPoint(doc, x, y) {
303
+ const candidates = trackedElementsAtPoint(doc, x, y);
304
+ for (const element of candidates) {
305
+ const padding = paddingAffordanceAtPoint(element, x, y);
306
+ if (padding)
307
+ return padding;
308
+ }
309
+ for (const element of candidates) {
310
+ const gap = gapAffordanceAtPoint(element, x, y);
311
+ if (gap)
312
+ return gap;
313
+ }
314
+ return null;
315
+ }
316
+ /** Re-resolves a renderer-reported spacing descriptor in its owner document. */
317
+ export function getSpacingAffordanceForDescriptor(element, descriptor) {
318
+ if (descriptor.kind === "padding") {
319
+ const side = descriptor.side;
320
+ if (!side || descriptor.property !== `padding-${side}`)
321
+ return null;
322
+ return paddingAffordanceForSide(element, side);
323
+ }
324
+ if (descriptor.property !== "row-gap" && descriptor.property !== "column-gap")
325
+ return null;
326
+ const segment = gapSegments(element).find((candidate) => candidate.property === descriptor.property);
327
+ if (!segment)
328
+ return null;
329
+ const guide = gapGuide(segment);
330
+ return gapAffordanceAtPoint(element, guide.left + guide.width / 2, guide.top + guide.height / 2, descriptor.property);
331
+ }
332
+ /** Calculates a direct-manipulation value, optionally stepping changes by 8px. */
333
+ export function spacingValueForDrag(affordance, start, point, shiftKey = false) {
334
+ const delta = affordance.dragAxis === "x" ? point.x - start.x : point.y - start.y;
335
+ const signedDelta = delta * affordance.direction;
336
+ const adjustedDelta = shiftKey
337
+ ? Math.trunc(signedDelta / SPACING_SHIFT_STEP) * SPACING_SHIFT_STEP
338
+ : signedDelta;
339
+ return Math.max(0, Math.round(affordance.value + adjustedDelta));
340
+ }
341
+ export function spacingValueCss(value) {
342
+ return `${Math.max(0, Math.round(value))}px`;
343
+ }
344
+ /** Serializes the stable part of an affordance for renderer-to-controller messages. */
345
+ export function toSpacingDescriptor(affordance) {
346
+ return {
347
+ kind: affordance.kind,
348
+ property: affordance.property,
349
+ side: affordance.side,
350
+ };
351
+ }
@@ -20,9 +20,9 @@ export declare function canContainElement(parent: HTMLElement, child: HTMLElemen
20
20
  /** Derives a safe insertion point; its nodes never leave this gesture module. */
21
21
  export declare function getDropLocationForElement(dragged: HTMLElement, target: HTMLElement, beforeTarget: boolean, preferContainer?: boolean): DropLocation | null;
22
22
  export declare function getDropLocationAtPoint(doc: Document, dragged: HTMLElement, x: number, y: number): DropLocation | null;
23
- /** Capture the move as canonical intent, then project only the controller host. */
23
+ /** Capture the move as canonical intent, then preview it in an inline host. */
24
24
  export declare function moveElement(node: HTMLElement, destination: DropLocation): StructuralMove | null;
25
25
  /** Reorder only same-parent siblings; horizontal movement needs a flex row. */
26
26
  export declare function nudgeElement(node: HTMLElement, key: StructuralNudgeKey): StructuralMove | null;
27
- /** Capture delete intent and project only the controller host. */
27
+ /** Capture delete intent, then preview it in an inline host. */
28
28
  export declare function deleteElement(selected: SelectedElement): StructuralDelete | null;
@@ -1,4 +1,5 @@
1
1
  import { applyStructuralProjection, canContainElement as canContainStructuralElement, createStructuralDelete, createStructuralMove, getStructuralMoveLegality, getStructuralChanges, } from "../projection/structuralProjection.js";
2
+ import { isEditorShellDocument } from "../runtime/editorShell.js";
2
3
  /** Conservative HTML guard shared with canonical capture and document replay. */
3
4
  export function canContainElement(parent, child) {
4
5
  return canContainStructuralElement(parent, child);
@@ -141,16 +142,18 @@ export function getDropLocationAtPoint(doc, dragged, x, y) {
141
142
  const inContainerInterior = y - rect.top > edgeBand && y - rect.top < rect.height - edgeBand;
142
143
  return getDropLocationForElement(dragged, target, y < rect.top + rect.height / 2, inContainerInterior);
143
144
  }
144
- /** Capture the move as canonical intent, then project only the controller host. */
145
+ function projectInlineHost(node) {
146
+ if (node.ownerDocument !== document || isEditorShellDocument(document))
147
+ return;
148
+ applyStructuralProjection(document, getStructuralChanges());
149
+ }
150
+ /** Capture the move as canonical intent, then preview it in an inline host. */
145
151
  export function moveElement(node, destination) {
146
152
  if (!getStructuralMoveLegality(node, destination).valid)
147
153
  return null;
148
154
  const change = createStructuralMove(node, destination);
149
- if (!change)
150
- return null;
151
- // `document` belongs to the controller runtime. A Canvas source element is
152
- // deliberately not projected here; its renderer receives this snapshot once.
153
- applyStructuralProjection(document, getStructuralChanges());
155
+ if (change)
156
+ projectInlineHost(node);
154
157
  return change;
155
158
  }
156
159
  /** Reorder only same-parent siblings; horizontal movement needs a flex row. */
@@ -167,14 +170,13 @@ export function nudgeElement(node, key) {
167
170
  const before = moveEarlier ? sibling : sibling.nextElementSibling;
168
171
  return moveElement(node, { parent, before, ...lineFor(parent, before) });
169
172
  }
170
- /** Capture delete intent and project only the controller host. */
173
+ /** Capture delete intent, then preview it in an inline host. */
171
174
  export function deleteElement(selected) {
172
175
  const node = selected.domElement;
173
176
  if (!node.parentElement || !node.isConnected)
174
177
  return null;
175
178
  const change = createStructuralDelete(node);
176
- if (!change)
177
- return null;
178
- applyStructuralProjection(document, getStructuralChanges());
179
+ if (change)
180
+ projectInlineHost(node);
179
181
  return change;
180
182
  }
@@ -171,6 +171,9 @@ function commaListIncludes(value, property) {
171
171
  return value.split(",").map((part) => part.trim()).some((part) => part === "all" || part === property);
172
172
  }
173
173
  function hasImportantAuthorRule(el, property) {
174
+ const StyleRule = el.ownerDocument.defaultView?.CSSStyleRule;
175
+ if (!StyleRule)
176
+ return false;
174
177
  for (const sheet of Array.from(el.ownerDocument.styleSheets)) {
175
178
  let rules;
176
179
  try {
@@ -180,7 +183,7 @@ function hasImportantAuthorRule(el, property) {
180
183
  continue;
181
184
  }
182
185
  for (const rule of Array.from(rules)) {
183
- if (!(rule instanceof CSSStyleRule))
186
+ if (!(rule instanceof StyleRule))
184
187
  continue;
185
188
  try {
186
189
  if (el.matches(rule.selectorText) && rule.style.getPropertyPriority(property) === "important")
@@ -44,6 +44,8 @@ export declare function collectRenderedInstanceOverrides(changes: ReadonlyArray<
44
44
  * later reconciliation changes diagnostics but never starts a reapply loop.
45
45
  */
46
46
  export declare function applyRenderedInstanceProjection(doc: Document, overrides: ReadonlyArray<RenderedInstanceOverride>): DocumentProjectionReport[];
47
+ /** Registers controller-owned overrides without projecting them into the editor shell. */
48
+ export declare function setCanonicalRenderedInstanceOverrides(overrides: ReadonlyArray<RenderedInstanceOverride>): void;
47
49
  export declare function getRenderedInstanceProjectionReports(doc: Document): readonly DocumentProjectionReport[];
48
50
  /** Diagnostics arrive independently when a renderer reports or React reconciles. */
49
51
  export declare function subscribeRenderedInstanceDiagnostics(listener: () => void): () => void;
@@ -213,7 +213,7 @@ export function applyRenderedInstanceProjection(doc, overrides) {
213
213
  if (!isNudgeUiDev())
214
214
  return [];
215
215
  const state = getDocumentState(doc);
216
- canonicalOverrides = new Map(overrides.map((override) => [override.id, override]));
216
+ setCanonicalRenderedInstanceOverrides(overrides);
217
217
  const key = JSON.stringify(overrides);
218
218
  if (state.snapshotKey === key) {
219
219
  const reports = reportsForSnapshot(state, overrides);
@@ -239,6 +239,10 @@ export function applyRenderedInstanceProjection(doc, overrides) {
239
239
  storeReports(doc, reports);
240
240
  return reports;
241
241
  }
242
+ /** Registers controller-owned overrides without projecting them into the editor shell. */
243
+ export function setCanonicalRenderedInstanceOverrides(overrides) {
244
+ canonicalOverrides = new Map(overrides.map((override) => [override.id, override]));
245
+ }
242
246
  export function getRenderedInstanceProjectionReports(doc) {
243
247
  return reportsByDocument.get(doc) ?? [];
244
248
  }
@@ -29,6 +29,8 @@ export declare function getStructuralMoveLegality(element: HTMLElement, destinat
29
29
  export interface StructuralChangeDiagnostic extends StructuralProjectionReport {
30
30
  document: "Inspect" | `Canvas ${string}`;
31
31
  }
32
+ /** Returns bounded view state used to verify a delete in the same rendered view. */
33
+ export declare function documentStateKey(doc: Document, excluded?: HTMLElement): string | undefined;
32
34
  /** Captures a delete intent once, in the controller, at the user gesture. */
33
35
  export declare function createStructuralDelete(element: HTMLElement, id?: string): StructuralDelete | null;
34
36
  /**
@@ -95,6 +95,82 @@ function notifyDiagnostics() {
95
95
  function structuralId() {
96
96
  return `structural-${crypto.randomUUID?.() ?? nextStructuralId++}`;
97
97
  }
98
+ function documentRoute(doc) {
99
+ if (!doc.location)
100
+ return undefined;
101
+ try {
102
+ const url = new URL(doc.location.href);
103
+ url.hash = "";
104
+ return url.href;
105
+ }
106
+ catch {
107
+ return undefined;
108
+ }
109
+ }
110
+ function stableStateValue(value, seen = new WeakSet()) {
111
+ if (!value || typeof value !== "object")
112
+ return value;
113
+ if (seen.has(value))
114
+ return "[Circular]";
115
+ seen.add(value);
116
+ if (Array.isArray(value))
117
+ return value.map((entry) => stableStateValue(entry, seen));
118
+ return Object.fromEntries(Object.entries(value)
119
+ .sort(([left], [right]) => left.localeCompare(right))
120
+ .map(([key, entry]) => [key, stableStateValue(entry, seen)]));
121
+ }
122
+ function textWithoutExcluded(element, excluded) {
123
+ let text = "";
124
+ const visit = (node) => {
125
+ if (node === excluded)
126
+ return;
127
+ if (node.nodeType === node.TEXT_NODE) {
128
+ text += node.nodeValue ?? "";
129
+ return;
130
+ }
131
+ node.childNodes.forEach(visit);
132
+ };
133
+ visit(element);
134
+ return text.replace(/\s+/g, " ").trim().slice(0, 120) || null;
135
+ }
136
+ function renderedInstanceState(doc, excluded) {
137
+ try {
138
+ return Array.from(doc.querySelectorAll("[data-cid][data-src]"))
139
+ .filter((element) => element !== excluded && !excluded?.contains(element))
140
+ .map((element) => ({
141
+ tag: element.tagName.toLowerCase(),
142
+ cid: element.getAttribute("data-cid"),
143
+ src: element.getAttribute("data-src"),
144
+ props: element.getAttribute("data-cprops"),
145
+ text: textWithoutExcluded(element, excluded),
146
+ ariaLabel: element.getAttribute("aria-label"),
147
+ }));
148
+ }
149
+ catch {
150
+ return undefined;
151
+ }
152
+ }
153
+ /** Returns bounded view state used to verify a delete in the same rendered view. */
154
+ export function documentStateKey(doc, excluded) {
155
+ const view = doc.defaultView;
156
+ if (!view || !doc.location)
157
+ return undefined;
158
+ try {
159
+ const url = new URL(doc.location.href);
160
+ const rendered = renderedInstanceState(doc, excluded);
161
+ if (!rendered)
162
+ return undefined;
163
+ const state = JSON.stringify({
164
+ hash: url.hash,
165
+ history: stableStateValue(view.history.state),
166
+ rendered,
167
+ });
168
+ return state.length <= 4096 ? state : undefined;
169
+ }
170
+ catch {
171
+ return undefined;
172
+ }
173
+ }
98
174
  function cloneSnapshot(snapshot) {
99
175
  // Structural changes are small JSON-safe records. Cloning their top-level
100
176
  // array is enough: no operation mutates a change or its nested references.
@@ -108,7 +184,15 @@ export function createStructuralDelete(element, id = structuralId()) {
108
184
  const target = captureRenderedInstance(element);
109
185
  if (!target)
110
186
  return null;
111
- const change = { id, kind: "delete", target };
187
+ const route = documentRoute(element.ownerDocument);
188
+ const state = documentStateKey(element.ownerDocument, element);
189
+ const change = {
190
+ id,
191
+ kind: "delete",
192
+ target,
193
+ ...(route ? { route } : {}),
194
+ ...(state ? { state } : {}),
195
+ };
112
196
  if (!workspaceChangeStore.commitStructuralChange(change))
113
197
  return null;
114
198
  projectStructuralChanges(workspaceChangeStore.getSnapshot());
@@ -18,12 +18,28 @@ export function isStructuralDelete(value) {
18
18
  if (!value || typeof value !== "object")
19
19
  return false;
20
20
  const candidate = value;
21
- if (!hasOnlyKeys(candidate, ["id", "kind", "target"]))
21
+ if (!hasOnlyKeys(candidate, ["id", "kind", "target", "route", "state"]))
22
22
  return false;
23
23
  return candidate.kind === "delete"
24
24
  && typeof candidate.id === "string"
25
+ && (candidate.route === undefined || isRoute(candidate.route))
26
+ && (candidate.state === undefined || isStateKey(candidate.state))
25
27
  && isRenderedInstanceRef(candidate.target);
26
28
  }
29
+ function isStateKey(value) {
30
+ return typeof value === "string" && value.length > 0 && value.length <= 4096;
31
+ }
32
+ function isRoute(value) {
33
+ if (typeof value !== "string" || value.length > 2048)
34
+ return false;
35
+ try {
36
+ const url = new URL(value);
37
+ return (url.protocol === "http:" || url.protocol === "https:") && url.hash === "";
38
+ }
39
+ catch {
40
+ return false;
41
+ }
42
+ }
27
43
  export function isStructuralMove(value) {
28
44
  if (!value || typeof value !== "object")
29
45
  return false;
@@ -43,6 +43,8 @@ export declare function collectTextContentChanges(changes: ReadonlyArray<unknown
43
43
  export declare function getTextContentChangeById(changeId: string): TextContentChangeRecord | null;
44
44
  /** Apply one controller-owned text snapshot to a document adapter. */
45
45
  export declare function applyTextContentProjection(doc: Document, changes: ReadonlyArray<TextContentChangeRecord>): TextProjectionReport[];
46
+ /** Registers controller-owned text changes without projecting them into the editor shell. */
47
+ export declare function setCanonicalTextContentChanges(changes: ReadonlyArray<TextContentChangeRecord>): void;
46
48
  export declare function getTextProjectionReports(doc: Document): readonly TextProjectionReport[];
47
49
  export declare function subscribeTextProjectionDiagnostics(listener: () => void): () => void;
48
50
  export declare function getTextProjectionDiagnosticRevision(): number;
@@ -42,7 +42,8 @@ function markerKey(target) {
42
42
  return target.textNodePath ? `path:${target.textNodePath.join(",")}` : "root";
43
43
  }
44
44
  function isEmptyTextAffordance(node) {
45
- return node instanceof HTMLElement && node.hasAttribute(EMPTY_TEXT_PROJECTION_ATTR);
45
+ return node?.nodeType === 1
46
+ && node.hasAttribute(EMPTY_TEXT_PROJECTION_ATTR);
46
47
  }
47
48
  function hasInlineTextEditor(element) {
48
49
  return element.querySelector('[data-inline-editor="true"]') !== null;
@@ -308,8 +309,9 @@ function emptyTextAffordanceFor(doc, state, change, element) {
308
309
  if (!textNode || textNode.nodeValue !== "")
309
310
  return null;
310
311
  const parent = textNode.parentNode;
311
- if (!(parent instanceof HTMLElement))
312
+ if (parent?.nodeType !== 1)
312
313
  return null;
314
+ const parentElement = parent;
313
315
  const path = markerKey(change.target);
314
316
  // During re-entry the temporary editor wraps the empty Text node. The
315
317
  // existing slot remains beside that wrapper; search the whole projected
@@ -332,7 +334,7 @@ function emptyTextAffordanceFor(doc, state, change, element) {
332
334
  // It has no application text and is never a source identity. The
333
335
  // interaction stylesheet supplies its visible, borderless hit geometry.
334
336
  affordance.hidden = !isInteractionStylesInstalled(doc);
335
- parent.insertBefore(affordance, textNode.nextSibling);
337
+ parentElement.insertBefore(affordance, textNode.nextSibling);
336
338
  state.emptyAffordances.set(change.id, affordance);
337
339
  return affordance;
338
340
  }
@@ -546,7 +548,7 @@ export function applyTextContentProjection(doc, changes) {
546
548
  if (!isNudgeUiDev())
547
549
  return [];
548
550
  const state = getDocumentState(doc);
549
- canonicalChanges = new Map(changes.map((change) => [change.id, change]));
551
+ setCanonicalTextContentChanges(changes);
550
552
  const key = JSON.stringify(changes);
551
553
  if (state.snapshotKey === key) {
552
554
  // Reconciliation can happen between controller snapshots. Validate before
@@ -624,6 +626,10 @@ export function applyTextContentProjection(doc, changes) {
624
626
  storeReports(doc, reports);
625
627
  return reports;
626
628
  }
629
+ /** Registers controller-owned text changes without projecting them into the editor shell. */
630
+ export function setCanonicalTextContentChanges(changes) {
631
+ canonicalChanges = new Map(changes.map((change) => [change.id, change]));
632
+ }
627
633
  export function getTextProjectionReports(doc) {
628
634
  return reportsByDocument.get(doc) ?? [];
629
635
  }
@@ -25,7 +25,7 @@ export declare function getLastClipboardReconciledCount(): number;
25
25
  * Checks a copied prompt against source-rendered output and reconciles only
26
26
  * exact records that the browser can positively verify.
27
27
  */
28
- export declare function reconcileClipboardHandoff(): Promise<number>;
28
+ export declare function reconcileClipboardHandoff(doc?: Document): Promise<number>;
29
29
  /**
30
30
  * Watches source-driven document refreshes and page-return signals. The
31
31
  * trailing debounce lets an HMR commit settle before verification lifts the