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,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
@@ -137,7 +137,7 @@ function pruneCheckpoint() {
137
137
  * Checks a copied prompt against source-rendered output and reconciles only
138
138
  * exact records that the browser can positively verify.
139
139
  */
140
- export async function reconcileClipboardHandoff() {
140
+ export async function reconcileClipboardHandoff(doc = document) {
141
141
  const snapshot = checkpoint;
142
142
  if (!snapshot)
143
143
  return 0;
@@ -147,7 +147,7 @@ export async function reconcileClipboardHandoff() {
147
147
  pruneCheckpoint();
148
148
  return 0;
149
149
  }
150
- const removed = await verifyAndReconcileHandoff({ changes, structuralChanges });
150
+ const removed = await verifyAndReconcileHandoff({ changes, structuralChanges }, doc);
151
151
  pruneCheckpoint();
152
152
  if (removed > 0) {
153
153
  lastReconciledCount = removed;
@@ -175,7 +175,7 @@ export function startClipboardHandoffController(doc = document) {
175
175
  if (stopped || checking || !checkpoint)
176
176
  return;
177
177
  checking = true;
178
- void reconcileClipboardHandoff()
178
+ void reconcileClipboardHandoff(doc)
179
179
  .catch(() => undefined)
180
180
  .finally(() => {
181
181
  // Keep the guard through the MutationObserver microtask generated by
@@ -1,7 +1,8 @@
1
1
  import type { ChangeRecord } from "../changes/changesLog.js";
2
2
  import type { StructuralChange } from "../projection/structuralProjection.js";
3
+ import type { AgentSketchMetadata } from "../agent/protocol.js";
3
4
  export interface FrameworkHints {
4
5
  framework?: string;
5
6
  stylingSystem?: string;
6
7
  }
7
- export declare function generatePrompt(changes: ChangeRecord[], frameworkHints?: FrameworkHints, structuralChanges?: readonly StructuralChange[], customInstructions?: string): string;
8
+ export declare function generatePrompt(changes: ChangeRecord[], frameworkHints?: FrameworkHints, structuralChanges?: readonly StructuralChange[], customInstructions?: string, sketches?: readonly AgentSketchMetadata[]): string;
@@ -244,6 +244,17 @@ function structuralChangeLine(change) {
244
244
  ? `- Move ${target} before ${renderedInstanceDescription(before)} in ${destinationParent}.`
245
245
  : `- Move ${target} to the end of ${destinationParent}.`;
246
246
  }
247
+ function sketchLines(sketches) {
248
+ const titles = [...new Set(sketches.map((sketch) => sketch.capture.title || "(untitled)"))];
249
+ return [
250
+ ...titles.map((title) => `- Page title: ${promptText(title)}`),
251
+ ...sketches.flatMap((sketch) => [
252
+ ...(sketch.description ? [`- Description: ${promptText(sketch.description)}`] : []),
253
+ ...(sketch.annotations ?? []).map((annotation) => `- Annotation ${annotation.number}: ${promptText(annotation.description)}`),
254
+ ]),
255
+ "- Interpret the attached frozen screenshot, blue strokes, and numbered annotations as visual feedback. Do not derive selectors, source coordinates, or semantics from the strokes. Use each annotation number to match the note to its numbered dot.",
256
+ ];
257
+ }
247
258
  function renderedInstanceKey(ref) {
248
259
  const { sourceSite, locator } = ref;
249
260
  return JSON.stringify([
@@ -304,10 +315,10 @@ function canonicalizeStructuralChanges(changes) {
304
315
  }
305
316
  return canonical;
306
317
  }
307
- export function generatePrompt(changes, frameworkHints, structuralChanges = [], customInstructions = DEFAULT_CUSTOM_INSTRUCTIONS) {
318
+ export function generatePrompt(changes, frameworkHints, structuralChanges = [], customInstructions = DEFAULT_CUSTOM_INSTRUCTIONS, sketches = []) {
308
319
  const deduplicated = canonicalizeChanges(changes);
309
320
  const structuralIntent = canonicalizeStructuralChanges(structuralChanges);
310
- if (deduplicated.length === 0 && structuralIntent.length === 0)
321
+ if (deduplicated.length === 0 && structuralIntent.length === 0 && sketches.length === 0)
311
322
  return EMPTY_SENTINEL;
312
323
  const tokenChanges = deduplicated.filter(isTokenChange);
313
324
  const componentChanges = deduplicated.filter(isComponentChange);
@@ -387,5 +398,11 @@ export function generatePrompt(changes, frameworkHints, structuralChanges = [],
387
398
  lines: structuralIntent.map(structuralChangeLine),
388
399
  });
389
400
  }
401
+ if (sketches.length > 0) {
402
+ sections.push({
403
+ heading: "Sketch annotations",
404
+ lines: sketchLines(sketches),
405
+ });
406
+ }
390
407
  return renderPrompt(sections, customInstructions);
391
408
  }
@@ -0,0 +1,2 @@
1
+ /** Returns whether a document is the application-free editor controller. */
2
+ export declare function isEditorShellDocument(doc?: Document): boolean;
@@ -0,0 +1,4 @@
1
+ /** Returns whether a document is the application-free editor controller. */
2
+ export function isEditorShellDocument(doc = document) {
3
+ return doc.documentElement.hasAttribute("data-nudge-ui-editor");
4
+ }
@@ -60,6 +60,10 @@ export interface NudgeUiRuntimeConfig {
60
60
  readonly componentContracts: readonly ComponentContract[];
61
61
  /** Whether this runtime is the explicit public landing-page demo. */
62
62
  readonly demo?: boolean;
63
+ /** Additional same-origin pages seeded into the public demo canvas. */
64
+ readonly demoPages?: readonly string[];
65
+ /** Labels for demo cards in display order, including the primary card. */
66
+ readonly demoCardLabels?: readonly string[];
63
67
  }
64
68
  /**
65
69
  * Validates a host-supplied configuration and fills safe defaults.
@@ -170,6 +170,8 @@ export function normalizeNudgeUiRuntimeConfig(input) {
170
170
  if (isNormalizedRuntimeConfig(input))
171
171
  return input;
172
172
  const demo = optionalDemoFlag(input);
173
+ const demoPages = optionalStringArray(input, "demoPages");
174
+ const demoCardLabels = optionalStringArray(input, "demoCardLabels");
173
175
  const normalized = cloneAndFreeze({
174
176
  projectId: requireString(input, "projectId"),
175
177
  host: requireEnum(input, "host", RUNTIME_HOSTS),
@@ -182,6 +184,8 @@ export function normalizeNudgeUiRuntimeConfig(input) {
182
184
  tokenGeneration: optionalString(input, "tokenGeneration"),
183
185
  componentContracts: optionalArray(input, "componentContracts"),
184
186
  ...(demo === true ? { demo: true } : {}),
187
+ ...(demoPages === undefined ? {} : { demoPages }),
188
+ ...(demoCardLabels === undefined ? {} : { demoCardLabels }),
185
189
  });
186
190
  normalizedRuntimeConfigs.add(normalized);
187
191
  return normalized;
@@ -1,6 +1,7 @@
1
1
  import { useSyncExternalStore } from "react";
2
2
  import { computeHierarchy } from "./hierarchy.js";
3
3
  import { resolveSelectionFromElement } from "./resolveSelection.js";
4
+ import { clearInlineTextDiagnosticsForSelection } from "../inline-text/inlineTextDiagnostics.js";
4
5
  let currentSelection = null;
5
6
  const EMPTY_SELECTION = [];
6
7
  let chain = [];
@@ -28,6 +29,7 @@ function getHierarchyIndex() {
28
29
  return index;
29
30
  }
30
31
  function notify() {
32
+ clearInlineTextDiagnosticsForSelection(getSelectedElements().map((element) => element.domElement));
31
33
  listeners.forEach((l) => l());
32
34
  }
33
35
  function sameComponentTargets(a, b) {
@@ -6,6 +6,10 @@ import { useBrowserCssInspection } from "../inspection/useBrowserCssInspection.j
6
6
  import { McpConnectionContent, } from "../agent/McpConnectionDialog.js";
7
7
  import { PromptSettingsFields, } from "../prompt/PromptSettingsDialog.js";
8
8
  import { TokensPanel } from "../tokens/TokensPanel.js";
9
+ import { useSelectedElement } from "../selection/selectionStore.js";
10
+ import { useFocusedCardId, useSelectedCardId } from "../canvas/canvasStore.js";
11
+ import { getRegisteredFrames } from "../canvas/projection.js";
12
+ import { getBrowserCssInspection } from "../inspection/browserCssInspectionRegistry.js";
9
13
  const SETTINGS_SECTIONS = [
10
14
  {
11
15
  id: "instructions",
@@ -33,8 +37,14 @@ export function SettingsDialog({ open, initialSection, value, onValueChange, onO
33
37
  const [activeSection, setActiveSection] = useState(initialSection);
34
38
  const closeRef = useRef(null);
35
39
  const instructionsRef = useRef(null);
40
+ const selectedElement = useSelectedElement();
41
+ const selectedCardId = useSelectedCardId();
42
+ const focusedCardId = useFocusedCardId();
43
+ const activeFrame = getRegisteredFrames().get(selectedCardId ?? focusedCardId ?? "");
44
+ const activeDocument = selectedElement?.domElement.ownerDocument ?? activeFrame?.contentDocument ?? document;
36
45
  const tokenInspection = useBrowserCssInspection(null, "base", {
37
46
  includeDocumentTokens: activeSection === "tokens",
47
+ session: getBrowserCssInspection(activeDocument),
38
48
  });
39
49
  const tokenRows = tokenInspection.documentTokens?.tokens ?? [];
40
50
  const activeDefinition = SETTINGS_SECTIONS.find((section) => section.id === activeSection) ?? SETTINGS_SECTIONS[0];
@@ -3,7 +3,11 @@ import { InspectorShell } from "./InspectorShell.js";
3
3
  import { CanvasWorkspace } from "../canvas/CanvasWorkspace.js";
4
4
  import { CanvasElementOverlay } from "../canvas/CanvasElementOverlay.js";
5
5
  import { useNudgeUiRuntimeConfig } from "../runtime/useRuntimeConfig.js";
6
+ import { readNudgeUiEditorTarget } from "../../transport/editor.js";
6
7
  export function AppShell() {
7
8
  const canvasEnabled = useNudgeUiRuntimeConfig().capabilities.canvas;
8
- return (_jsxs(_Fragment, { children: [canvasEnabled ? _jsx(CanvasWorkspace, {}) : null, canvasEnabled ? _jsx(CanvasElementOverlay, {}) : null, _jsx(InspectorShell, {})] }));
9
+ const primaryUrl = typeof window === "undefined"
10
+ ? null
11
+ : readNudgeUiEditorTarget(window.location.href);
12
+ return (_jsxs(_Fragment, { children: [canvasEnabled ? _jsx(CanvasWorkspace, { primaryUrl: primaryUrl }) : null, canvasEnabled ? _jsx(CanvasElementOverlay, {}) : null, _jsx(InspectorShell, {})] }));
9
13
  }
@@ -8,6 +8,8 @@ import { presentChange } from "../changes/presentation.js";
8
8
  import { getStructuralChanges, getStructuralChangeDiagnostics, getStructuralDiagnosticRevision, revertStructuralChange, subscribeStructuralChanges, subscribeStructuralDiagnostics, } from "../projection/structuralProjection.js";
9
9
  import { getRenderedInstanceChangeDiagnostics, getRenderedInstanceDiagnosticRevision, subscribeRenderedInstanceDiagnostics, } from "../projection/renderedInstance.js";
10
10
  import { getTextContentChangeDiagnostics, getTextProjectionDiagnosticRevision, subscribeTextProjectionDiagnostics, } from "../projection/textProjection.js";
11
+ import { SketchChanges } from "../sketch/SketchChanges.js";
12
+ import { useSketchStore } from "../sketch/store.js";
11
13
  function groupChanges(changes) {
12
14
  const map = new Map();
13
15
  for (const change of changes) {
@@ -60,6 +62,8 @@ function structuralDiagnosticText(diagnostic) {
60
62
  }
61
63
  export function ChangesLog({ onClearSession }) {
62
64
  const changes = useChanges();
65
+ const sketchStore = useSketchStore();
66
+ const sketches = sketchStore.items;
63
67
  const structuralChanges = useSyncExternalStore(subscribeStructuralChanges, getStructuralChanges, getStructuralChanges);
64
68
  // Diagnostics arrive independently from structural state when a renderer
65
69
  // resolves a snapshot or its document observer detects a reconciliation.
@@ -67,10 +71,10 @@ export function ChangesLog({ onClearSession }) {
67
71
  useSyncExternalStore(subscribeRenderedInstanceDiagnostics, getRenderedInstanceDiagnosticRevision, getRenderedInstanceDiagnosticRevision);
68
72
  useSyncExternalStore(subscribeTextProjectionDiagnostics, getTextProjectionDiagnosticRevision, getTextProjectionDiagnosticRevision);
69
73
  const groups = useMemo(() => groupChanges(changes), [changes]);
70
- const total = changes.length + structuralChanges.length;
71
- if (total === 0)
74
+ const total = changes.length + structuralChanges.length + sketches.length;
75
+ if (total === 0 && sketchStore.error === null)
72
76
  return null;
73
- return (_jsxs(_Fragment, { children: [_jsxs("details", { className: "changes", "data-test": "changes-log", children: [_jsxs("summary", { className: "changes__title", "data-test": "changes-toggle", children: [_jsx("span", { className: "changes__title-label", children: "Changes" }), total > 0 ? _jsx("span", { className: "changes__count", children: total }) : null, _jsx(IconChevronDown, { className: "changes__toggle-icon", size: 15, stroke: 2, "aria-hidden": "true" })] }), _jsx("div", { className: "changes__content", children: groups.length === 0 && structuralChanges.length === 0 ? (_jsx("div", { className: "changes__empty", "data-test": "changes-empty", children: "No changes yet" })) : (_jsxs(_Fragment, { children: [groups.map((group) => (_jsxs("div", { className: "changes__group", "data-test": "changes-group", "data-cid": group.label, children: [_jsxs("div", { className: "changes__group-title", children: [_jsx("span", { children: group.label }), _jsx("span", { className: "changes__group-file", children: group.file })] }), group.changes.map((change, i) => {
77
+ return (_jsxs(_Fragment, { children: [_jsxs("details", { className: "changes", "data-test": "changes-log", children: [_jsxs("summary", { className: "changes__title", "data-test": "changes-toggle", children: [_jsx("span", { className: "changes__title-label", children: "Changes" }), total > 0 ? _jsx("span", { className: "changes__count", children: total }) : null, _jsx(IconChevronDown, { className: "changes__toggle-icon", size: 15, stroke: 2, "aria-hidden": "true" })] }), _jsx("div", { className: "changes__content", children: groups.length === 0 && structuralChanges.length === 0 && sketches.length === 0 && sketchStore.error === null ? (_jsx("div", { className: "changes__empty", "data-test": "changes-empty", children: "No changes yet" })) : (_jsxs(_Fragment, { children: [groups.map((group) => (_jsxs("div", { className: "changes__group", "data-test": "changes-group", "data-cid": group.label, children: [_jsxs("div", { className: "changes__group-title", children: [_jsx("span", { children: group.label }), _jsx("span", { className: "changes__group-file", children: group.file })] }), group.changes.map((change, i) => {
74
78
  const presentation = presentChange(change);
75
79
  const instanceDiagnostics = isElementChange(change) && change.scope === "rendered-instance"
76
80
  && change.instanceOverride
@@ -88,5 +92,5 @@ export function ChangesLog({ onClearSession }) {
88
92
  ? structuralParentIdentityKey(change.source.parent) === structuralParentIdentityKey(change.destination.parent)
89
93
  ? `${change.presentation.destinationParentTag} position ${change.presentation.toIndex + 1}`
90
94
  : `${structuralParentLabel(change, "destination")} position ${change.presentation.toIndex + 1}`
91
- : "Removed" })] }), _jsx("span", { className: "changes__scope", "data-test": "structural-scope", children: "This rendered item only" }), getStructuralChangeDiagnostics(change.id).map((diagnostic) => (_jsx("span", { className: "changes__diagnostic", "data-test": "structural-diagnostic", "data-document": diagnostic.document, "data-status": diagnostic.status, "data-reason": diagnostic.reason, children: structuralDiagnosticText(diagnostic) }, `${diagnostic.document}:${diagnostic.status}:${diagnostic.reason ?? ""}`))), _jsx(Button, { size: "compact", className: "changes__revert", "data-test": "dom-change-revert", onClick: () => revertStructuralChange(change.id), children: "Revert" })] })] }, change.id)))] })) })] }), onClearSession ? (_jsx("div", { className: "changes__session-action", "data-test": "session-actions", children: _jsx(Button, { variant: "secondary", type: "button", "data-test": "clear-session", onClick: onClearSession, children: "Clear Changes" }) })) : null] }));
95
+ : "Removed" })] }), _jsx("span", { className: "changes__scope", "data-test": "structural-scope", children: "This rendered item only" }), getStructuralChangeDiagnostics(change.id).map((diagnostic) => (_jsx("span", { className: "changes__diagnostic", "data-test": "structural-diagnostic", "data-document": diagnostic.document, "data-status": diagnostic.status, "data-reason": diagnostic.reason, children: structuralDiagnosticText(diagnostic) }, `${diagnostic.document}:${diagnostic.status}:${diagnostic.reason ?? ""}`))), _jsx(Button, { size: "compact", className: "changes__revert", "data-test": "dom-change-revert", onClick: () => revertStructuralChange(change.id), children: "Revert" })] })] }, change.id))), _jsx(SketchChanges, {})] })) })] }), onClearSession ? (_jsx("div", { className: "changes__session-action", "data-test": "session-actions", children: _jsx(Button, { variant: "secondary", type: "button", "data-test": "clear-session", onClick: onClearSession, children: "Clear Changes" }) })) : null] }));
92
96
  }