nudge-ui 0.2.3 → 0.2.5

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 (264) hide show
  1. package/dist/cli/agentCommand.js +4 -28
  2. package/dist/client.mjs +870 -212
  3. package/dist/hosts/next/wrapper.js +1 -7
  4. package/dist/hosts/projectBridge.js +3 -17
  5. package/dist/inspector/agent/McpConnectionDialog.js +7 -3
  6. package/dist/inspector/agent/activity.d.ts +1 -1
  7. package/dist/inspector/agent/activity.js +3 -2
  8. package/dist/inspector/agent/client.d.ts +2 -2
  9. package/dist/inspector/agent/client.js +28 -29
  10. package/dist/inspector/agent/connectionStatus.d.ts +3 -1
  11. package/dist/inspector/agent/connectionStatus.js +25 -0
  12. package/dist/inspector/agent/httpTransport.js +31 -147
  13. package/dist/inspector/agent/protocol.d.ts +1 -1
  14. package/dist/inspector/agent/protocol.js +1 -1
  15. package/dist/inspector/agent/verification.d.ts +5 -7
  16. package/dist/inspector/agent/verification.js +67 -20
  17. package/dist/inspector/boot.d.ts +3 -0
  18. package/dist/inspector/boot.js +297 -0
  19. package/dist/inspector/canvas/AgentActivityOverlay.d.ts +2 -1
  20. package/dist/inspector/canvas/AgentActivityOverlay.js +6 -3
  21. package/dist/inspector/canvas/CanvasCard.js +23 -12
  22. package/dist/inspector/canvas/CanvasElementOverlay.js +97 -128
  23. package/dist/inspector/canvas/CanvasToolbar.js +2 -1
  24. package/dist/inspector/canvas/CanvasWorkspace.js +119 -162
  25. package/dist/inspector/canvas/LockedWorkspaceNotice.d.ts +3 -2
  26. package/dist/inspector/canvas/LockedWorkspaceNotice.js +10 -6
  27. package/dist/inspector/canvas/PageCard.d.ts +10 -0
  28. package/dist/inspector/canvas/PageCard.js +51 -0
  29. package/dist/inspector/canvas/SketchToolOptions.d.ts +5 -0
  30. package/dist/inspector/canvas/SketchToolOptions.js +86 -0
  31. package/dist/inspector/canvas/StaleChangeIndicator.d.ts +1 -1
  32. package/dist/inspector/canvas/StaleChangeIndicator.js +1 -1
  33. package/dist/inspector/canvas/ToolOptionsDrawer.d.ts +21 -0
  34. package/dist/inspector/canvas/ToolOptionsDrawer.js +77 -0
  35. package/dist/inspector/canvas/activeCanvasDocument.d.ts +3 -2
  36. package/dist/inspector/canvas/assets/eraser.svg +28 -0
  37. package/dist/inspector/canvas/assets/pen.svg +25 -0
  38. package/dist/inspector/canvas/canvasFrame.d.ts +16 -0
  39. package/dist/inspector/canvas/canvasFrame.js +10 -0
  40. package/dist/inspector/canvas/canvasGestures.d.ts +0 -1
  41. package/dist/inspector/canvas/canvasGestures.js +0 -6
  42. package/dist/inspector/canvas/canvasStore.d.ts +6 -0
  43. package/dist/inspector/canvas/canvasStore.js +36 -1
  44. package/dist/inspector/canvas/frameConnection.d.ts +48 -0
  45. package/dist/inspector/canvas/frameConnection.js +73 -0
  46. package/dist/inspector/canvas/frameProtocol.d.ts +18 -1
  47. package/dist/inspector/canvas/frameProtocol.js +36 -0
  48. package/dist/inspector/canvas/frameScrollbarStyles.d.ts +3 -0
  49. package/dist/inspector/canvas/frameScrollbarStyles.js +21 -0
  50. package/dist/inspector/canvas/pageSurface.d.ts +11 -0
  51. package/dist/inspector/canvas/pageSurface.js +34 -0
  52. package/dist/inspector/canvas/projection.d.ts +19 -9
  53. package/dist/inspector/canvas/projection.js +58 -31
  54. package/dist/inspector/canvas/rendererBootstrap.js +4 -1
  55. package/dist/inspector/canvas/rendererElementSelector.js +9 -1
  56. package/dist/inspector/canvas/rendererMessageRouter.d.ts +4 -5
  57. package/dist/inspector/canvas/rendererMessageRouter.js +12 -16
  58. package/dist/inspector/canvas/rendererSelectionProxy.d.ts +7 -1
  59. package/dist/inspector/canvas/rendererSelectionProxy.js +8 -36
  60. package/dist/inspector/canvas/rendererStylesheet.js +15 -0
  61. package/dist/inspector/canvas/sessionStore.d.ts +2 -2
  62. package/dist/inspector/canvas/sessionStore.js +12 -9
  63. package/dist/inspector/canvas/staleChangeDetector.d.ts +1 -1
  64. package/dist/inspector/canvas/staleChangeDetector.js +10 -22
  65. package/dist/inspector/changes/codecs.d.ts +2 -0
  66. package/dist/inspector/changes/codecs.js +27 -3
  67. package/dist/inspector/changes/draftPreview.d.ts +12 -0
  68. package/dist/inspector/changes/draftPreview.js +133 -0
  69. package/dist/inspector/changes/editModel.d.ts +18 -0
  70. package/dist/inspector/changes/editModel.js +16 -3
  71. package/dist/inspector/changes/managedStyleProjection.js +2 -1
  72. package/dist/inspector/changes/previewDiagnostics.js +1 -1
  73. package/dist/inspector/changes/types.d.ts +18 -1
  74. package/dist/inspector/comments/CommentsOverlay.js +18 -16
  75. package/dist/inspector/comments/element.js +10 -3
  76. package/dist/inspector/comments/store.js +3 -2
  77. package/dist/inspector/comments/verification.js +1 -1
  78. package/dist/inspector/componentSemantics/ComponentPropsSection.js +2 -1
  79. package/dist/inspector/componentSemantics/adapterRegistry.d.ts +6 -1
  80. package/dist/inspector/componentSemantics/adapterRegistry.js +23 -1
  81. package/dist/inspector/componentSemantics/changeAction.js +1 -1
  82. package/dist/inspector/componentSemantics/reactRuntime.d.ts +2 -1
  83. package/dist/inspector/componentSemantics/reactRuntime.js +38 -0
  84. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +4 -2
  85. package/dist/inspector/componentSemantics/runtimeBridge.js +17 -0
  86. package/dist/inspector/componentSemantics/textBinding.d.ts +1 -2
  87. package/dist/inspector/componentSemantics/textBinding.js +4 -45
  88. package/dist/inspector/componentSemantics/types.d.ts +11 -0
  89. package/dist/inspector/drafts/draftStore.d.ts +109 -0
  90. package/dist/inspector/drafts/draftStore.js +451 -0
  91. package/dist/inspector/drafts/model.d.ts +9 -1
  92. package/dist/inspector/drafts/model.js +5 -1
  93. package/dist/inspector/drafts/store.d.ts +53 -15
  94. package/dist/inspector/drafts/store.js +143 -169
  95. package/dist/inspector/index.d.ts +2 -4
  96. package/dist/inspector/index.js +2 -264
  97. package/dist/inspector/inline-text/inlineTextEditor.d.ts +1 -1
  98. package/dist/inspector/inline-text/inlineTextEditor.js +1 -2
  99. package/dist/inspector/inline-text/textEditChange.d.ts +1 -1
  100. package/dist/inspector/inspection/bridge.d.ts +1 -1
  101. package/dist/inspector/inspection/bridge.js +2 -1
  102. package/dist/inspector/inspection/browserCssInspection.d.ts +8 -0
  103. package/dist/inspector/inspection/browserCssInspection.js +28 -6
  104. package/dist/inspector/inspection/inspectionSnapshot.d.ts +6 -0
  105. package/dist/inspector/inspection/inspectionSnapshot.js +10 -0
  106. package/dist/inspector/inspection/performanceDiagnostics.d.ts +18 -0
  107. package/dist/inspector/inspection/performanceDiagnostics.js +31 -0
  108. package/dist/inspector/inspection/useBrowserCssInspection.js +17 -38
  109. package/dist/inspector/iterations/capture.d.ts +2 -1
  110. package/dist/inspector/iterations/capture.js +2 -1
  111. package/dist/inspector/iterations/client.d.ts +2 -1
  112. package/dist/inspector/iterations/coordinator.d.ts +4 -3
  113. package/dist/inspector/iterations/coordinator.js +5 -3
  114. package/dist/inspector/iterations/prompt.js +9 -8
  115. package/dist/inspector/overlay/interactionStyles.js +17 -0
  116. package/dist/inspector/pageInspector.d.ts +42 -0
  117. package/dist/inspector/pageInspector.js +342 -0
  118. package/dist/inspector/projection/draftProjection.d.ts +10 -2
  119. package/dist/inspector/projection/draftProjection.js +9 -1
  120. package/dist/inspector/projection/managedStylesheet.js +24 -4
  121. package/dist/inspector/projection/renderedInstance.d.ts +15 -2
  122. package/dist/inspector/projection/renderedInstance.js +60 -7
  123. package/dist/inspector/projection/structuralProjection.d.ts +0 -9
  124. package/dist/inspector/projection/structuralProjection.js +16 -46
  125. package/dist/inspector/projection/textProjection.d.ts +8 -2
  126. package/dist/inspector/projection/textProjection.js +77 -24
  127. package/dist/inspector/prompt/clipboardHandoff.d.ts +14 -0
  128. package/dist/inspector/prompt/clipboardHandoff.js +47 -16
  129. package/dist/inspector/prompt/generatePrompt.d.ts +1 -1
  130. package/dist/inspector/prompt/generatePrompt.js +248 -148
  131. package/dist/inspector/prompt/promptDocument.d.ts +3 -0
  132. package/dist/inspector/prompt/promptDocument.js +8 -0
  133. package/dist/inspector/prompt/promptSettings.js +7 -5
  134. package/dist/inspector/runtime/inspectorStorage.d.ts +17 -0
  135. package/dist/inspector/runtime/inspectorStorage.js +42 -0
  136. package/dist/inspector/runtime/ownedStylesheets.d.ts +20 -0
  137. package/dist/inspector/runtime/ownedStylesheets.js +72 -0
  138. package/dist/inspector/runtime/runtimeConfig.d.ts +4 -0
  139. package/dist/inspector/runtime/runtimeConfig.js +10 -1
  140. package/dist/inspector/runtime/runtimeIdentityClaims.d.ts +29 -0
  141. package/dist/inspector/runtime/runtimeIdentityClaims.js +188 -0
  142. package/dist/inspector/runtime/staticHtmlRuntimeIdentity.d.ts +15 -0
  143. package/dist/inspector/runtime/staticHtmlRuntimeIdentity.js +113 -29
  144. package/dist/inspector/selection/editScope.d.ts +1 -0
  145. package/dist/inspector/selection/editScope.js +13 -2
  146. package/dist/inspector/selection/sourceSite.d.ts +12 -0
  147. package/dist/inspector/selection/sourceSite.js +28 -0
  148. package/dist/inspector/shell/AgentStatusButton.js +2 -2
  149. package/dist/inspector/shell/AppShell.js +6 -2
  150. package/dist/inspector/shell/ChangesLog.js +27 -9
  151. package/dist/inspector/shell/CopyPromptButton.js +74 -26
  152. package/dist/inspector/shell/EmptyState.js +3 -3
  153. package/dist/inspector/shell/InspectorShell.d.ts +0 -3
  154. package/dist/inspector/shell/InspectorShell.js +21 -13
  155. package/dist/inspector/shell/PeekOriginalButton.js +3 -3
  156. package/dist/inspector/shell/interactionStatePreview.d.ts +9 -0
  157. package/dist/inspector/shell/interactionStatePreview.js +112 -0
  158. package/dist/inspector/shell/originalPreview.d.ts +3 -2
  159. package/dist/inspector/shell/originalPreview.js +10 -37
  160. package/dist/inspector/shell/stateValue.js +2 -3
  161. package/dist/inspector/sketch/SketchEditor.js +5 -4
  162. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +2 -1
  163. package/dist/inspector/sketch/SketchFrameOverlay.js +5 -4
  164. package/dist/inspector/sketch/SketchOverlay.d.ts +3 -5
  165. package/dist/inspector/sketch/SketchOverlay.js +139 -263
  166. package/dist/inspector/sketch/SketchPromptPanel.d.ts +1 -1
  167. package/dist/inspector/sketch/SketchPromptPanel.js +8 -1
  168. package/dist/inspector/sketch/SketchThumbnail.d.ts +5 -0
  169. package/dist/inspector/sketch/SketchThumbnail.js +18 -0
  170. package/dist/inspector/sketch/SketchWorkspace.d.ts +1 -2
  171. package/dist/inspector/sketch/SketchWorkspace.js +69 -34
  172. package/dist/inspector/sketch/capture.js +19 -3
  173. package/dist/inspector/sketch/copySketch.d.ts +10 -0
  174. package/dist/inspector/sketch/copySketch.js +52 -0
  175. package/dist/inspector/sketch/drawingTools.d.ts +17 -0
  176. package/dist/inspector/sketch/drawingTools.js +47 -0
  177. package/dist/inspector/sketch/frameHeading.d.ts +9 -0
  178. package/dist/inspector/sketch/frameHeading.js +30 -0
  179. package/dist/inspector/sketch/freehand.js +3 -3
  180. package/dist/inspector/sketch/geometry.d.ts +6 -0
  181. package/dist/inspector/sketch/geometry.js +19 -0
  182. package/dist/inspector/sketch/history.d.ts +30 -0
  183. package/dist/inspector/sketch/history.js +58 -0
  184. package/dist/inspector/sketch/interaction.d.ts +1 -3
  185. package/dist/inspector/sketch/interaction.js +6 -9
  186. package/dist/inspector/sketch/model.d.ts +35 -1
  187. package/dist/inspector/sketch/model.js +116 -61
  188. package/dist/inspector/sketch/persistence.d.ts +14 -0
  189. package/dist/inspector/sketch/persistence.js +63 -7
  190. package/dist/inspector/sketch/raster.d.ts +6 -2
  191. package/dist/inspector/sketch/raster.js +43 -17
  192. package/dist/inspector/sketch/store.d.ts +1 -1
  193. package/dist/inspector/sketch/store.js +1 -1
  194. package/dist/inspector/sketch/videoFrames.d.ts +6 -0
  195. package/dist/inspector/sketch/videoFrames.js +81 -0
  196. package/dist/inspector/styleEditors/BackgroundEditor.js +3 -2
  197. package/dist/inspector/styleEditors/BorderEditor.js +30 -130
  198. package/dist/inspector/styleEditors/BorderRadiusEditor.js +8 -3
  199. package/dist/inspector/styleEditors/ImageBackgroundEditor.js +1 -1
  200. package/dist/inspector/styleEditors/LayoutSection.js +2 -2
  201. package/dist/inspector/styleEditors/borderModel.d.ts +42 -0
  202. package/dist/inspector/styleEditors/borderModel.js +114 -0
  203. package/dist/inspector/styleEditors/layoutValue.js +2 -3
  204. package/dist/inspector/tokens/TokenField.js +40 -7
  205. package/dist/inspector/tokens/TokensPanel.js +2 -1
  206. package/dist/inspector/tokens/editActions.d.ts +3 -3
  207. package/dist/inspector/tokens/editActions.js +15 -3
  208. package/dist/inspector/tokens/resolution/containerQueryEvaluator.d.ts +15 -0
  209. package/dist/inspector/tokens/resolution/containerQueryEvaluator.js +86 -0
  210. package/dist/inspector/tokens/resolution/cssomCollector.js +44 -9
  211. package/dist/inspector/tokens/resolution/nestedSelectors.d.ts +6 -0
  212. package/dist/inspector/tokens/resolution/nestedSelectors.js +67 -0
  213. package/dist/inspector/tokens/resolution/ruleMatching.js +23 -4
  214. package/dist/inspector/tokens/resolution.d.ts +6 -3
  215. package/dist/inspector/tokens/resolution.js +91 -112
  216. package/dist/inspector/tokens/tokenEdits.d.ts +1 -1
  217. package/dist/inspector/tokens/tokenEdits.js +1 -1
  218. package/dist/inspector/ui/InlineStyleWarning.js +2 -2
  219. package/dist/inspector/ui/InspectorStyle.d.ts +8 -0
  220. package/dist/inspector/ui/InspectorStyle.js +26 -0
  221. package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
  222. package/dist/inspector/ui/PopoverListbox.js +3 -3
  223. package/dist/inspector/ui/Toast.d.ts +14 -0
  224. package/dist/inspector/ui/Toast.js +30 -0
  225. package/dist/inspector/ui/assets/canvasIllustration.d.ts +2 -0
  226. package/dist/inspector/ui/assets/canvasIllustration.js +21 -0
  227. package/dist/inspector/ui/assets/gridIllustration.d.ts +2 -0
  228. package/dist/inspector/ui/assets/{grid.svg → gridIllustration.js} +3 -2
  229. package/dist/inspector/ui/customCursors.d.ts +5 -0
  230. package/dist/inspector/ui/customCursors.js +32 -6
  231. package/dist/inspector/ui/styles.js +12 -10
  232. package/dist/inspector/workspace/commands.js +1 -2
  233. package/dist/inspector/workspace/controller.js +1 -2
  234. package/dist/inspector/workspace/handoff.js +3 -2
  235. package/dist/inspector/workspace/sentItems.d.ts +16 -0
  236. package/dist/inspector/workspace/sentItems.js +33 -0
  237. package/dist/inspector/workspace/timeline.d.ts +24 -10
  238. package/dist/inspector/workspace/timeline.js +118 -96
  239. package/dist/internal/agent-protocol.d.ts +50 -13
  240. package/dist/internal/agent-protocol.js +145 -19
  241. package/dist/nudge-ui.mjs +81 -65
  242. package/dist/project/mcpPackage.d.ts +8 -0
  243. package/dist/project/mcpPackage.js +60 -0
  244. package/package.json +13 -3
  245. package/dist/inspector/canvas/measurementProjection.d.ts +0 -8
  246. package/dist/inspector/canvas/measurementProjection.js +0 -16
  247. package/dist/inspector/changes/changesLog.d.ts +0 -47
  248. package/dist/inspector/changes/changesLog.js +0 -247
  249. package/dist/inspector/changes/draftChanges.d.ts +0 -56
  250. package/dist/inspector/changes/draftChanges.js +0 -300
  251. package/dist/inspector/conformance/projection.d.ts +0 -2
  252. package/dist/inspector/conformance/projection.js +0 -3
  253. package/dist/inspector/inline-text/textChangeBoundary.d.ts +0 -6
  254. package/dist/inspector/inline-text/textChangeBoundary.js +0 -1
  255. package/dist/inspector/overlay/InspectorOverlay.d.ts +0 -5
  256. package/dist/inspector/overlay/InspectorOverlay.js +0 -332
  257. package/dist/inspector/overlay/elementSelector.d.ts +0 -1
  258. package/dist/inspector/overlay/elementSelector.js +0 -111
  259. package/dist/inspector/sketch/SketchChanges.d.ts +0 -2
  260. package/dist/inspector/sketch/SketchChanges.js +0 -72
  261. package/dist/inspector/sketch/SketchEntryButton.d.ts +0 -4
  262. package/dist/inspector/sketch/SketchEntryButton.js +0 -11
  263. package/dist/inspector/sketch/SketchLayersPanel.d.ts +0 -2
  264. package/dist/inspector/sketch/SketchLayersPanel.js +0 -71
@@ -1,16 +1,37 @@
1
- import { isComponentChange, isElementChange, isTextContentChange, isTokenChange } from "../changes/changesLog.js";
2
- import { canonicalizeChanges, changeKey, tokenReference } from "../changes/model.js";
3
- import { getAnyPreviewDiagnostic } from "../changes/previewDiagnostics.js";
1
+ import { isComponentChange, isElementChange, isTextContentChange, isTokenChange } from "../changes/types.js";
2
+ import { canonicalizeChanges, tokenReference } from "../changes/model.js";
4
3
  import { formatComponentPropBaseline, formatComponentPropValue, } from "../componentSemantics/changeModel.js";
5
4
  import { boundRuntimeEvidence, isRuntimeGeneratedSource, normalizeRuntimeTag, normalizeRuntimeText, } from "../runtime/staticHtmlRuntimeIdentity.js";
6
5
  import { getSourceCoordinatePolicy } from "../runtime/runtimeConfig.js";
7
6
  import { DEFAULT_CUSTOM_INSTRUCTIONS } from "./promptSettings.js";
7
+ import { PROMPT_TITLE } from "./promptDocument.js";
8
8
  import { getCommentsForTarget } from "../comments/store.js";
9
9
  import { documentTarget } from "../drafts/model.js";
10
10
  const EMPTY_SENTINEL = "<!-- No changes to export -->\n\nThe changes log is empty. Make a change in the Nudge UI inspector first.";
11
+ /** Labels describe the observed tag, without guessing its role in the page. */
12
+ function elementLabel(tag) {
13
+ const normalized = normalizeRuntimeTag(tag);
14
+ const labels = {
15
+ p: "Paragraph", button: "Button", a: "Link", img: "Image", input: "Input",
16
+ textarea: "Text area", select: "Select", form: "Form", nav: "Navigation", section: "Section",
17
+ };
18
+ const label = /^h[1-6]$/.test(normalized) ? "Heading" : labels[normalized] ?? "Element";
19
+ return normalized ? `${label} (${promptText(`<${normalized}>`)})` : label;
20
+ }
21
+ /** Runtime IDs identify one DOM element; authored sites can render several items. */
22
+ function elementPromptKey(cid, src, scope, locator) {
23
+ if (!src || isRuntimeGeneratedSource(src))
24
+ return JSON.stringify(["runtime", cid]);
25
+ return JSON.stringify([cid, src, scope, scope === "rendered-instance" ? [
26
+ locator?.occurrence, locator?.props, locator?.ariaLabel ?? null, locator?.invocation?.callsiteId ?? null,
27
+ ] : null]);
28
+ }
29
+ function refPromptKey(ref) {
30
+ return elementPromptKey(ref.sourceSite.cid, ref.sourceSite.src, "rendered-instance", ref.locator);
31
+ }
11
32
  function renderPrompt(sections, customInstructions) {
12
33
  const lines = [
13
- "# Requested design changes",
34
+ PROMPT_TITLE,
14
35
  ];
15
36
  for (const section of sections) {
16
37
  const content = [...section.lines];
@@ -47,6 +68,7 @@ function groupElementChanges(changes) {
47
68
  line: change.source.line || change.line,
48
69
  column: change.column ?? 0,
49
70
  runtimeEvidence: change.runtimeEvidence,
71
+ sourceEvidence: change.sourceEvidence,
50
72
  instanceOverride,
51
73
  changes: [],
52
74
  };
@@ -56,28 +78,24 @@ function groupElementChanges(changes) {
56
78
  }
57
79
  return [...map.values()];
58
80
  }
59
- function conflictSuffix(rec) {
60
- const result = getAnyPreviewDiagnostic(changeKey(rec))?.result;
61
- if (!result || result.status === "applied")
62
- return "";
63
- return ` — preview conflict: browser computed \`${result.computedValue || "(no value)"}\` (${result.reason ?? "cascade conflict"}); implement the requested value without assuming \`!important\``;
64
- }
65
81
  function elementChangeLine(rec) {
82
+ const state = rec.state && rec.state !== "base" ? ` (${rec.state})` : "";
83
+ const property = `\`${rec.property}\`${state}`;
66
84
  if (rec.newToken && rec.oldToken) {
67
- return `- \`${rec.property}\`: \`${rec.oldToken.name}\` → \`${rec.newToken.name}\`${conflictSuffix(rec)}`;
85
+ return `- **Change:** ${property}: \`${rec.oldToken.name}\` → \`${rec.newToken.name}\``;
68
86
  }
69
87
  if (rec.newToken) {
70
88
  const before = rec.oldRawValue !== undefined ? `\`${rec.oldRawValue}\` → ` : "";
71
89
  // Describe exactly the value the managed stylesheet wrote for this swap
72
90
  // (adapter literal or var(cssName)); a bare var(name) would be invalid
73
91
  // CSS for adapter tokens whose names are not custom-property names.
74
- return `- \`${rec.property}\`: ${before}\`${tokenReference(rec.newToken)}\`${conflictSuffix(rec)}`;
92
+ return `- **Change:** ${property}: ${before}\`${tokenReference(rec.newToken)}\``;
75
93
  }
76
94
  if (rec.rawValue !== undefined) {
77
95
  const before = rec.oldRawValue !== undefined ? `\`${rec.oldRawValue}\` → ` : "";
78
- return `- \`${rec.property}\`: ${before}\`${rec.rawValue}\`${conflictSuffix(rec)}`;
96
+ return `- **Change:** ${property}: ${before}\`${rec.rawValue}\``;
79
97
  }
80
- return `- \`${rec.property}\`: (no value)`;
98
+ return `- **Change:** ${property}: (no value)`;
81
99
  }
82
100
  function sourceIntentLine(rec) {
83
101
  if (!rec.sourceProperty || rec.sourceProperty === rec.property)
@@ -86,27 +104,22 @@ function sourceIntentLine(rec) {
86
104
  return ` - Source declaration (CSSOM): \`${rec.sourceProperty}${authored}\`; preview edit uses physical \`${rec.property}\``;
87
105
  }
88
106
  function tokenChangeLine(rec) {
89
- return `- \`${rec.tokenName}\` (${rec.contextLabel}, ${rec.file}:${rec.line}): \`${rec.oldRawValue}\` → \`${rec.rawValue}\`${conflictSuffix(rec)}`;
107
+ return `- **Change:** \`${rec.tokenName}\` (${rec.contextLabel}, ${rec.file}:${rec.line}): \`${rec.oldRawValue}\` → \`${rec.rawValue}\``;
90
108
  }
91
109
  function componentChangeLine(rec) {
92
110
  const sourceGuidance = rec.authoredAs === "literal"
93
111
  ? rec.property === "children"
94
112
  ? "replace the literal child text"
95
- : "replace the invocation prop literal"
113
+ : ""
96
114
  : rec.authoredAs === "default"
97
115
  ? "add the prop at this invocation"
98
116
  : `preserve the authored ${rec.authoredAs} and update its source logic`;
99
- const scope = rec.scope === "rendered-instance"
100
- ? "this rendered item only"
101
- : "all outputs at this source site";
102
- return `- \`${rec.property}\`: \`${formatComponentPropBaseline(rec.before)}\` → \`${formatComponentPropValue(rec.after)}\` — ${sourceGuidance}; scope: ${scope}`;
117
+ return `- **Change:** \`${rec.property}\`: \`${formatComponentPropBaseline(rec.before)}\` → \`${formatComponentPropValue(rec.after)}\`${sourceGuidance ? ` — ${sourceGuidance}` : ""}`;
103
118
  }
104
- function textAuthorshipGuidance(authoredAs) {
105
- if (authoredAs === "literal")
106
- return "replace the authored literal text";
107
- if (authoredAs === "expression")
108
- return "preserve the expression and update its source logic";
109
- return "update the source that produces this rendered copy";
119
+ function editScopeLine(scope) {
120
+ return scope === "rendered-instance"
121
+ ? " - Apply to this selected item only; keep other instances unchanged."
122
+ : " - Apply to all items rendered at this source location.";
110
123
  }
111
124
  /** Keep arbitrary rendered copy valid inside the Markdown prompt. */
112
125
  function promptText(value) {
@@ -116,35 +129,62 @@ function promptText(value) {
116
129
  const fence = "~".repeat(Math.max(3, longestTildeRun + 1));
117
130
  return `${fence}text\n${value}\n${fence}`;
118
131
  }
119
- function textEvidenceLines(target) {
120
- const sourceLabel = isRuntimeGeneratedSource(target.sourceSite.src)
121
- ? `${target.sourceSite.cid} (source unknown; runtime-created DOM)`
122
- : `${target.sourceSite.cid} (\`${target.sourceSite.src}\`)`;
123
- const lines = [
124
- ` - Source site: ${sourceLabel}`,
125
- ];
126
- const props = boundRuntimeEvidence(target.props);
127
- const ariaLabel = boundRuntimeEvidence(target.ariaLabel);
128
- if (props)
129
- lines.push(` - Props evidence: ${promptText(props)}`);
130
- if (ariaLabel)
131
- lines.push(` - Accessible name evidence: ${promptText(ariaLabel)}`);
132
- return lines;
133
- }
134
- function textScopeLines(change) {
135
- const scope = change.scope === "source-site" ? "source site / all outputs" : "this rendered item only";
136
- const lines = [` - Scope: ${scope}`];
132
+ function textBindingLines(change, sourceLocation) {
133
+ const lines = [];
137
134
  if (change.evidence) {
138
- lines.push(` - Semantic evidence: \`${change.evidence.componentName}.${change.evidence.property}\` at callsite \`${change.evidence.callsiteId}\` (${change.evidence.mountedCount} mounted outputs)`);
135
+ const evidence = change.evidence;
136
+ const location = evidence.callsiteId === sourceLocation ? "" : ` at ${promptText(evidence.callsiteId)}`;
137
+ lines.push(` - Text source: ${promptText(`${evidence.componentName}.${evidence.property}`)}${location} (${evidence.mountedCount} rendered instances).`);
139
138
  }
140
139
  return lines;
141
140
  }
141
+ /** Shared presentation for captured facts; these do not change target matching. */
142
+ function targetEvidenceLines(evidence, example = false) {
143
+ const text = normalizeRuntimeText(evidence.text ?? null);
144
+ const ariaLabel = boundRuntimeEvidence(evidence.ariaLabel ?? null);
145
+ const elementId = boundRuntimeEvidence(evidence.elementId ?? null);
146
+ const props = boundRuntimeEvidence(evidence.props ?? null);
147
+ const lines = [];
148
+ if (elementId && !example)
149
+ lines.push(`- Element ID: ${promptText(elementId)}`);
150
+ if (text)
151
+ lines.push(`- ${example ? "Example text" : "Text"}: ${promptText(text)}`);
152
+ if (ariaLabel)
153
+ lines.push(`- ${example ? "Example accessible name" : "Accessible name"}: ${promptText(ariaLabel)}`);
154
+ if (props && props !== `id:${elementId}`)
155
+ lines.push(`- ${example ? "Example props" : "Props"}: ${promptText(props)}`);
156
+ if (evidence.domPath && !example)
157
+ lines.push(`- DOM path: ${promptText(evidence.domPath)}`);
158
+ return lines;
159
+ }
160
+ function readableTargetLabel(text, tag) {
161
+ if (tag)
162
+ return elementLabel(tag);
163
+ const bounded = normalizeRuntimeText(text);
164
+ return bounded ? `Text: ${inlineHeadingText(bounded)}` : "Element";
165
+ }
166
+ /** Use an inline code span even when copy contains Markdown delimiters. */
167
+ function inlineHeadingText(text) {
168
+ const runs = text.match(/`+/g) ?? [];
169
+ const delimiter = "`".repeat(Math.max(0, ...runs.map((run) => run.length)) + 1);
170
+ const padding = text.startsWith("`") || text.endsWith("`") ? " " : "";
171
+ return `${delimiter}${padding}${text}${padding}${delimiter}`;
172
+ }
142
173
  function boundedEvidence(value) {
143
174
  return boundRuntimeEvidence(value);
144
175
  }
145
176
  function sourceSiteLabel(ref) {
146
- if (isRuntimeGeneratedSource(ref.sourceSite.src)) {
147
- return `${ref.sourceSite.cid} (source unknown; runtime-created DOM)`;
177
+ if (!ref.sourceSite.src || isRuntimeGeneratedSource(ref.sourceSite.src)) {
178
+ const text = boundedEvidence(ref.locator.text);
179
+ const ariaLabel = boundedEvidence(ref.locator.ariaLabel ?? null);
180
+ const props = boundedEvidence(ref.locator.props);
181
+ if (ariaLabel)
182
+ return `the element named ${promptText(ariaLabel)}`;
183
+ if (text)
184
+ return `the element with text ${promptText(text)}`;
185
+ if (props)
186
+ return `the element with props ${promptText(props)}`;
187
+ return "the element";
148
188
  }
149
189
  return `${ref.sourceSite.cid} (${ref.sourceSite.src})`;
150
190
  }
@@ -159,25 +199,27 @@ function structuralParentIdentityKey(ref) {
159
199
  locator.ariaLabel ?? null,
160
200
  ]);
161
201
  }
162
- function runtimeEvidenceLines(group) {
163
- const evidence = group.runtimeEvidence;
164
- if (!evidence)
165
- return [];
166
- const tagName = normalizeRuntimeTag(evidence.tagName) || "unknown";
167
- const text = normalizeRuntimeText(evidence.text);
168
- const ariaLabel = boundRuntimeEvidence(evidence.ariaLabel);
169
- const props = boundRuntimeEvidence(evidence.props);
170
- const lines = [
171
- " - Source: unknown; locate the JavaScript or template that creates this runtime DOM.",
172
- ` - Rendered element: \`<${tagName}>\``,
173
- ];
174
- if (text)
175
- lines.push(` - Text evidence: ${promptText(text)}`);
176
- if (ariaLabel)
177
- lines.push(` - Accessible name evidence: ${promptText(ariaLabel)}`);
178
- if (props)
179
- lines.push(` - Props evidence: ${promptText(props)}`);
180
- return lines;
202
+ function elementEvidenceLines(group, example = false) {
203
+ const evidence = group.runtimeEvidence ?? group.sourceEvidence;
204
+ return evidence ? targetEvidenceLines(evidence, example)
205
+ : group.instanceOverride ? targetEvidenceLines(group.instanceOverride.target.locator, example) : [];
206
+ }
207
+ /** Combine identical final requests for individually selected elements. */
208
+ function selectedStyleGroupKey(group) {
209
+ const requests = group.changes.map((change) => JSON.stringify([elementChangeLine(change), sourceIntentLine(change)])).sort();
210
+ return JSON.stringify(["selected-styles", requests]);
211
+ }
212
+ function selectedItemLine(group, sourceLocation) {
213
+ const facts = elementEvidenceLines(group).map((line) => line.slice(2));
214
+ if (sourceLocation)
215
+ facts.unshift(`Source: ${promptText(sourceLocation)}`);
216
+ const invocation = group.instanceOverride?.target.locator.invocation;
217
+ if (invocation)
218
+ facts.push(`Component usage: ${promptText(invocation.callsiteId)}`);
219
+ return ` - ${facts.join("; ") || "Selected element"}`;
220
+ }
221
+ function setHeading(label, tag) {
222
+ return tag ? `${label} (${promptText(`<${normalizeRuntimeTag(tag)}>`)})` : label;
181
223
  }
182
224
  /**
183
225
  * Coordinate precision follows each element's identity origin, not the
@@ -194,7 +236,7 @@ function hasExactAuthoredCoordinates(policy, cid, file) {
194
236
  return policy.exactFileExtensions.some((extension) => file.endsWith(extension));
195
237
  }
196
238
  /**
197
- * Formats one source location for a prompt heading. Exact origins keep the
239
+ * Formats one source location for a prompt. Exact origins keep the
198
240
  * authored column; line-only origins — and sites whose column is unknown —
199
241
  * degrade to `file:line` rather than fabricating precision.
200
242
  */
@@ -204,27 +246,19 @@ function formatSourceLocation(policy, source) {
204
246
  }
205
247
  return `${source.file}:${source.line}`;
206
248
  }
207
- function elementGroupSource(group, policy) {
208
- if (group.runtimeEvidence) {
209
- return group.runtimeEvidence.reason === "unannotated"
210
- ? "source unknown; no authored location available"
211
- : "source unknown; runtime-created DOM";
212
- }
213
- return formatSourceLocation(policy, group);
214
- }
215
249
  function renderedInstanceDescription(ref) {
216
250
  const text = boundedEvidence(ref.locator.text);
217
251
  const ariaLabel = boundedEvidence(ref.locator.ariaLabel ?? null);
218
252
  const props = boundedEvidence(ref.locator.props);
219
- const source = isRuntimeGeneratedSource(ref.sourceSite.src)
220
- ? sourceSiteLabel(ref)
221
- : ref.sourceSite.src;
253
+ const source = ref.sourceSite.src && !isRuntimeGeneratedSource(ref.sourceSite.src)
254
+ ? ` (${ref.sourceSite.src})`
255
+ : "";
222
256
  if (text)
223
- return `text ${promptText(text)} (${source})`;
257
+ return `text ${promptText(text)}${source}`;
224
258
  if (ariaLabel)
225
- return `the element named ${promptText(ariaLabel)} (${source})`;
259
+ return `the element named ${promptText(ariaLabel)}${source}`;
226
260
  if (props)
227
- return `the element with props ${promptText(props)} (${source})`;
261
+ return `the element with props ${promptText(props)}${source}`;
228
262
  // With no semantic evidence, the ordinal is the only way to distinguish
229
263
  // repeated outputs from the same source site.
230
264
  return `rendered instance ${ref.locator.occurrence + 1} of ${sourceSiteLabel(ref)}`;
@@ -232,7 +266,10 @@ function renderedInstanceDescription(ref) {
232
266
  function structuralChangeLine(change) {
233
267
  const target = renderedInstanceDescription(change.target);
234
268
  if (change.kind === "delete") {
235
- return `- Remove ${target} from the source.`;
269
+ const invocation = change.target.locator.invocation;
270
+ return invocation
271
+ ? `- Remove the ${promptText(`<${invocation.componentName}>`)} invocation at ${invocation.callsiteId} from the source; keep its other invocations. It renders ${target}.`
272
+ : `- Remove ${target} from the source.`;
236
273
  }
237
274
  const before = change.destination.before;
238
275
  const sourceParent = sourceSiteLabel(change.source.parent);
@@ -254,6 +291,7 @@ function renderedInstanceKey(ref) {
254
291
  locator.props,
255
292
  locator.text,
256
293
  locator.ariaLabel ?? null,
294
+ locator.invocation?.callsiteId ?? null,
257
295
  ]);
258
296
  }
259
297
  /** Export final intent, not the gesture history required to replay the preview. */
@@ -328,82 +366,144 @@ export function generatePrompt(changes, frameworkHints, structuralChanges = [],
328
366
  lines: tokenChanges.map(tokenChangeLine),
329
367
  });
330
368
  }
331
- if (componentChanges.length > 0) {
332
- const lines = [];
333
- for (const change of componentChanges) {
334
- const target = change.target;
335
- const source = formatSourceLocation(coordinatePolicy, target);
336
- lines.push(`### ${target.componentName} invocation (${source})`);
337
- lines.push(componentChangeLine(change));
338
- lines.push(` - Component contract: \`${target.componentId}\``);
339
- if (change.evidence) {
340
- lines.push(` - Rendered evidence: ${change.evidence.mountedCount} mounted outputs`);
341
- if (change.evidence.props)
342
- lines.push(` - Props evidence: ${promptText(boundedEvidence(change.evidence.props) ?? "")}`);
343
- if (change.evidence.ariaLabel)
344
- lines.push(` - Accessible name evidence: ${promptText(boundedEvidence(change.evidence.ariaLabel) ?? "")}`);
345
- lines.push(` - Before text: ${promptText(change.evidence.beforeText)}`);
346
- }
347
- lines.push("");
369
+ const groups = new Map();
370
+ function groupFor(key, heading, tag) {
371
+ let group = groups.get(key);
372
+ if (!group) {
373
+ group = { heading, details: new Set(), lines: [] };
374
+ groups.set(key, group);
348
375
  }
349
- sections.push({ heading: "Component prop changes", lines });
376
+ if (tag)
377
+ group.heading = elementLabel(tag);
378
+ return group;
350
379
  }
351
- if (elementGroups.length > 0) {
352
- const lines = [];
353
- for (const group of elementGroups) {
354
- const state = group.changes[0]?.state ?? "base";
355
- lines.push(`### ${group.cid} (${elementGroupSource(group, coordinatePolicy)}) · ${state}`);
356
- if (group.instanceOverride) {
357
- lines.push(`- Applies only to ${renderedInstanceDescription(group.instanceOverride.target)}.`);
358
- }
359
- lines.push(...runtimeEvidenceLines(group));
360
- for (const change of group.changes) {
361
- lines.push(elementChangeLine(change));
380
+ for (const change of componentChanges) {
381
+ const target = change.target;
382
+ const evidence = change.evidence;
383
+ const scope = change.scope ?? "source-site";
384
+ const key = JSON.stringify(["component", target.callsiteId, scope, scope === "rendered-instance"
385
+ ? [evidence?.occurrence, evidence?.props, evidence?.ariaLabel] : null]);
386
+ const group = groupFor(key, `${target.componentName} component`);
387
+ group.lines.push(componentChangeLine(change));
388
+ group.lines.push(editScopeLine(scope));
389
+ group.details.add(`- Source: ${promptText(formatSourceLocation(coordinatePolicy, target))}`);
390
+ if (evidence) {
391
+ group.details.add(`- Rendered instances: ${evidence.mountedCount}`);
392
+ if (evidence.props)
393
+ group.details.add(`- Props: ${promptText(boundedEvidence(evidence.props) ?? "")}`);
394
+ if (evidence.ariaLabel)
395
+ group.details.add(`- Accessible name: ${promptText(boundedEvidence(evidence.ariaLabel) ?? "")}`);
396
+ group.details.add(`- Before text: ${promptText(evidence.beforeText)}`);
397
+ }
398
+ }
399
+ const selectedSets = new Map();
400
+ for (const element of elementGroups) {
401
+ if (!element.instanceOverride && element.changes[0]?.sourceInstanceCount !== 1)
402
+ continue;
403
+ const key = selectedStyleGroupKey(element);
404
+ const selected = selectedSets.get(key) ?? [];
405
+ selected.push(element);
406
+ selectedSets.set(key, selected);
407
+ }
408
+ const exportedSets = new Set();
409
+ for (const element of elementGroups) {
410
+ const first = element.changes[0];
411
+ const runtimeCreated = !!element.runtimeEvidence || !element.file || isRuntimeGeneratedSource(element.file);
412
+ const src = runtimeCreated ? "" : `${element.file}:${element.line}:${element.column}`;
413
+ const key = element.instanceOverride ? refPromptKey(element.instanceOverride.target)
414
+ : first.scope === "rendered-instance" && !runtimeCreated ? `style:${element.key}`
415
+ : elementPromptKey(element.cid, src, "source-site");
416
+ const tag = element.runtimeEvidence?.tagName ?? element.sourceEvidence?.tagName;
417
+ const label = readableTargetLabel(element.runtimeEvidence?.text ?? element.sourceEvidence?.text ?? null, tag);
418
+ const selectedKey = selectedStyleGroupKey(element);
419
+ const selected = element.instanceOverride || first.sourceInstanceCount === 1 ? selectedSets.get(selectedKey) ?? [] : [];
420
+ if (selected.length > 1) {
421
+ if (exportedSets.has(selectedKey))
422
+ continue;
423
+ exportedSets.add(selectedKey);
424
+ const tags = new Set(selected.map((item) => item.runtimeEvidence?.tagName ?? item.sourceEvidence?.tagName));
425
+ const group = groupFor(selectedKey, setHeading("Selected elements", tags.size === 1 ? tag : undefined));
426
+ const locations = selected.map((item) => !item.runtimeEvidence && item.file && !isRuntimeGeneratedSource(item.file)
427
+ ? formatSourceLocation(coordinatePolicy, item) : null);
428
+ const sharedLocation = new Set(locations).size === 1 ? locations[0] : null;
429
+ if (sharedLocation)
430
+ group.details.add(`- Source: ${promptText(sharedLocation)}`);
431
+ group.details.add(["- Selected items:", ...selected.map((item, index) => selectedItemLine(item, sharedLocation ? null : locations[index] ?? null))].join("\n"));
432
+ for (const change of element.changes) {
433
+ group.lines.push(`${elementChangeLine(change)} for these selected items.`);
362
434
  const intent = sourceIntentLine(change);
363
435
  if (intent)
364
- lines.push(intent);
436
+ group.lines.push(intent);
365
437
  }
366
- lines.push("");
438
+ continue;
367
439
  }
368
- sections.push({ heading: "Changes", lines });
369
- }
370
- if (textChanges.length > 0) {
371
- const lines = [];
372
- for (const change of textChanges) {
373
- const runtimeCreated = isRuntimeGeneratedSource(change.target.sourceSite.src);
374
- const textSource = runtimeCreated
375
- ? "source unknown; runtime-created DOM"
376
- : formatSourceLocation(coordinatePolicy, {
377
- cid: change.target.sourceSite.cid,
378
- file: change.source.file,
379
- line: change.source.line,
380
- column: change.source.column,
381
- });
382
- lines.push(`### ${change.source.component || change.target.sourceSite.cid} (${textSource})`);
383
- lines.push(`- Rendered text: ${promptText(change.before)} → ${promptText(change.after)} — ${textAuthorshipGuidance(change.authoredAs)}`);
384
- lines.push(...textScopeLines(change));
385
- lines.push(...textEvidenceLines(change.target));
386
- lines.push("");
440
+ const linked = !element.instanceOverride && !runtimeCreated && first.scope !== "rendered-instance"
441
+ && (element.changes.some((change) => (change.sourceInstanceCount ?? 1) > 1)
442
+ || first.scope === "source-site" && first.sourceInstanceCount === undefined);
443
+ const group = groupFor(key, label, tag);
444
+ if (linked)
445
+ group.heading = setHeading("Linked elements", tag);
446
+ if (!runtimeCreated) {
447
+ group.details.add(`- Source: \`${formatSourceLocation(coordinatePolicy, element)}\``);
448
+ }
449
+ for (const detail of elementEvidenceLines(element, linked))
450
+ group.details.add(detail);
451
+ for (const change of element.changes) {
452
+ const targetSet = linked ? " for all items rendered at this source location."
453
+ : element.instanceOverride ? " for this selected item." : "";
454
+ group.lines.push(`${elementChangeLine(change)}${targetSet}`);
455
+ const intent = sourceIntentLine(change);
456
+ if (intent)
457
+ group.lines.push(intent);
387
458
  }
388
- sections.push({ heading: "Rendered text changes", lines });
389
459
  }
390
- if (structuralIntent.length > 0) {
391
- sections.push({
392
- heading: "Structural changes",
393
- lines: structuralIntent.map(structuralChangeLine),
460
+ for (const change of textChanges) {
461
+ const target = change.target;
462
+ const runtimeCreated = !target.sourceSite.src || isRuntimeGeneratedSource(target.sourceSite.src);
463
+ const scope = change.scope ?? "rendered-instance";
464
+ const key = elementPromptKey(target.sourceSite.cid, target.sourceSite.src, scope, {
465
+ kind: "evidence", occurrence: target.occurrence, props: target.props, text: target.beforeText, ariaLabel: target.ariaLabel,
394
466
  });
395
- }
396
- if (comments.length > 0) {
397
- sections.push({
398
- heading: "Element comments",
399
- lines: comments.flatMap((comment) => [
400
- `### <${comment.tag}> (${comment.target.sourceSite.src || "source unknown"})`,
401
- `- Page: ${promptText(comment.route)}`,
402
- `- Target: ${renderedInstanceDescription(comment.target)}`,
403
- `- Note: ${promptText(comment.note)}`,
404
- "",
405
- ]),
467
+ const group = groupFor(key, readableTargetLabel(change.before, target.tagName), target.tagName);
468
+ const sourceLocation = runtimeCreated ? null : formatSourceLocation(coordinatePolicy, {
469
+ cid: target.sourceSite.cid, ...change.source,
406
470
  });
471
+ if (sourceLocation)
472
+ group.details.add(`- Source: ${promptText(sourceLocation)}`);
473
+ group.lines.push(`- **Change:** Replace text ${promptText(change.before)} with ${promptText(change.after)}.`);
474
+ if (change.scope)
475
+ group.lines.push(editScopeLine(change.scope));
476
+ if (change.authoredAs === "expression")
477
+ group.lines.push(" - Preserve the expression and update its source logic.");
478
+ group.lines.push(...textBindingLines(change, sourceLocation));
479
+ for (const detail of targetEvidenceLines({
480
+ ...target, text: group.heading.startsWith("Text:") ? null : target.beforeText,
481
+ }))
482
+ group.details.add(detail);
483
+ }
484
+ for (const change of structuralIntent) {
485
+ const ref = change.target;
486
+ const group = groupFor(refPromptKey(ref), readableTargetLabel(ref.locator.text));
487
+ group.lines.push(structuralChangeLine(change));
488
+ }
489
+ for (const comment of comments) {
490
+ const ref = comment.target;
491
+ const runtimeCreated = !ref.sourceSite.src || isRuntimeGeneratedSource(ref.sourceSite.src);
492
+ const group = groupFor(refPromptKey(ref), elementLabel(comment.tag), comment.tag);
493
+ if (!runtimeCreated) {
494
+ const parsed = /^(.*):(\d+):(\d+)$/.exec(ref.sourceSite.src);
495
+ const location = parsed ? formatSourceLocation(coordinatePolicy, {
496
+ cid: ref.sourceSite.cid, file: parsed[1], line: Number(parsed[2]), column: Number(parsed[3]),
497
+ }) : ref.sourceSite.src;
498
+ group.details.add(`- Source: ${promptText(location)}`);
499
+ }
500
+ group.details.add(`- Page: ${promptText(comment.route)}`);
501
+ for (const detail of targetEvidenceLines(ref.locator))
502
+ group.details.add(detail);
503
+ group.lines.push(`- **Note:** ${promptText(comment.note)}`);
504
+ }
505
+ for (const group of groups.values()) {
506
+ sections.push({ heading: group.heading, lines: group.details.size > 0 ? [...group.details, "", ...group.lines] : group.lines });
407
507
  }
408
508
  return renderPrompt(sections, customInstructions);
409
509
  }
@@ -0,0 +1,3 @@
1
+ export declare const PROMPT_TITLE = "# Requested design changes";
2
+ /** Keeps the title first when a host adds page or iteration instructions. */
3
+ export declare function withPromptContext(context: readonly string[], prompt: string): string;
@@ -0,0 +1,8 @@
1
+ export const PROMPT_TITLE = "# Requested design changes";
2
+ /** Keeps the title first when a host adds page or iteration instructions. */
3
+ export function withPromptContext(context, prompt) {
4
+ const body = prompt === PROMPT_TITLE || prompt.startsWith(`${PROMPT_TITLE}\n`)
5
+ ? prompt.slice(PROMPT_TITLE.length).trimStart()
6
+ : prompt;
7
+ return [PROMPT_TITLE, "", ...context, ...(body.trim() ? ["", body] : [])].join("\n");
8
+ }
@@ -1,3 +1,5 @@
1
+ import { inspectorStorage } from "../runtime/inspectorStorage.js";
2
+ import { isDemoRuntime } from "../runtime/runtimeConfig.js";
1
3
  /** The default guidance included in every generated prompt. */
2
4
  export const DEFAULT_CUSTOM_INSTRUCTIONS = "Preserve existing tokens, logical properties, and CSS intent while applying these rendered changes.";
3
5
  function storageKey(projectId) {
@@ -5,10 +7,10 @@ function storageKey(projectId) {
5
7
  }
6
8
  /** Reads the project-scoped custom prompt instructions from browser storage. */
7
9
  export function loadCustomInstructions(projectId) {
8
- if (typeof localStorage === "undefined")
10
+ if (isDemoRuntime())
9
11
  return DEFAULT_CUSTOM_INSTRUCTIONS;
10
12
  try {
11
- return localStorage.getItem(storageKey(projectId)) ?? DEFAULT_CUSTOM_INSTRUCTIONS;
13
+ return inspectorStorage()?.getItem(storageKey(projectId)) ?? DEFAULT_CUSTOM_INSTRUCTIONS;
12
14
  }
13
15
  catch {
14
16
  return DEFAULT_CUSTOM_INSTRUCTIONS;
@@ -16,10 +18,10 @@ export function loadCustomInstructions(projectId) {
16
18
  }
17
19
  /** Persists custom prompt instructions without making storage availability a prerequisite for editing. */
18
20
  export function saveCustomInstructions(projectId, instructions) {
19
- if (typeof localStorage === "undefined")
20
- return;
21
+ if (isDemoRuntime())
22
+ return; // demo runtimes never persist
21
23
  try {
22
- localStorage.setItem(storageKey(projectId), instructions);
24
+ inspectorStorage()?.setItem(storageKey(projectId), instructions);
23
25
  }
24
26
  catch {
25
27
  // Storage can be disabled or full; the in-memory UI state still applies.
@@ -0,0 +1,17 @@
1
+ /** The synchronous subset of `Storage` that inspector persistence uses. */
2
+ export interface InspectorStorage {
3
+ getItem(key: string): string | null;
4
+ setItem(key: string, value: string): void;
5
+ removeItem(key: string): void;
6
+ /** Reloads an asynchronous backend under the lease, before controller hydration. */
7
+ refresh?(): Promise<void>;
8
+ }
9
+ /** Replaces `localStorage` for inspector-owned state, for example with extension storage. */
10
+ export declare function configureInspectorStorage(storage: InspectorStorage | null): void;
11
+ /** The configured storage, else `localStorage`; null where neither exists. */
12
+ export declare function inspectorStorage(): InspectorStorage | null;
13
+ /**
14
+ * Serves reads from a snapshot loaded before boot and forwards each write.
15
+ * Asynchronous backends stay behind the synchronous interface callers expect.
16
+ */
17
+ export declare function createMirroredStorage(initial: Readonly<Record<string, string>>, persist: (key: string, value: string | null) => void, read?: () => Promise<Readonly<Record<string, string>>>): InspectorStorage;
@@ -0,0 +1,42 @@
1
+ let configured = null;
2
+ /** Replaces `localStorage` for inspector-owned state, for example with extension storage. */
3
+ export function configureInspectorStorage(storage) {
4
+ configured = storage;
5
+ }
6
+ /** The configured storage, else `localStorage`; null where neither exists. */
7
+ export function inspectorStorage() {
8
+ if (configured)
9
+ return configured;
10
+ return typeof localStorage === "undefined" ? null : localStorage;
11
+ }
12
+ /**
13
+ * Serves reads from a snapshot loaded before boot and forwards each write.
14
+ * Asynchronous backends stay behind the synchronous interface callers expect.
15
+ */
16
+ export function createMirroredStorage(initial, persist, read) {
17
+ const values = new Map(Object.entries(initial));
18
+ let refreshId = 0;
19
+ return {
20
+ getItem: (key) => values.get(key) ?? null,
21
+ setItem(key, value) {
22
+ values.set(key, value);
23
+ persist(key, value);
24
+ },
25
+ removeItem(key) {
26
+ if (!values.delete(key))
27
+ return;
28
+ persist(key, null);
29
+ },
30
+ ...(read ? {
31
+ async refresh() {
32
+ const id = ++refreshId;
33
+ const snapshot = await read();
34
+ if (id !== refreshId)
35
+ return;
36
+ values.clear();
37
+ for (const [key, value] of Object.entries(snapshot))
38
+ values.set(key, value);
39
+ },
40
+ } : {}),
41
+ };
42
+ }