nudge-ui 0.2.2 → 0.2.4

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 (244) hide show
  1. package/README.md +164 -46
  2. package/dist/client.mjs +260 -61
  3. package/dist/css/value-semantics/colorSemantics.d.ts +4 -0
  4. package/dist/css/value-semantics/colorSemantics.js +17 -0
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -0
  6. package/dist/hosts/astro/clientTransport.js +38 -32
  7. package/dist/hosts/astro/integration.js +5 -1
  8. package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
  9. package/dist/hosts/astro/markdownIdentity.js +81 -0
  10. package/dist/hosts/next/manifest.js +1 -1
  11. package/dist/hosts/next/sidecar.js +5 -1
  12. package/dist/hosts/static/manifest.js +1 -1
  13. package/dist/hosts/static/server.js +3 -0
  14. package/dist/hosts/vite/vite.d.ts +3 -0
  15. package/dist/hosts/vite/vite.js +24 -4
  16. package/dist/inspector/agent/McpConnectionDialog.js +13 -19
  17. package/dist/inspector/agent/activity.d.ts +18 -0
  18. package/dist/inspector/agent/activity.js +70 -0
  19. package/dist/inspector/agent/client.d.ts +2 -0
  20. package/dist/inspector/agent/client.js +29 -1
  21. package/dist/inspector/agent/connectionStatus.js +1 -1
  22. package/dist/inspector/agent/httpTransport.js +9 -2
  23. package/dist/inspector/agent/protocol.d.ts +6 -3
  24. package/dist/inspector/agent/protocol.js +1 -1
  25. package/dist/inspector/agent/verification.d.ts +15 -7
  26. package/dist/inspector/agent/verification.js +113 -41
  27. package/dist/inspector/canvas/AgentActivityOverlay.d.ts +13 -0
  28. package/dist/inspector/canvas/AgentActivityOverlay.js +111 -0
  29. package/dist/inspector/canvas/CanvasCard.d.ts +9 -2
  30. package/dist/inspector/canvas/CanvasCard.js +135 -38
  31. package/dist/inspector/canvas/CanvasElementOverlay.js +166 -16
  32. package/dist/inspector/canvas/CanvasToolIcons.d.ts +10 -0
  33. package/dist/inspector/canvas/CanvasToolIcons.js +20 -0
  34. package/dist/inspector/canvas/CanvasToolbar.js +8 -4
  35. package/dist/inspector/canvas/CanvasWorkspace.js +270 -122
  36. package/dist/inspector/canvas/StaleChangeIndicator.js +3 -13
  37. package/dist/inspector/canvas/agentPresentation.d.ts +8 -8
  38. package/dist/inspector/canvas/agentPresentation.js +64 -52
  39. package/dist/inspector/canvas/canvasGestures.d.ts +1 -1
  40. package/dist/inspector/canvas/canvasGestures.js +1 -1
  41. package/dist/inspector/canvas/canvasStore.d.ts +46 -82
  42. package/dist/inspector/canvas/canvasStore.js +251 -284
  43. package/dist/inspector/canvas/frameContent.d.ts +21 -0
  44. package/dist/inspector/canvas/frameContent.js +44 -0
  45. package/dist/inspector/canvas/frameProtocol.d.ts +21 -4
  46. package/dist/inspector/canvas/frameProtocol.js +3 -3
  47. package/dist/inspector/canvas/frameScrollbarStyles.d.ts +3 -0
  48. package/dist/inspector/canvas/frameScrollbarStyles.js +21 -0
  49. package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -1
  50. package/dist/inspector/canvas/keyboardShortcuts.js +4 -4
  51. package/dist/inspector/canvas/layout.d.ts +52 -0
  52. package/dist/inspector/canvas/layout.js +106 -0
  53. package/dist/inspector/canvas/projection.d.ts +15 -5
  54. package/dist/inspector/canvas/projection.js +69 -13
  55. package/dist/inspector/canvas/rendererBootstrap.js +32 -4
  56. package/dist/inspector/canvas/rendererElementSelector.js +47 -6
  57. package/dist/inspector/canvas/rendererStylesheet.js +2 -2
  58. package/dist/inspector/canvas/resizeHandleScale.js +1 -1
  59. package/dist/inspector/canvas/sessionStore.d.ts +11 -26
  60. package/dist/inspector/canvas/sessionStore.js +43 -183
  61. package/dist/inspector/canvas/staleChangeDetector.js +9 -3
  62. package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
  63. package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
  64. package/dist/inspector/canvas/viewStore.d.ts +38 -0
  65. package/dist/inspector/canvas/viewStore.js +104 -0
  66. package/dist/inspector/canvas/wheelPan.d.ts +8 -0
  67. package/dist/inspector/canvas/wheelPan.js +8 -0
  68. package/dist/inspector/changes/changesLog.d.ts +9 -6
  69. package/dist/inspector/changes/changesLog.js +48 -32
  70. package/dist/inspector/changes/codecs.d.ts +1 -0
  71. package/dist/inspector/changes/codecs.js +10 -1
  72. package/dist/inspector/changes/draftChanges.d.ts +58 -0
  73. package/dist/inspector/changes/draftChanges.js +304 -0
  74. package/dist/inspector/changes/editModel.d.ts +10 -0
  75. package/dist/inspector/changes/editModel.js +9 -2
  76. package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
  77. package/dist/inspector/changes/previewDiagnostics.js +11 -25
  78. package/dist/inspector/changes/types.d.ts +7 -0
  79. package/dist/inspector/clientManifest.d.ts +1 -1
  80. package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
  81. package/dist/inspector/comments/CommentsOverlay.js +250 -0
  82. package/dist/inspector/comments/element.d.ts +6 -0
  83. package/dist/inspector/comments/element.js +54 -0
  84. package/dist/inspector/comments/store.d.ts +29 -0
  85. package/dist/inspector/comments/store.js +121 -0
  86. package/dist/inspector/comments/verification.d.ts +5 -0
  87. package/dist/inspector/comments/verification.js +52 -0
  88. package/dist/inspector/componentSemantics/adapterRegistry.d.ts +6 -1
  89. package/dist/inspector/componentSemantics/adapterRegistry.js +23 -1
  90. package/dist/inspector/componentSemantics/reactRuntime.d.ts +2 -1
  91. package/dist/inspector/componentSemantics/reactRuntime.js +38 -0
  92. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +4 -2
  93. package/dist/inspector/componentSemantics/runtimeBridge.js +17 -0
  94. package/dist/inspector/componentSemantics/types.d.ts +11 -0
  95. package/dist/inspector/drafts/model.d.ts +35 -0
  96. package/dist/inspector/drafts/model.js +45 -0
  97. package/dist/inspector/drafts/store.d.ts +28 -0
  98. package/dist/inspector/drafts/store.js +231 -0
  99. package/dist/inspector/index.js +20 -27
  100. package/dist/inspector/inline-text/inlineTextEditor.js +7 -0
  101. package/dist/inspector/inspection/useBrowserCssInspection.js +15 -1
  102. package/dist/inspector/iterations/capture.d.ts +2 -0
  103. package/dist/inspector/iterations/capture.js +174 -0
  104. package/dist/inspector/iterations/client.d.ts +7 -0
  105. package/dist/inspector/iterations/client.js +52 -0
  106. package/dist/inspector/iterations/coordinator.d.ts +15 -0
  107. package/dist/inspector/iterations/coordinator.js +130 -0
  108. package/dist/inspector/iterations/prompt.d.ts +3 -0
  109. package/dist/inspector/iterations/prompt.js +26 -0
  110. package/dist/inspector/overlay/DomNavigator.js +21 -15
  111. package/dist/inspector/overlay/interactionStyles.js +3 -1
  112. package/dist/inspector/overlay/spacingGestures.d.ts +13 -1
  113. package/dist/inspector/overlay/spacingGestures.js +82 -5
  114. package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
  115. package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
  116. package/dist/inspector/projection/renderedInstance.d.ts +17 -3
  117. package/dist/inspector/projection/renderedInstance.js +62 -11
  118. package/dist/inspector/projection/structuralProjection.d.ts +2 -1
  119. package/dist/inspector/projection/structuralProjection.js +21 -17
  120. package/dist/inspector/projection/textProjection.d.ts +2 -1
  121. package/dist/inspector/projection/textProjection.js +64 -15
  122. package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
  123. package/dist/inspector/prompt/clipboardHandoff.d.ts +15 -18
  124. package/dist/inspector/prompt/clipboardHandoff.js +97 -143
  125. package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
  126. package/dist/inspector/prompt/generatePrompt.js +22 -17
  127. package/dist/inspector/runtime/runtimeConfig.d.ts +9 -0
  128. package/dist/inspector/runtime/runtimeConfig.js +24 -0
  129. package/dist/inspector/selection/sourceSite.d.ts +12 -0
  130. package/dist/inspector/selection/sourceSite.js +28 -0
  131. package/dist/inspector/settings/SettingsDialog.js +21 -8
  132. package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
  133. package/dist/inspector/shell/AgentStatusButton.js +36 -0
  134. package/dist/inspector/shell/AppShell.js +4 -2
  135. package/dist/inspector/shell/ChangesLog.js +46 -26
  136. package/dist/inspector/shell/CopyPromptButton.js +184 -93
  137. package/dist/inspector/shell/EmptyState.js +9 -8
  138. package/dist/inspector/shell/InspectorShell.js +13 -21
  139. package/dist/inspector/shell/PeekOriginalButton.js +24 -23
  140. package/dist/inspector/shell/originalPreview.js +5 -5
  141. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  142. package/dist/inspector/shell/shortcuts.js +11 -0
  143. package/dist/inspector/sketch/SketchEditor.js +14 -14
  144. package/dist/inspector/sketch/SketchEntryButton.js +1 -1
  145. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
  146. package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
  147. package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
  148. package/dist/inspector/sketch/SketchOverlay.js +7 -7
  149. package/dist/inspector/sketch/SketchWorkspace.js +18 -12
  150. package/dist/inspector/sketch/interaction.d.ts +1 -0
  151. package/dist/inspector/sketch/interaction.js +8 -4
  152. package/dist/inspector/sketch/model.d.ts +5 -0
  153. package/dist/inspector/sketch/model.js +6 -2
  154. package/dist/inspector/sketch/raster.d.ts +1 -2
  155. package/dist/inspector/sketch/raster.js +68 -4
  156. package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
  157. package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
  158. package/dist/inspector/styleEditors/BorderEditor.js +49 -78
  159. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
  160. package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
  161. package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
  162. package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
  163. package/dist/inspector/styleEditors/ColorPicker.js +10 -4
  164. package/dist/inspector/styleEditors/GapField.js +3 -5
  165. package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
  166. package/dist/inspector/styleEditors/GridChildSection.js +18 -9
  167. package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
  168. package/dist/inspector/styleEditors/GridPicker.js +8 -2
  169. package/dist/inspector/styleEditors/GridSection.js +1 -1
  170. package/dist/inspector/styleEditors/GridValueField.js +19 -14
  171. package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
  172. package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
  173. package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
  174. package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
  175. package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
  176. package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
  177. package/dist/inspector/styleEditors/LayoutSection.js +32 -11
  178. package/dist/inspector/styleEditors/SpacingBox.js +7 -5
  179. package/dist/inspector/styleEditors/Typography.js +11 -3
  180. package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
  181. package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
  182. package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
  183. package/dist/inspector/styleEditors/gradientValue.js +114 -0
  184. package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
  185. package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
  186. package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
  187. package/dist/inspector/styleEditors/layoutValue.js +0 -42
  188. package/dist/inspector/styleEditors/nudgeValue.d.ts +2 -2
  189. package/dist/inspector/styleEditors/nudgeValue.js +36 -8
  190. package/dist/inspector/tokens/TokenField.d.ts +7 -0
  191. package/dist/inspector/tokens/TokenField.js +35 -25
  192. package/dist/inspector/tokens/TokenLabel.js +5 -14
  193. package/dist/inspector/tokens/editActions.d.ts +1 -1
  194. package/dist/inspector/tokens/editActions.js +7 -1
  195. package/dist/inspector/ui/AtRuleContext.js +4 -6
  196. package/dist/inspector/ui/Button.d.ts +6 -2
  197. package/dist/inspector/ui/Button.js +4 -3
  198. package/dist/inspector/ui/ControlSurface.d.ts +2 -2
  199. package/dist/inspector/ui/ControlSurface.js +4 -3
  200. package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
  201. package/dist/inspector/ui/InlineStyleWarning.js +13 -0
  202. package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
  203. package/dist/inspector/ui/InspectorPopover.js +4 -5
  204. package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
  205. package/dist/inspector/ui/PopoverListbox.js +8 -7
  206. package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
  207. package/dist/inspector/ui/SegmentedControl.js +2 -2
  208. package/dist/inspector/ui/Select.d.ts +6 -1
  209. package/dist/inspector/ui/Select.js +7 -11
  210. package/dist/inspector/ui/SideValuesField.js +1 -1
  211. package/dist/inspector/ui/Tooltip.d.ts +26 -0
  212. package/dist/inspector/ui/Tooltip.js +32 -0
  213. package/dist/inspector/ui/classNames.d.ts +2 -0
  214. package/dist/inspector/ui/classNames.js +4 -0
  215. package/dist/inspector/ui/customCursors.d.ts +23 -0
  216. package/dist/inspector/ui/customCursors.js +122 -0
  217. package/dist/inspector/ui/dragCursor.d.ts +0 -2
  218. package/dist/inspector/ui/dragCursor.js +2 -10
  219. package/dist/inspector/ui/portalContainer.d.ts +2 -0
  220. package/dist/inspector/ui/portalContainer.js +6 -0
  221. package/dist/inspector/ui/styles.js +29 -21
  222. package/dist/inspector/workspace/commands.d.ts +9 -0
  223. package/dist/inspector/workspace/commands.js +82 -0
  224. package/dist/inspector/workspace/controller.d.ts +4 -0
  225. package/dist/inspector/workspace/controller.js +66 -0
  226. package/dist/inspector/workspace/handoff.d.ts +42 -0
  227. package/dist/inspector/workspace/handoff.js +115 -0
  228. package/dist/inspector/workspace/sentItems.d.ts +19 -0
  229. package/dist/inspector/workspace/sentItems.js +51 -0
  230. package/dist/inspector/workspace/timeline.d.ts +30 -0
  231. package/dist/inspector/workspace/timeline.js +108 -0
  232. package/dist/internal/agent-protocol.d.ts +19 -2
  233. package/dist/internal/agent-protocol.js +13 -0
  234. package/dist/nudge-ui.mjs +558 -311
  235. package/dist/project/artifacts.d.ts +13 -0
  236. package/dist/project/artifacts.js +253 -0
  237. package/dist/transport/artifacts.d.ts +8 -0
  238. package/dist/transport/artifacts.js +9 -0
  239. package/dist/transport/editor.js +3 -1
  240. package/dist/transport/routes.d.ts +2 -0
  241. package/dist/transport/routes.js +2 -0
  242. package/package.json +7 -3
  243. package/dist/inspector/changes/workspaceChanges.d.ts +0 -47
  244. package/dist/inspector/changes/workspaceChanges.js +0 -265
@@ -5,6 +5,8 @@ import { formatComponentPropBaseline, formatComponentPropValue, } from "../compo
5
5
  import { boundRuntimeEvidence, isRuntimeGeneratedSource, normalizeRuntimeTag, normalizeRuntimeText, } from "../runtime/staticHtmlRuntimeIdentity.js";
6
6
  import { getSourceCoordinatePolicy } from "../runtime/runtimeConfig.js";
7
7
  import { DEFAULT_CUSTOM_INSTRUCTIONS } from "./promptSettings.js";
8
+ import { getCommentsForTarget } from "../comments/store.js";
9
+ import { documentTarget } from "../drafts/model.js";
8
10
  const EMPTY_SENTINEL = "<!-- No changes to export -->\n\nThe changes log is empty. Make a change in the Nudge UI inspector first.";
9
11
  function renderPrompt(sections, customInstructions) {
10
12
  const lines = [
@@ -230,7 +232,10 @@ function renderedInstanceDescription(ref) {
230
232
  function structuralChangeLine(change) {
231
233
  const target = renderedInstanceDescription(change.target);
232
234
  if (change.kind === "delete") {
233
- return `- Remove ${target} from the source.`;
235
+ const invocation = change.target.locator.invocation;
236
+ return invocation
237
+ ? `- Remove the ${promptText(`<${invocation.componentName}>`)} invocation at ${invocation.callsiteId} from the source; keep its other invocations. It renders ${target}.`
238
+ : `- Remove ${target} from the source.`;
234
239
  }
235
240
  const before = change.destination.before;
236
241
  const sourceParent = sourceSiteLabel(change.source.parent);
@@ -244,17 +249,6 @@ function structuralChangeLine(change) {
244
249
  ? `- Move ${target} before ${renderedInstanceDescription(before)} in ${destinationParent}.`
245
250
  : `- Move ${target} to the end of ${destinationParent}.`;
246
251
  }
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
- }
258
252
  function renderedInstanceKey(ref) {
259
253
  const { sourceSite, locator } = ref;
260
254
  return JSON.stringify([
@@ -263,6 +257,7 @@ function renderedInstanceKey(ref) {
263
257
  locator.props,
264
258
  locator.text,
265
259
  locator.ariaLabel ?? null,
260
+ locator.invocation?.callsiteId ?? null,
266
261
  ]);
267
262
  }
268
263
  /** Export final intent, not the gesture history required to replay the preview. */
@@ -315,10 +310,14 @@ function canonicalizeStructuralChanges(changes) {
315
310
  }
316
311
  return canonical;
317
312
  }
318
- export function generatePrompt(changes, frameworkHints, structuralChanges = [], customInstructions = DEFAULT_CUSTOM_INSTRUCTIONS, sketches = []) {
313
+ /** Counts the final edit intents exported by the prompt, preserving gesture history for undo. */
314
+ export function countPromptChanges(changes, structuralChanges = [], sketchCount = 0) {
315
+ return canonicalizeChanges([...changes]).length + canonicalizeStructuralChanges(structuralChanges).length + sketchCount;
316
+ }
317
+ export function generatePrompt(changes, frameworkHints, structuralChanges = [], customInstructions = DEFAULT_CUSTOM_INSTRUCTIONS, sketches = [], comments = getCommentsForTarget(documentTarget(window.location.href))) {
319
318
  const deduplicated = canonicalizeChanges(changes);
320
319
  const structuralIntent = canonicalizeStructuralChanges(structuralChanges);
321
- if (deduplicated.length === 0 && structuralIntent.length === 0 && sketches.length === 0)
320
+ if (deduplicated.length === 0 && structuralIntent.length === 0 && sketches.length === 0 && comments.length === 0)
322
321
  return EMPTY_SENTINEL;
323
322
  const tokenChanges = deduplicated.filter(isTokenChange);
324
323
  const componentChanges = deduplicated.filter(isComponentChange);
@@ -398,10 +397,16 @@ export function generatePrompt(changes, frameworkHints, structuralChanges = [],
398
397
  lines: structuralIntent.map(structuralChangeLine),
399
398
  });
400
399
  }
401
- if (sketches.length > 0) {
400
+ if (comments.length > 0) {
402
401
  sections.push({
403
- heading: "Sketch annotations",
404
- lines: sketchLines(sketches),
402
+ heading: "Element comments",
403
+ lines: comments.flatMap((comment) => [
404
+ `### <${comment.tag}> (${comment.target.sourceSite.src || "source unknown"})`,
405
+ `- Page: ${promptText(comment.route)}`,
406
+ `- Target: ${renderedInstanceDescription(comment.target)}`,
407
+ `- Note: ${promptText(comment.note)}`,
408
+ "",
409
+ ]),
405
410
  });
406
411
  }
407
412
  return renderPrompt(sections, customInstructions);
@@ -39,6 +39,11 @@ export interface NudgeUiRuntimeCapabilities {
39
39
  */
40
40
  readonly scopingSelectorPattern?: string;
41
41
  }
42
+ /** Product features that a host can selectively expose in its inspector UI. */
43
+ export interface NudgeUiRuntimeFeatures {
44
+ /** Whether Canvas can create filesystem-backed HTML iterations. */
45
+ readonly canvasIterations?: boolean;
46
+ }
42
47
  /**
43
48
  * Browser knowledge supplied by a host Adapter before the inspector mounts.
44
49
  *
@@ -53,6 +58,8 @@ export interface NudgeUiRuntimeConfig {
53
58
  readonly framework: NudgeUiRuntimeFramework;
54
59
  readonly stylingSystem: string;
55
60
  readonly capabilities: NudgeUiRuntimeCapabilities;
61
+ /** Product-level feature switches; omitted features remain enabled. */
62
+ readonly features?: NudgeUiRuntimeFeatures;
56
63
  readonly tokenCatalog: readonly TokenDefinition[];
57
64
  readonly tokens: readonly TokenEntry[];
58
65
  readonly tokenDiagnostics: readonly TokenCatalogDiagnostic[];
@@ -91,6 +98,8 @@ export declare function normalizeNudgeUiRuntimeConfig(input: unknown): NudgeUiRu
91
98
  export declare function configureNudgeUiRuntime(config: NudgeUiRuntimeConfig): void;
92
99
  /** Returns the immutable runtime snapshot used by shared inspector Modules. */
93
100
  export declare function getNudgeUiRuntimeConfig(): NudgeUiRuntimeConfig;
101
+ /** Returns whether a product feature is enabled; omitted flags default to enabled. */
102
+ export declare function isNudgeUiFeatureEnabled(features: NudgeUiRuntimeFeatures | undefined, feature: keyof NudgeUiRuntimeFeatures): boolean;
94
103
  /**
95
104
  * Whether this document runs the explicit public landing demo (ADR-0014 and
96
105
  * ADR-0016). The plugin emits `demo: true` for the flagged demo route and for
@@ -98,6 +98,15 @@ function optionalBoolean(input, field) {
98
98
  }
99
99
  return value;
100
100
  }
101
+ function optionalFeatureBoolean(input, field) {
102
+ const value = input[field];
103
+ if (value === undefined)
104
+ return undefined;
105
+ if (typeof value !== "boolean") {
106
+ throw new TypeError(`Nudge UI runtime feature "${field}" must be a boolean; received ${JSON.stringify(value)}.`);
107
+ }
108
+ return value;
109
+ }
101
110
  function optionalNonEmptyString(input, field) {
102
111
  const value = input[field];
103
112
  if (value === undefined)
@@ -142,6 +151,15 @@ function normalizeCapabilities(input) {
142
151
  scopingSelectorPattern: optionalNonEmptyString(input, "scopingSelectorPattern"),
143
152
  };
144
153
  }
154
+ function normalizeFeatures(input) {
155
+ if (input === undefined)
156
+ return undefined;
157
+ if (!isPlainRecord(input)) {
158
+ throw new TypeError('Nudge UI runtime configuration field "features" must be an object.');
159
+ }
160
+ const canvasIterations = optionalFeatureBoolean(input, "canvasIterations");
161
+ return canvasIterations === undefined ? {} : { canvasIterations };
162
+ }
145
163
  function optionalDemoFlag(input) {
146
164
  const value = input.demo;
147
165
  if (value === undefined || value === false)
@@ -172,12 +190,14 @@ export function normalizeNudgeUiRuntimeConfig(input) {
172
190
  const demo = optionalDemoFlag(input);
173
191
  const demoPages = optionalStringArray(input, "demoPages");
174
192
  const demoCardLabels = optionalStringArray(input, "demoCardLabels");
193
+ const features = normalizeFeatures(input.features);
175
194
  const normalized = cloneAndFreeze({
176
195
  projectId: requireString(input, "projectId"),
177
196
  host: requireEnum(input, "host", RUNTIME_HOSTS),
178
197
  framework: requireEnum(input, "framework", RUNTIME_FRAMEWORKS),
179
198
  stylingSystem: optionalString(input, "stylingSystem"),
180
199
  capabilities: normalizeCapabilities(input.capabilities),
200
+ ...(features === undefined ? {} : { features }),
181
201
  tokenCatalog: optionalArray(input, "tokenCatalog"),
182
202
  tokens: optionalArray(input, "tokens"),
183
203
  tokenDiagnostics: optionalArray(input, "tokenDiagnostics"),
@@ -217,6 +237,10 @@ export function configureNudgeUiRuntime(config) {
217
237
  export function getNudgeUiRuntimeConfig() {
218
238
  return activeRuntimeConfig;
219
239
  }
240
+ /** Returns whether a product feature is enabled; omitted flags default to enabled. */
241
+ export function isNudgeUiFeatureEnabled(features, feature) {
242
+ return features?.[feature] !== false;
243
+ }
220
244
  /**
221
245
  * Whether this document runs the explicit public landing demo (ADR-0014 and
222
246
  * ADR-0016). The plugin emits `demo: true` for the flagged demo route and for
@@ -1,2 +1,14 @@
1
1
  /** Stable JSX instrumentation selector, shared by source and instance edits. */
2
2
  export declare function sourceSiteSelector(cid: string, src: string): string | null;
3
+ /**
4
+ * Finds a source site whose position moved because its file gained or lost
5
+ * lines. It returns a site only when the original renders nothing and exactly
6
+ * one other position in the same file and component renders matching elements.
7
+ */
8
+ export declare function relocateSourceSite(doc: Document, site: {
9
+ readonly cid: string;
10
+ readonly src: string;
11
+ }, matches: (element: HTMLElement) => boolean): {
12
+ cid: string;
13
+ src: string;
14
+ } | null;
@@ -1,4 +1,5 @@
1
1
  import { escapeAttrValue } from "../projection/cssEscapes.js";
2
+ const SOURCE_POSITION = /^(.+):\d+:\d+$/;
2
3
  /** Stable JSX instrumentation selector, shared by source and instance edits. */
3
4
  export function sourceSiteSelector(cid, src) {
4
5
  if (!cid)
@@ -10,3 +11,30 @@ export function sourceSiteSelector(cid, src) {
10
11
  // single formatted line share a managed rule.
11
12
  return `[data-cid="${escapeAttrValue(cid)}"][data-src="${escapeAttrValue(src)}"]`;
12
13
  }
14
+ /**
15
+ * Finds a source site whose position moved because its file gained or lost
16
+ * lines. It returns a site only when the original renders nothing and exactly
17
+ * one other position in the same file and component renders matching elements.
18
+ */
19
+ export function relocateSourceSite(doc, site, matches) {
20
+ const file = SOURCE_POSITION.exec(site.src)?.[1];
21
+ const exact = sourceSiteSelector(site.cid, site.src);
22
+ if (!file || !exact)
23
+ return null;
24
+ try {
25
+ if (doc.querySelector(exact))
26
+ return null;
27
+ const sites = new Set();
28
+ const sameFile = `[data-cid="${escapeAttrValue(site.cid)}"][data-src^="${escapeAttrValue(`${file}:`)}"]`;
29
+ for (const element of doc.querySelectorAll(sameFile)) {
30
+ const src = element.getAttribute("data-src") ?? "";
31
+ if (SOURCE_POSITION.exec(src)?.[1] === file && matches(element))
32
+ sites.add(src);
33
+ }
34
+ const [src] = sites;
35
+ return sites.size === 1 && src ? { cid: site.cid, src } : null;
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from "react";
3
3
  import { Dialog } from "@base-ui/react/dialog";
4
- import { IconColorSwatch, IconPlugConnected, IconSettings, IconX, } from "@tabler/icons-react";
4
+ import { IconColorSwatch, IconPlugConnected, IconSettings, IconX, IconTrash, } from "@tabler/icons-react";
5
5
  import { useBrowserCssInspection } from "../inspection/useBrowserCssInspection.js";
6
6
  import { McpConnectionContent, } from "../agent/McpConnectionDialog.js";
7
7
  import { PromptSettingsFields, } from "../prompt/PromptSettingsDialog.js";
@@ -10,10 +10,17 @@ import { useSelectedElement } from "../selection/selectionStore.js";
10
10
  import { useFocusedCardId, useSelectedCardId } from "../canvas/canvasStore.js";
11
11
  import { getRegisteredFrames } from "../canvas/projection.js";
12
12
  import { getBrowserCssInspection } from "../inspection/browserCssInspectionRegistry.js";
13
+ import { portalContainer } from "../ui/portalContainer.js";
14
+ import { clearRestoreCount, clearSession } from "../canvas/sessionStore.js";
15
+ import { hasWriteLease } from "../canvas/workspaceLease.js";
16
+ import { cancelSketchInteraction } from "../sketch/interaction.js";
17
+ import { closeSketchNote } from "../sketch/sketchNote.js";
18
+ import { clearSketchClipboardHandoff } from "../sketch/handoff.js";
19
+ import { clearSketchesForProject } from "../sketch/store.js";
13
20
  const SETTINGS_SECTIONS = [
14
21
  {
15
22
  id: "instructions",
16
- label: "Custom instructions",
23
+ label: "Instructions",
17
24
  icon: _jsx(IconSettings, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }),
18
25
  },
19
26
  {
@@ -27,11 +34,6 @@ const SETTINGS_SECTIONS = [
27
34
  icon: _jsx(IconColorSwatch, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }),
28
35
  },
29
36
  ];
30
- function portalContainer() {
31
- return typeof document !== "undefined"
32
- ? document.getElementById("nudge-ui-root")?.shadowRoot ?? document.body
33
- : null;
34
- }
35
37
  /** Provides one settings surface for prompt, MCP, and token configuration. */
36
38
  export function SettingsDialog({ open, initialSection, value, onValueChange, onOpenChange, projectId, origin, snapshot, onConnect, onTakeOver, onDisconnect, onCheckAgain, }) {
37
39
  const [activeSection, setActiveSection] = useState(initialSection);
@@ -52,5 +54,16 @@ export function SettingsDialog({ open, initialSection, value, onValueChange, onO
52
54
  if (open)
53
55
  setActiveSection(initialSection);
54
56
  }, [initialSection, open]);
55
- return (_jsx(Dialog.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(Dialog.Portal, { container: portalContainer(), children: [_jsx(Dialog.Backdrop, { className: "settings__backdrop", "data-test": "settings-backdrop" }), _jsxs(Dialog.Popup, { className: "settings__popup", "data-test": "settings-dialog", initialFocus: closeRef, children: [_jsx(Dialog.Title, { className: "settings__visually-hidden", children: "Settings" }), _jsx(Dialog.Description, { className: "settings__visually-hidden", children: "Configure how Nudge connects to your coding agent and uses your design system." }), _jsx(Dialog.Close, { ref: closeRef, className: "icon-button icon-button--quiet settings__close", "aria-label": "Close settings", "data-test": "settings-close", type: "button", children: _jsx(IconX, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) }), _jsxs("div", { className: "settings__body", children: [_jsxs("nav", { className: "settings__nav", "aria-label": "Settings sections", children: [_jsx("span", { className: "settings__nav-label", children: "Settings" }), _jsx("div", { className: "settings__nav-items", children: SETTINGS_SECTIONS.map((section) => (_jsxs("button", { className: `settings__nav-item${activeSection === section.id ? " settings__nav-item--active" : ""}`, "data-test": `settings-nav-${section.id}`, type: "button", "aria-current": activeSection === section.id ? "page" : undefined, onClick: () => setActiveSection(section.id), children: [section.icon, _jsx("span", { className: "settings__nav-item-label", children: section.label })] }, section.id))) })] }), _jsxs("main", { className: "settings__content", "data-test": "settings-content", children: [activeSection === "instructions" ? (_jsxs("section", { className: "settings__section", "data-test": "prompt-settings-dialog", "data-settings-section": "instructions", children: [_jsx("h2", { className: "settings__section-title", children: activeDefinition.label }), _jsx("p", { className: "settings__section-description", children: "These instructions are added to the end of every copied prompt." }), _jsx(PromptSettingsFields, { value: value, onChange: onValueChange, instructionsRef: instructionsRef })] })) : null, activeSection === "mcp" ? (_jsxs("section", { className: "settings__section", "data-test": "settings-section-mcp", children: [_jsx("h2", { className: "settings__section-title", children: activeDefinition.label }), _jsx("div", { "data-test": "mcp-connection-dialog", children: _jsx(McpConnectionContent, { projectId: projectId, origin: origin, snapshot: snapshot, onConnect: onConnect, onTakeOver: onTakeOver, onDisconnect: onDisconnect, onCheckAgain: onCheckAgain }) })] })) : null, activeSection === "tokens" ? (_jsxs("section", { className: "settings__section", "data-test": "settings-section-tokens", children: [_jsx("h2", { className: "settings__section-title", children: activeDefinition.label }), _jsx("p", { className: "settings__section-description", children: "Inspect and edit the design tokens available in this page." }), _jsx(TokensPanel, { rows: tokenRows })] })) : null] })] })] })] }) }));
57
+ function resetCanvas() {
58
+ if (!hasWriteLease())
59
+ return;
60
+ cancelSketchInteraction();
61
+ closeSketchNote();
62
+ clearSketchClipboardHandoff();
63
+ clearSession();
64
+ clearRestoreCount();
65
+ void clearSketchesForProject(projectId).catch(() => undefined);
66
+ onOpenChange(false);
67
+ }
68
+ return (_jsx(Dialog.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(Dialog.Portal, { container: portalContainer(), children: [_jsx(Dialog.Backdrop, { className: "settings__backdrop", "data-test": "settings-backdrop" }), _jsxs(Dialog.Popup, { className: "settings__popup", "data-test": "settings-dialog", initialFocus: closeRef, children: [_jsx(Dialog.Title, { className: "settings__visually-hidden", children: "Settings" }), _jsx(Dialog.Description, { className: "settings__visually-hidden", children: "Configure how Nudge connects to your coding agent and uses your design system." }), _jsx(Dialog.Close, { ref: closeRef, className: "icon-button icon-button--quiet settings__close", "aria-label": "Close settings", "data-test": "settings-close", type: "button", children: _jsx(IconX, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) }), _jsxs("div", { className: "settings__body", children: [_jsxs("nav", { className: "settings__nav", "aria-label": "Settings sections", children: [_jsx("span", { className: "settings__nav-label", children: "Settings" }), _jsx("div", { className: "settings__nav-items", children: SETTINGS_SECTIONS.map((section) => (_jsxs("button", { className: `settings__nav-item${activeSection === section.id ? " settings__nav-item--active" : ""}`, "data-test": `settings-nav-${section.id}`, type: "button", "aria-current": activeSection === section.id ? "page" : undefined, onClick: () => setActiveSection(section.id), children: [section.icon, _jsx("span", { className: "settings__nav-item-label", children: section.label })] }, section.id))) }), _jsxs("button", { className: "settings__nav-item settings__reset-canvas", "data-test": "settings-reset-canvas", type: "button", onClick: resetCanvas, children: [_jsx(IconTrash, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), _jsx("span", { className: "settings__nav-item-label", children: "Reset canvas" })] })] }), _jsxs("main", { className: "settings__content", "data-test": "settings-content", children: [activeSection === "instructions" ? (_jsxs("section", { className: "settings__section", "data-test": "prompt-settings-dialog", "data-settings-section": "instructions", children: [_jsx("h2", { className: "settings__section-title", children: "Custom instructions" }), _jsx("p", { className: "settings__section-description", children: "These instructions are added to the end of every copied prompt." }), _jsx(PromptSettingsFields, { value: value, onChange: onValueChange, instructionsRef: instructionsRef })] })) : null, activeSection === "mcp" ? (_jsxs("section", { className: "settings__section", "data-test": "settings-section-mcp", children: [_jsx("h2", { className: "settings__section-title", children: activeDefinition.label }), _jsx("div", { "data-test": "mcp-connection-dialog", children: _jsx(McpConnectionContent, { projectId: projectId, origin: origin, snapshot: snapshot, onConnect: onConnect, onTakeOver: onTakeOver, onDisconnect: onDisconnect, onCheckAgain: onCheckAgain }) })] })) : null, activeSection === "tokens" ? (_jsxs("section", { className: "settings__section", "data-test": "settings-section-tokens", children: [_jsx("h2", { className: "settings__section-title", children: activeDefinition.label }), _jsx("p", { className: "settings__section-description", children: "Inspect and edit the design tokens available in this page." }), _jsx(TokensPanel, { rows: tokenRows })] })) : null] })] })] })] }) }));
56
69
  }
@@ -0,0 +1,11 @@
1
+ import type { ReactElement } from "react";
2
+ import { type AgentClientSnapshot } from "../agent/client.js";
3
+ import type { SettingsSection } from "../settings/SettingsDialog.js";
4
+ export interface AgentStatusButtonProps {
5
+ onOpenSettings: (section: SettingsSection) => void;
6
+ }
7
+ export declare function AgentStatusButton({ onOpenSettings }: AgentStatusButtonProps): ReactElement;
8
+ /** Renders the connection snapshot provided by the project client. */
9
+ export declare function AgentStatusButtonView({ agent, onOpenSettings }: AgentStatusButtonProps & {
10
+ agent: AgentClientSnapshot;
11
+ }): ReactElement;
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { IconRobot } from "@tabler/icons-react";
3
+ import { useAgentClient } from "../agent/client.js";
4
+ import { getAgentConnectionStatus } from "../agent/connectionStatus.js";
5
+ import { useNudgeUiRuntimeConfig } from "../runtime/useRuntimeConfig.js";
6
+ import { Button } from "../ui/Button.js";
7
+ import { Tooltip } from "../ui/Tooltip.js";
8
+ /** Renders a known MCP client logo, or a generic robot avatar. */
9
+ function ConnectedAgentAvatar({ name }) {
10
+ const harness = name?.toLowerCase().match(/(?:^|[^a-z])(codex|claude|cursor)(?:$|[^a-z])/)?.[1];
11
+ return (_jsx("span", { className: "agent-status-button__avatars", role: "img", "aria-label": harness === "codex" ? "Codex" : harness === "claude" ? "Claude" : harness === "cursor" ? "Cursor" : "Agent", children: harness === "codex" ? _jsx(CodexAvatar, {})
12
+ : harness === "claude" ? _jsx(ClaudeAvatar, {})
13
+ : harness === "cursor" ? _jsx(CursorAvatar, {})
14
+ : _jsx("span", { className: "agent-status-button__avatar", children: _jsx(IconRobot, { size: 24, color: "#000", "aria-hidden": "true" }) }) }));
15
+ }
16
+ function CodexAvatar() {
17
+ return (_jsx("span", { className: "agent-status-button__avatar", children: _jsx("svg", { viewBox: "0 0 24 24", width: "24", height: "24", fill: "none", children: _jsx("path", { fill: "#000", d: "M20.686 10.184a4.92 4.92 0 0 0-.435-4.092 5.1 5.1 0 0 0-2.35-2.093 5.17 5.17 0 0 0-3.15-.324A5.13 5.13 0 0 0 12.53 2.25a5.2 5.2 0 0 0-2.648-.14 5.15 5.15 0 0 0-2.364 1.185 5.05 5.05 0 0 0-1.447 2.19 5.1 5.1 0 0 0-1.953.85A5 5 0 0 0 2.693 7.9a4.98 4.98 0 0 0 .627 5.914 4.92 4.92 0 0 0 .432 4.092A5.1 5.1 0 0 0 6.104 20c.99.435 2.093.548 3.151.324.478.53 1.065.955 1.722 1.243a5.1 5.1 0 0 0 2.087.433 5.16 5.16 0 0 0 3.018-.968 5.05 5.05 0 0 0 1.858-2.537 5.1 5.1 0 0 0 1.953-.849 5 5 0 0 0 1.425-1.568 4.99 4.99 0 0 0-.632-5.894m-7.622 10.507a3.8 3.8 0 0 1-2.43-.867l.12-.067 4.037-2.299a.67.67 0 0 0 .332-.568v-5.614l1.706.974a.06.06 0 0 1 .032.043v4.653a3.73 3.73 0 0 1-1.114 2.646 3.83 3.83 0 0 1-2.683 1.099m-8.161-3.438a3.68 3.68 0 0 1-.452-2.511l.12.07 4.041 2.3a.66.66 0 0 0 .66 0l4.935-2.808v1.944a.07.07 0 0 1-.027.051l-4.09 2.326a3.85 3.85 0 0 1-2.88.373 3.8 3.8 0 0 1-2.307-1.745M3.84 8.58a3.78 3.78 0 0 1 1.998-1.644v4.73a.63.63 0 0 0 .328.564l4.912 2.796-1.707.973a.07.07 0 0 1-.06 0l-4.08-2.322a3.76 3.76 0 0 1-1.758-2.267 3.7 3.7 0 0 1 .367-2.83m14.02 3.213L12.934 8.97 14.635 8a.07.07 0 0 1 .06 0l4.08 2.326a3.77 3.77 0 0 1 1.467 1.508 3.7 3.7 0 0 1-.338 3.999 3.8 3.8 0 0 1-1.7 1.246v-4.73a.65.65 0 0 0-.343-.556m1.7-2.52-.12-.07-4.034-2.319a.66.66 0 0 0-.663 0L9.811 9.691V7.748a.05.05 0 0 1 .024-.051l4.08-2.323a3.84 3.84 0 0 1 4.073.174 3.76 3.76 0 0 0 1.332 1.63c.279.657.361 1.378.24 2.08zM8.88 12.72l-1.707-.97a.07.07 0 0 1-.032-.047v-4.64c.001-.712.207-1.409.595-2.01s.94-1.079 1.594-1.38a3.85 3.85 0 0 1 4.042.512l-.12.067-4.036 2.298a.67.67 0 0 0-.332.568zm.927-1.971 2.198-1.25 2.202 1.25v2.5l-2.194 1.25-2.202-1.25z" }) }) }));
18
+ }
19
+ function ClaudeAvatar() {
20
+ return (_jsx("span", { className: "agent-status-button__avatar", children: _jsx("svg", { viewBox: "0 0 24 24", width: "24", height: "24", fill: "none", children: _jsx("path", { fill: "#e8704e", d: "m5.924 15.296 3.934-2.206.066-.192-.066-.106h-.192l-.658-.04-2.248-.061-1.95-.081-1.888-.101-.476-.102L2 11.82l.046-.293.4-.268.572.05 1.266.086 1.898.132 1.377.08 2.041.213h.324l.046-.131-.112-.081-.086-.081-1.964-1.33L5.68 8.79l-1.114-.81-.602-.41-.304-.384-.132-.84.547-.602.734.05.188.051.744.572 1.59 1.23 2.076 1.527.304.253.121-.086.015-.06-.137-.228-1.129-2.04L7.377 4.94l-.536-.86-.142-.517a2.5 2.5 0 0 1-.086-.607l.623-.845L7.58 2l.83.111.35.304.516 1.179.835 1.857 1.297 2.524.38.749.202.693.076.213h.131v-.122l.107-1.421.197-1.746.193-2.246.065-.633.314-.759.623-.41.486.233.4.572-.055.37-.238 1.542-.466 2.419-.304 1.619h.177l.203-.203.82-1.087 1.377-1.72.608-.684.709-.753.455-.36h.861l.633.941-.284.972-.886 1.123-.734.951-1.053 1.417-.658 1.133.06.091.158-.015 2.38-.506 1.285-.233 1.535-.263.693.324.076.329-.273.673-1.64.405-1.925.384-2.866.678-.035.025.04.051 1.292.121.551.03h1.352l2.517.188.658.435.395.531-.066.405-1.012.516-1.368-.324-3.19-.759-1.093-.273h-.152v.091l.911.89 1.671 1.508 2.091 1.943.107.48-.269.38-.283-.04-1.838-1.381-.709-.623-1.605-1.35h-.106v.141l.37.542 1.954 2.934.1.9-.14.294-.507.177-.557-.101-1.144-1.604-1.18-1.806-.952-1.62-.117.067-.562 6.046-.263.308-.607.233-.507-.384-.268-.623.268-1.23.324-1.603.264-1.275.238-1.584.141-.526-.01-.035-.116.015-1.195 1.64-1.818 2.453-1.438 1.538-.344.137-.598-.309.056-.551.334-.491 1.99-2.53 1.2-1.568.775-.906-.005-.132h-.046l-5.286 3.43-.942.122-.405-.38.05-.622.193-.202 1.59-1.093z" }) }) }));
21
+ }
22
+ function CursorAvatar() {
23
+ return (_jsx("span", { className: "agent-status-button__avatar", children: _jsx("svg", { viewBox: "0 0 24 24", width: "24", height: "24", fill: "none", children: _jsx("path", { fill: "#000", d: "M11.576 2.11 3.359 6.731a.7.7 0 0 0-.262.256.7.7 0 0 0-.097.349v9.323c0 .25.138.48.359.604l8.214 4.625a.87.87 0 0 0 .853 0l8.215-4.625a.7.7 0 0 0 .262-.255.7.7 0 0 0-.097-.349V7.337a.7.7 0 0 0-.096-.35.7.7 0 0 0-.263-.255l-8.216-4.623a.88.88 0 0 0-.851 0m-7.56 5.173H19.87c.225 0 .367.24.254.43l-7.928 13.387c-.053.089-.196.053-.196-.05v-8.77a.48.48 0 0 0-.252-.425l-7.787-4.38c-.094-.053-.055-.192.052-.192" }) }) }));
24
+ }
25
+ export function AgentStatusButton({ onOpenSettings }) {
26
+ const runtimeConfig = useNudgeUiRuntimeConfig();
27
+ const agent = useAgentClient(runtimeConfig.projectId);
28
+ return _jsx(AgentStatusButtonView, { agent: agent, onOpenSettings: onOpenSettings });
29
+ }
30
+ /** Renders the connection snapshot provided by the project client. */
31
+ export function AgentStatusButtonView({ agent, onOpenSettings }) {
32
+ const status = getAgentConnectionStatus(agent);
33
+ const connected = agent.paired || agent.pairedElsewhere;
34
+ const listening = connected && agent.listenerActive;
35
+ return (_jsx(Tooltip, { content: connected ? status.label : "Agent & MCP settings", stableTrigger: true, children: _jsxs(Button, { variant: "quiet", className: "agent-status-button", "aria-label": connected ? `MCP · ${status.label}` : "Settings", "data-test": "settings-button", "data-mcp-connected": connected ? "true" : "false", "data-mcp-listening": listening ? "true" : "false", onClick: () => onOpenSettings("mcp"), children: [connected ? _jsx(ConnectedAgentAvatar, { name: agent.agentClientName }) : _jsxs("span", { className: "agent-status-button__avatars", children: [_jsx(CodexAvatar, {}), _jsx(ClaudeAvatar, {}), _jsx(CursorAvatar, {})] }), _jsx("span", { className: "agent-status-button__label", children: connected ? "MCP" : "Settings" }), connected ? (_jsx("span", { className: "agent-status-button__dot", "data-test": "agent-status-dot", "aria-hidden": "true" })) : null] }) }));
36
+ }
@@ -1,13 +1,15 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  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
6
  import { readNudgeUiEditorTarget } from "../../transport/editor.js";
7
+ import { TooltipGroup } from "../ui/Tooltip.js";
8
+ import { CommentsOverlay } from "../comments/CommentsOverlay.js";
7
9
  export function AppShell() {
8
10
  const canvasEnabled = useNudgeUiRuntimeConfig().capabilities.canvas;
9
11
  const primaryUrl = typeof window === "undefined"
10
12
  ? null
11
13
  : readNudgeUiEditorTarget(window.location.href);
12
- return (_jsxs(_Fragment, { children: [canvasEnabled ? _jsx(CanvasWorkspace, { primaryUrl: primaryUrl }) : null, canvasEnabled ? _jsx(CanvasElementOverlay, {}) : null, _jsx(InspectorShell, {})] }));
14
+ return (_jsxs(TooltipGroup, { children: [canvasEnabled ? _jsx(CanvasWorkspace, { primaryUrl: primaryUrl }) : null, canvasEnabled ? _jsx(CanvasElementOverlay, {}) : null, canvasEnabled ? _jsx(CommentsOverlay, {}) : null, _jsx(InspectorShell, {})] }));
13
15
  }
@@ -1,15 +1,20 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useMemo, useSyncExternalStore } from "react";
3
- import { isElementChange, useChanges, revertChange } from "../changes/changesLog.js";
3
+ import { IconArrowBackUp } from "@tabler/icons-react";
4
+ import { isComponentChange, isElementChange, isTextContentChange, isTokenChange, useChanges, revertChange, } from "../changes/changesLog.js";
4
5
  import { StaleChangeIndicator } from "../canvas/StaleChangeIndicator.js";
5
6
  import { Button } from "../ui/Button.js";
6
7
  import { Disclosure } from "../ui/Disclosure.js";
8
+ import { IconButton } from "../ui/IconButton.js";
9
+ import { Tooltip } from "../ui/Tooltip.js";
7
10
  import { presentChange } from "../changes/presentation.js";
8
11
  import { getStructuralChanges, getStructuralChangeDiagnostics, getStructuralDiagnosticRevision, revertStructuralChange, subscribeStructuralChanges, subscribeStructuralDiagnostics, } from "../projection/structuralProjection.js";
9
12
  import { getRenderedInstanceChangeDiagnostics, getRenderedInstanceDiagnosticRevision, subscribeRenderedInstanceDiagnostics, } from "../projection/renderedInstance.js";
10
13
  import { getTextContentChangeDiagnostics, getTextProjectionDiagnosticRevision, subscribeTextProjectionDiagnostics, } from "../projection/textProjection.js";
11
14
  import { SketchChanges } from "../sketch/SketchChanges.js";
12
15
  import { useSketchStore } from "../sketch/store.js";
16
+ import { useFrameComments } from "../comments/store.js";
17
+ import { countPromptChanges } from "../prompt/generatePrompt.js";
13
18
  function groupChanges(changes) {
14
19
  const map = new Map();
15
20
  for (const change of changes) {
@@ -20,7 +25,6 @@ function groupChanges(changes) {
20
25
  group = {
21
26
  key,
22
27
  label: presentation.groupLabel,
23
- file: presentation.file,
24
28
  changes: [],
25
29
  };
26
30
  map.set(key, group);
@@ -29,9 +33,6 @@ function groupChanges(changes) {
29
33
  }
30
34
  return Array.from(map.values());
31
35
  }
32
- function sourceFile(change) {
33
- return change.target.sourceSite.src.split(":").slice(0, -2).join(":") || change.target.sourceSite.src;
34
- }
35
36
  function structuralParentLabel(change, side) {
36
37
  const parent = side === "source" ? change.source.parent : change.destination.parent;
37
38
  const tag = side === "source" ? change.presentation.sourceParentTag : change.presentation.destinationParentTag;
@@ -60,8 +61,26 @@ function structuralReasonLabel(reason) {
60
61
  function structuralDiagnosticText(diagnostic) {
61
62
  return `${diagnostic.document}: ${diagnostic.status}${diagnostic.reason ? ` (${structuralReasonLabel(diagnostic.reason)})` : ""}`;
62
63
  }
64
+ function changeTargetLabel(change, presentation) {
65
+ if (isComponentChange(change))
66
+ return change.target.componentName;
67
+ if (isTextContentChange(change))
68
+ return change.target.sourceSite.cid;
69
+ if (isTokenChange(change))
70
+ return change.tokenName;
71
+ return isElementChange(change) ? change.cid : presentation.groupLabel;
72
+ }
73
+ function changeActionLabel(change, presentation) {
74
+ if (isTextContentChange(change))
75
+ return "Changed text";
76
+ return `Changed ${presentation.propertyLabel}`;
77
+ }
78
+ function RevertButton({ dataTest, dataProperty, onClick, }) {
79
+ return (_jsx(Tooltip, { content: "Revert", stableTrigger: true, children: _jsx(IconButton, { variant: "quiet", size: "compact", className: "changes__revert", "data-test": dataTest, "data-property": dataProperty, label: "Revert change", onClick: onClick, children: _jsx(IconArrowBackUp, { size: "var(--icon-size-small)", stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }) }) }));
80
+ }
63
81
  export function ChangesLog({ onClearSession }) {
64
82
  const changes = useChanges();
83
+ const comments = useFrameComments();
65
84
  const sketchStore = useSketchStore();
66
85
  const sketches = sketchStore.items;
67
86
  const structuralChanges = useSyncExternalStore(subscribeStructuralChanges, getStructuralChanges, getStructuralChanges);
@@ -71,26 +90,27 @@ export function ChangesLog({ onClearSession }) {
71
90
  useSyncExternalStore(subscribeRenderedInstanceDiagnostics, getRenderedInstanceDiagnosticRevision, getRenderedInstanceDiagnosticRevision);
72
91
  useSyncExternalStore(subscribeTextProjectionDiagnostics, getTextProjectionDiagnosticRevision, getTextProjectionDiagnosticRevision);
73
92
  const groups = useMemo(() => groupChanges(changes), [changes]);
74
- const total = changes.length + structuralChanges.length + sketches.length;
75
- if (total === 0 && sketchStore.error === null)
93
+ const total = countPromptChanges(changes, structuralChanges, sketches.length);
94
+ const hasLoggedChanges = total > 0 || sketchStore.error !== null;
95
+ if (!hasLoggedChanges && comments.length === 0)
76
96
  return null;
77
- return (_jsxs(_Fragment, { children: [_jsx(Disclosure, { className: "changes", "data-test": "changes-log", triggerDataTest: "changes-toggle", title: "Changes", badge: total > 0 ? _jsx("span", { className: "changes__count", children: total }) : null, panelClassName: "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) => {
78
- const presentation = presentChange(change);
79
- const instanceDiagnostics = isElementChange(change) && change.scope === "rendered-instance"
80
- && change.instanceOverride
81
- ? getRenderedInstanceChangeDiagnostics(change.instanceOverride.id)
82
- : [];
83
- const textDiagnostics = change.kind === "text-content"
84
- ? getTextContentChangeDiagnostics(change.id)
85
- : [];
86
- return (_jsxs("div", { className: "changes__row", "data-test": "change-row", "data-property": presentation.property, children: [_jsx("span", { className: "changes__prop", children: presentation.propertyLabel }), _jsxs("span", { className: "changes__value", children: [_jsx("span", { className: "changes__before", children: presentation.before }), _jsx("span", { className: "changes__arrow", children: "\u2192" }), _jsx("span", { className: "changes__after", children: presentation.after })] }), presentation.scope ? (_jsx("span", { className: "changes__scope", "data-test": "change-scope", children: presentation.scope === "source-site" ? "All outputs at source site" : "This rendered item only" })) : null, presentation.evidence ? (_jsx("span", { className: "changes__evidence", "data-test": "change-evidence", children: presentation.evidence })) : null, _jsx(StaleChangeIndicator, { change: change }), instanceDiagnostics.map((diagnostic) => (_jsxs("span", { className: "changes__diagnostic", "data-test": "instance-diagnostic", "data-document": diagnostic.document, "data-status": diagnostic.status, children: [diagnostic.document, ": ", diagnostic.status] }, `${diagnostic.document}:${diagnostic.status}`))), textDiagnostics.map((diagnostic) => (_jsxs("span", { className: "changes__diagnostic", "data-test": "text-projection-diagnostic", "data-document": diagnostic.document, "data-status": diagnostic.status, children: [diagnostic.document, ": ", diagnostic.status] }, `${diagnostic.document}:${diagnostic.status}`))), _jsx(Button, { size: "compact", className: "changes__revert", "data-test": "change-revert", "data-property": presentation.property, onClick: () => revertChange(change), children: "Revert" })] }, `${group.key}\u0000${presentation.property}\u0000${i}`));
87
- })] }, group.key))), structuralChanges.map((change) => (_jsxs("div", { className: "changes__group", "data-test": "dom-change", "data-cid": change.target.sourceSite.cid, children: [_jsxs("div", { className: "changes__group-title", children: [_jsx("span", { children: change.target.sourceSite.cid }), _jsx("span", { className: "changes__group-file", children: sourceFile(change) })] }), _jsxs("div", { className: "changes__row", "data-test": "dom-change-row", "data-action": change.kind, children: [_jsxs("span", { className: "changes__source-site", "data-test": "structural-source-site", children: ["Source site: ", change.target.sourceSite.cid] }), _jsx("span", { className: "changes__prop", children: change.kind === "move" ? "Move in DOM" : "Delete from DOM" }), _jsxs("span", { className: "changes__value", children: [_jsx("span", { className: "changes__before", children: change.kind === "move"
88
- ? structuralParentIdentityKey(change.source.parent) === structuralParentIdentityKey(change.destination.parent)
89
- ? `${change.presentation.sourceParentTag} position ${change.presentation.fromIndex + 1}`
90
- : `${structuralParentLabel(change, "source")} position ${change.presentation.fromIndex + 1}`
91
- : "Visible" }), _jsx("span", { className: "changes__arrow", children: "\u2192" }), _jsx("span", { className: "changes__after", children: change.kind === "move"
92
- ? structuralParentIdentityKey(change.source.parent) === structuralParentIdentityKey(change.destination.parent)
93
- ? `${change.presentation.destinationParentTag} position ${change.presentation.toIndex + 1}`
94
- : `${structuralParentLabel(change, "destination")} position ${change.presentation.toIndex + 1}`
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] }));
97
+ return (_jsxs(_Fragment, { children: [hasLoggedChanges ? _jsx(Disclosure, { className: "changes", "data-test": "changes-log", triggerDataTest: "changes-toggle", title: "Changes", badge: total > 0 ? _jsx("span", { className: "changes__count", children: total }) : null, panelClassName: "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) => (_jsx("div", { className: "changes__group", "data-test": "changes-group", "data-cid": group.label, children: group.changes.map((change, i) => {
98
+ const presentation = presentChange(change);
99
+ const instanceDiagnostics = isElementChange(change) && change.scope === "rendered-instance"
100
+ && change.instanceOverride
101
+ ? getRenderedInstanceChangeDiagnostics(change.instanceOverride.id)
102
+ : [];
103
+ const textDiagnostics = change.kind === "text-content"
104
+ ? getTextContentChangeDiagnostics(change.id)
105
+ : [];
106
+ return (_jsxs("div", { className: "changes__row", "data-test": "change-row", "data-property": presentation.property, children: [_jsx("span", { className: "changes__target", "data-test": "change-target", children: changeTargetLabel(change, presentation) }), _jsx("span", { className: "changes__action", "data-test": "change-action", children: changeActionLabel(change, presentation) }), _jsxs("span", { className: "changes__metadata", "aria-hidden": "true", children: [_jsx("span", { className: "changes__prop", children: presentation.propertyLabel }), _jsxs("span", { className: "changes__value", children: [_jsx("span", { className: "changes__before", children: presentation.before }), _jsx("span", { className: "changes__arrow", children: "\u2192" }), _jsx("span", { className: "changes__after", children: presentation.after })] }), presentation.scope ? (_jsx("span", { className: "changes__scope", "data-test": "change-scope", children: presentation.scope === "source-site" ? "All outputs at source site" : "This rendered item only" })) : null, presentation.evidence ? (_jsx("span", { className: "changes__evidence", "data-test": "change-evidence", children: presentation.evidence })) : null, _jsx(StaleChangeIndicator, { change: change }), instanceDiagnostics.map((diagnostic) => (_jsxs("span", { className: "changes__diagnostic", "data-test": "instance-diagnostic", "data-document": diagnostic.document, "data-status": diagnostic.status, children: [diagnostic.document, ": ", diagnostic.status] }, `${diagnostic.document}:${diagnostic.status}`))), textDiagnostics.map((diagnostic) => (_jsxs("span", { className: "changes__diagnostic", "data-test": "text-projection-diagnostic", "data-document": diagnostic.document, "data-status": diagnostic.status, children: [diagnostic.document, ": ", diagnostic.status] }, `${diagnostic.document}:${diagnostic.status}`)))] }), _jsx(RevertButton, { dataTest: "change-revert", dataProperty: presentation.property, onClick: () => revertChange(change) })] }, `${group.key}\u0000${presentation.property}\u0000${i}`));
107
+ }) }, group.key))), structuralChanges.map((change) => (_jsx("div", { className: "changes__group", "data-test": "dom-change", "data-cid": change.target.sourceSite.cid, children: _jsxs("div", { className: "changes__row", "data-test": "dom-change-row", "data-action": change.kind, children: [_jsx("span", { className: "changes__target", "data-test": "structural-target", children: change.target.sourceSite.cid }), _jsx("span", { className: "changes__action", "data-test": "structural-action", children: change.kind === "move" ? "Moved" : "Deleted" }), _jsxs("span", { className: "changes__metadata", "aria-hidden": "true", children: [_jsxs("span", { className: "changes__source-site", "data-test": "structural-source-site", children: ["Source site: ", change.target.sourceSite.cid] }), _jsx("span", { className: "changes__prop", children: change.kind === "move" ? "Move in DOM" : "Delete from DOM" }), _jsxs("span", { className: "changes__value", children: [_jsx("span", { className: "changes__before", children: change.kind === "move"
108
+ ? structuralParentIdentityKey(change.source.parent) === structuralParentIdentityKey(change.destination.parent)
109
+ ? `${change.presentation.sourceParentTag} position ${change.presentation.fromIndex + 1}`
110
+ : `${structuralParentLabel(change, "source")} position ${change.presentation.fromIndex + 1}`
111
+ : "Visible" }), _jsx("span", { className: "changes__arrow", children: "\u2192" }), _jsx("span", { className: "changes__after", children: change.kind === "move"
112
+ ? structuralParentIdentityKey(change.source.parent) === structuralParentIdentityKey(change.destination.parent)
113
+ ? `${change.presentation.destinationParentTag} position ${change.presentation.toIndex + 1}`
114
+ : `${structuralParentLabel(change, "destination")} position ${change.presentation.toIndex + 1}`
115
+ : "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(RevertButton, { dataTest: "dom-change-revert", onClick: () => revertStructuralChange(change.id) })] }) }, change.id))), _jsx(SketchChanges, {})] })) }) : null, 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] }));
96
116
  }