nudge-ui 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/bin/nudge-ui.mjs +3 -1
  2. package/dist/cli/agentCommand.d.ts +8 -0
  3. package/dist/cli/agentCommand.js +59 -0
  4. package/dist/client.mjs +872 -12
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
  6. package/dist/hosts/astro/clientTransport.js +60 -11
  7. package/dist/hosts/next/sidecar.d.ts +4 -0
  8. package/dist/hosts/next/sidecar.js +49 -6
  9. package/dist/hosts/next/wrapper.js +30 -2
  10. package/dist/hosts/projectBridge.d.ts +20 -0
  11. package/dist/hosts/projectBridge.js +97 -0
  12. package/dist/hosts/static/cli.d.ts +1 -1
  13. package/dist/hosts/static/cli.js +6 -1
  14. package/dist/hosts/static/manifest.d.ts +1 -1
  15. package/dist/hosts/static/manifest.js +1 -1
  16. package/dist/hosts/static/server.js +44 -3
  17. package/dist/hosts/vite/vite.d.ts +5 -1
  18. package/dist/hosts/vite/vite.js +78 -14
  19. package/dist/inspector/agent/McpConnectionDialog.d.ts +2 -2
  20. package/dist/inspector/agent/McpConnectionDialog.js +11 -35
  21. package/dist/inspector/agent/client.d.ts +10 -3
  22. package/dist/inspector/agent/client.js +80 -9
  23. package/dist/inspector/agent/connectionStatus.js +3 -3
  24. package/dist/inspector/agent/httpTransport.d.ts +2 -0
  25. package/dist/inspector/agent/httpTransport.js +37 -17
  26. package/dist/inspector/agent/protocol.d.ts +8 -3
  27. package/dist/inspector/agent/protocol.js +1 -1
  28. package/dist/inspector/agent/verification.d.ts +1 -1
  29. package/dist/inspector/agent/verification.js +69 -12
  30. package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
  31. package/dist/inspector/canvas/CanvasCard.js +181 -91
  32. package/dist/inspector/canvas/CanvasElementOverlay.js +246 -52
  33. package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
  34. package/dist/inspector/canvas/CanvasToolbar.js +24 -0
  35. package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
  36. package/dist/inspector/canvas/CanvasWorkspace.js +392 -73
  37. package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
  38. package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
  39. package/dist/inspector/canvas/canvasResize.d.ts +30 -0
  40. package/dist/inspector/canvas/canvasResize.js +52 -0
  41. package/dist/inspector/canvas/canvasStore.d.ts +30 -6
  42. package/dist/inspector/canvas/canvasStore.js +128 -61
  43. package/dist/inspector/canvas/frameProtocol.d.ts +69 -5
  44. package/dist/inspector/canvas/frameProtocol.js +56 -2
  45. package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -0
  46. package/dist/inspector/canvas/keyboardShortcuts.js +14 -0
  47. package/dist/inspector/canvas/projection.d.ts +7 -0
  48. package/dist/inspector/canvas/projection.js +118 -25
  49. package/dist/inspector/canvas/rendererBootstrap.js +29 -29
  50. package/dist/inspector/canvas/rendererElementSelector.js +341 -30
  51. package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
  52. package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
  53. package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
  54. package/dist/inspector/canvas/sessionStore.d.ts +1 -0
  55. package/dist/inspector/canvas/sessionStore.js +12 -4
  56. package/dist/inspector/canvas/staleChangeDetector.js +16 -14
  57. package/dist/inspector/changes/changesLog.js +31 -1
  58. package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
  59. package/dist/inspector/changes/managedStyleProjection.js +3 -3
  60. package/dist/inspector/changes/structuralTypes.d.ts +4 -0
  61. package/dist/inspector/clientManifest.d.ts +5 -0
  62. package/dist/inspector/clientManifest.js +11 -0
  63. package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
  64. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
  65. package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
  66. package/dist/inspector/index.d.ts +2 -0
  67. package/dist/inspector/index.js +74 -24
  68. package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
  69. package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
  70. package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
  71. package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
  72. package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
  73. package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
  74. package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
  75. package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
  76. package/dist/inspector/inline-text/textEditChange.js +165 -0
  77. package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
  78. package/dist/inspector/inspection/selectionProperty.js +1 -0
  79. package/dist/inspector/overlay/selectedGeometry.js +21 -2
  80. package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
  81. package/dist/inspector/overlay/spacingGestures.js +353 -0
  82. package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
  83. package/dist/inspector/overlay/structuralGestures.js +12 -10
  84. package/dist/inspector/projection/managedStylesheet.js +4 -1
  85. package/dist/inspector/projection/renderedInstance.d.ts +2 -0
  86. package/dist/inspector/projection/renderedInstance.js +5 -1
  87. package/dist/inspector/projection/structuralProjection.d.ts +2 -0
  88. package/dist/inspector/projection/structuralProjection.js +85 -1
  89. package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
  90. package/dist/inspector/projection/textProjection.d.ts +2 -0
  91. package/dist/inspector/projection/textProjection.js +10 -4
  92. package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
  93. package/dist/inspector/prompt/clipboardHandoff.js +3 -3
  94. package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
  95. package/dist/inspector/prompt/generatePrompt.js +19 -2
  96. package/dist/inspector/runtime/editorShell.d.ts +2 -0
  97. package/dist/inspector/runtime/editorShell.js +4 -0
  98. package/dist/inspector/runtime/runtimeConfig.d.ts +4 -0
  99. package/dist/inspector/runtime/runtimeConfig.js +4 -0
  100. package/dist/inspector/selection/selectionStore.js +2 -0
  101. package/dist/inspector/settings/SettingsDialog.js +10 -0
  102. package/dist/inspector/shell/AppShell.js +5 -1
  103. package/dist/inspector/shell/ChangesLog.js +8 -4
  104. package/dist/inspector/shell/CopyPromptButton.js +95 -19
  105. package/dist/inspector/shell/EmptyState.js +1 -1
  106. package/dist/inspector/shell/InspectorShell.d.ts +0 -1
  107. package/dist/inspector/shell/InspectorShell.js +37 -38
  108. package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
  109. package/dist/inspector/shell/PeekOriginalButton.js +57 -0
  110. package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
  111. package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
  112. package/dist/inspector/shell/originalPreview.d.ts +9 -0
  113. package/dist/inspector/shell/originalPreview.js +61 -0
  114. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  115. package/dist/inspector/shell/shortcuts.js +15 -0
  116. package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
  117. package/dist/inspector/sketch/SketchChanges.js +72 -0
  118. package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
  119. package/dist/inspector/sketch/SketchEditor.js +402 -0
  120. package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
  121. package/dist/inspector/sketch/SketchEntryButton.js +11 -0
  122. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
  123. package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
  124. package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
  125. package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
  126. package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
  127. package/dist/inspector/sketch/SketchOverlay.js +420 -0
  128. package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
  129. package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
  130. package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
  131. package/dist/inspector/sketch/SketchWorkspace.js +152 -0
  132. package/dist/inspector/sketch/capture.d.ts +55 -0
  133. package/dist/inspector/sketch/capture.js +350 -0
  134. package/dist/inspector/sketch/freehand.d.ts +13 -0
  135. package/dist/inspector/sketch/freehand.js +64 -0
  136. package/dist/inspector/sketch/geometry.d.ts +17 -0
  137. package/dist/inspector/sketch/geometry.js +98 -0
  138. package/dist/inspector/sketch/handoff.d.ts +21 -0
  139. package/dist/inspector/sketch/handoff.js +72 -0
  140. package/dist/inspector/sketch/interaction.d.ts +21 -0
  141. package/dist/inspector/sketch/interaction.js +98 -0
  142. package/dist/inspector/sketch/model.d.ts +104 -0
  143. package/dist/inspector/sketch/model.js +282 -0
  144. package/dist/inspector/sketch/persistence.d.ts +21 -0
  145. package/dist/inspector/sketch/persistence.js +205 -0
  146. package/dist/inspector/sketch/raster.d.ts +22 -0
  147. package/dist/inspector/sketch/raster.js +203 -0
  148. package/dist/inspector/sketch/sketchNote.d.ts +10 -0
  149. package/dist/inspector/sketch/sketchNote.js +30 -0
  150. package/dist/inspector/sketch/store.d.ts +47 -0
  151. package/dist/inspector/sketch/store.js +409 -0
  152. package/dist/inspector/styleEditors/BorderEditor.js +15 -19
  153. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
  154. package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
  155. package/dist/inspector/styleEditors/ColorPicker.js +11 -36
  156. package/dist/inspector/styleEditors/GapField.d.ts +3 -2
  157. package/dist/inspector/styleEditors/GapField.js +2 -2
  158. package/dist/inspector/styleEditors/GridSection.js +1 -1
  159. package/dist/inspector/styleEditors/LayoutSection.js +11 -8
  160. package/dist/inspector/styleEditors/SpacingBox.js +23 -30
  161. package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
  162. package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
  163. package/dist/inspector/styleEditors/layoutValue.js +3 -0
  164. package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
  165. package/dist/inspector/styleEditors/nudgeValue.js +91 -5
  166. package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
  167. package/dist/inspector/styleEditors/stylePresence.js +22 -0
  168. package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
  169. package/dist/inspector/styleEditors/textRelevance.js +35 -0
  170. package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
  171. package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
  172. package/dist/inspector/tokens/TokenField.js +111 -7
  173. package/dist/inspector/tokens/editActions.d.ts +2 -0
  174. package/dist/inspector/tokens/editActions.js +2 -2
  175. package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
  176. package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
  177. package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
  178. package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
  179. package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
  180. package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
  181. package/dist/inspector/tokens/resolution.d.ts +3 -26
  182. package/dist/inspector/tokens/resolution.js +44 -735
  183. package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
  184. package/dist/inspector/tokens/tokenAvailability.js +204 -0
  185. package/dist/inspector/ui/Button.d.ts +1 -1
  186. package/dist/inspector/ui/SideValuesField.js +2 -2
  187. package/dist/inspector/ui/StatusCallout.d.ts +4 -1
  188. package/dist/inspector/ui/StatusCallout.js +2 -2
  189. package/dist/inspector/ui/styles.js +13 -11
  190. package/dist/internal/agent-protocol.d.ts +65 -1
  191. package/dist/internal/agent-protocol.js +183 -3
  192. package/dist/nudge-ui.mjs +261 -57
  193. package/dist/transport/editor.d.ts +28 -0
  194. package/dist/transport/editor.js +132 -0
  195. package/dist/transport/index.d.ts +1 -0
  196. package/dist/transport/index.js +1 -0
  197. package/dist/transport/routes.d.ts +7 -0
  198. package/dist/transport/routes.js +7 -0
  199. package/package.json +3 -1
  200. package/dist/inspector/shell/panelLayout.d.ts +0 -2
  201. package/dist/inspector/shell/panelLayout.js +0 -29
@@ -13,16 +13,22 @@ import { getStructuralChanges, subscribeStructuralChanges } from "../projection/
13
13
  import { getNudgeUiRuntimeConfig } from "../runtime/runtimeConfig.js";
14
14
  import { createPromptRevision, getAgentClient, useAgentClient, } from "../agent/client.js";
15
15
  import { createAgentPresentationAdapter } from "../canvas/agentPresentation.js";
16
- import { discardAgentDispatch, getAgentDispatchSize, recordAgentDispatch, verifyAndReconcileAgentDispatch, } from "../agent/verification.js";
16
+ import { discardAgentDispatch, recordAgentDispatch, verifyAndReconcileAgentDispatch, } from "../agent/verification.js";
17
17
  import { getClipboardHandoffRevision, getLastClipboardReconciledCount, recordClipboardHandoff, subscribeClipboardHandoff, } from "../prompt/clipboardHandoff.js";
18
+ import { useSketches, markSketchesDispatching, markSketchesHandingOff, settleSketchDispatch } from "../sketch/store.js";
19
+ import { createSketchAttachments, createSketchHandoffSnapshot, sketchMetadataForHandoff, } from "../sketch/handoff.js";
20
+ import { SketchLayersPanel } from "../sketch/SketchLayersPanel.js";
21
+ import { isSendPromptShortcut, SEND_PROMPT_HOTKEY_EVENT } from "./shortcuts.js";
18
22
  export function CopyPromptButton({ settingsOpen: controlledSettingsOpen, settingsSection: controlledSettingsSection, onOpenSettings, onSettingsOpenChange, } = {}) {
19
23
  const changes = useChanges();
24
+ const sketches = useSketches();
25
+ const pendingSketches = sketches.filter((item) => item.status === "pending");
20
26
  const structuralChanges = useSyncExternalStore(subscribeStructuralChanges, getStructuralChanges, getStructuralChanges);
21
27
  useSyncExternalStore(subscribeClipboardHandoff, getClipboardHandoffRevision, getClipboardHandoffRevision);
22
28
  const reconciledCount = getLastClipboardReconciledCount();
23
29
  const [copied, setCopied] = useState(false);
24
30
  const [agentCompletionStatus, setAgentCompletionStatus] = useState(null);
25
- const [agentCompletionSent, setAgentCompletionSent] = useState(0);
31
+ const [sketchFallback, setSketchFallback] = useState(null);
26
32
  const [localSettingsOpen, setLocalSettingsOpen] = useState(false);
27
33
  const [localSettingsSection, setLocalSettingsSection] = useState("instructions");
28
34
  const settingsOpen = controlledSettingsOpen ?? localSettingsOpen;
@@ -33,7 +39,8 @@ export function CopyPromptButton({ settingsOpen: controlledSettingsOpen, setting
33
39
  const agent = useAgentClient(runtimeConfig.projectId, agentClient);
34
40
  const agentRef = useRef(agent);
35
41
  agentRef.current = agent;
36
- const hasChanges = changes.length + structuralChanges.length > 0;
42
+ const hasChanges = changes.length + structuralChanges.length + pendingSketches.length > 0;
43
+ const changeCount = changes.length + structuralChanges.length + sketches.length;
37
44
  useEffect(() => {
38
45
  setCustomInstructions(loadCustomInstructions(runtimeConfig.projectId));
39
46
  }, [runtimeConfig.projectId]);
@@ -52,7 +59,6 @@ export function CopyPromptButton({ settingsOpen: controlledSettingsOpen, setting
52
59
  const revision = agent.request?.changeRevision;
53
60
  if (agent.state !== "completed" || revision === undefined)
54
61
  return;
55
- const sent = getAgentDispatchSize(revision);
56
62
  let active = true;
57
63
  void verifyAndReconcileAgentDispatch(revision)
58
64
  .then((removed) => {
@@ -61,8 +67,7 @@ export function CopyPromptButton({ settingsOpen: controlledSettingsOpen, setting
61
67
  const latest = agentRef.current;
62
68
  if (latest.request?.changeRevision !== revision || latest.state !== "completed")
63
69
  return;
64
- setAgentCompletionSent(sent);
65
- setAgentCompletionStatus(removed > 0 ? "verified" : "completed");
70
+ setAgentCompletionStatus(removed > 0 ? "verified" : null);
66
71
  })
67
72
  .catch(() => {
68
73
  if (!active)
@@ -70,13 +75,18 @@ export function CopyPromptButton({ settingsOpen: controlledSettingsOpen, setting
70
75
  const latest = agentRef.current;
71
76
  if (latest.request?.changeRevision !== revision || latest.state !== "completed")
72
77
  return;
73
- setAgentCompletionSent(sent);
74
- setAgentCompletionStatus("completed");
78
+ setAgentCompletionStatus(null);
75
79
  });
76
80
  return () => {
77
81
  active = false;
78
82
  };
79
83
  }, [agent.request?.changeRevision, agent.state]);
84
+ useEffect(() => {
85
+ const request = agent.request;
86
+ if (!request?.sketches || request.sketches.length === 0)
87
+ return;
88
+ void settleSketchDispatch(request.changeRevision, request.status, request.requestId, request.clientDispatchId).catch(() => undefined);
89
+ }, [agent.request?.changeRevision, agent.request?.requestId, agent.request?.clientDispatchId, agent.request?.sketches, agent.request?.status]);
80
90
  const connecting = agent.state === "pairing";
81
91
  const working = agent.state === "working" || agent.request?.status === "working";
82
92
  const canConnect = agent.companionReachable
@@ -105,7 +115,7 @@ export function CopyPromptButton({ settingsOpen: controlledSettingsOpen, setting
105
115
  ? _jsx(IconPlugConnected, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" })
106
116
  : canSend || working
107
117
  ? _jsx(IconSend, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" })
108
- : _jsx(IconCopy, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" });
118
+ : _jsx(IconCopy, { size: "var(--icon-size-small)", stroke: "var(--icon-stroke-width)", "aria-hidden": "true" });
109
119
  async function onClick() {
110
120
  if (disabled)
111
121
  return;
@@ -117,22 +127,83 @@ export function CopyPromptButton({ settingsOpen: controlledSettingsOpen, setting
117
127
  framework: runtimeConfig.framework,
118
128
  stylingSystem: runtimeConfig.stylingSystem,
119
129
  };
120
- const text = generatePrompt(changes, hints, structuralChanges, customInstructions);
130
+ const sketchHandoff = createSketchHandoffSnapshot(pendingSketches);
131
+ const sketchMetadata = sketchHandoff ? sketchMetadataForHandoff(sketchHandoff) : [];
132
+ const text = generatePrompt(changes, hints, structuralChanges, customInstructions, sketchMetadata);
121
133
  setAgentCompletionStatus(null);
122
- setAgentCompletionSent(0);
134
+ setSketchFallback(null);
123
135
  if (canSend) {
124
- const revision = createPromptRevision(changes, structuralChanges);
136
+ const revision = createPromptRevision(changes, structuralChanges, sketchMetadata);
125
137
  recordAgentDispatch(revision, changes, structuralChanges);
126
- const response = await agentClient.dispatchPrompt(text, revision);
127
- if (response)
128
- return;
138
+ const clientDispatchId = sketchHandoff?.localBatchId ?? `dispatch-${Date.now()}-${Math.random().toString(36).slice(2)}`;
139
+ let attachments = undefined;
140
+ try {
141
+ if (sketchHandoff) {
142
+ await markSketchesDispatching(sketchHandoff.entries.map((entry) => ({ id: entry.id, revision: entry.revision })), revision, sketchHandoff.localBatchId);
143
+ attachments = await createSketchAttachments(sketchHandoff);
144
+ }
145
+ const response = await agentClient.dispatchPrompt(text, revision, {
146
+ clientDispatchId,
147
+ ...(attachments === undefined ? {} : { attachments }),
148
+ });
149
+ if (response) {
150
+ if (sketchHandoff) {
151
+ await markSketchesHandingOff(sketchHandoff.entries.map((entry) => ({ id: entry.id, revision: entry.revision })), revision, sketchHandoff.localBatchId, response.request.requestId);
152
+ }
153
+ return;
154
+ }
155
+ if (sketchHandoff) {
156
+ await settleSketchDispatch(revision, "failed", undefined, sketchHandoff.localBatchId);
157
+ discardAgentDispatch(revision);
158
+ const latestAgent = agentClient.getSnapshot();
159
+ setSketchFallback({
160
+ prompt: text,
161
+ error: latestAgent.request?.error
162
+ ?? latestAgent.error
163
+ ?? "The agent did not accept the sketch attachments.",
164
+ });
165
+ return;
166
+ }
167
+ }
168
+ catch (error) {
169
+ discardAgentDispatch(revision);
170
+ if (sketchHandoff) {
171
+ await settleSketchDispatch(revision, "failed", undefined, sketchHandoff.localBatchId).catch(() => undefined);
172
+ setSketchFallback({
173
+ prompt: text,
174
+ error: error instanceof Error ? error.message : "The sketch could not be sent to the agent.",
175
+ });
176
+ return;
177
+ }
178
+ }
129
179
  discardAgentDispatch(revision);
130
180
  }
131
181
  await copyToClipboard(text);
132
- recordClipboardHandoff(changes, structuralChanges);
182
+ if (changes.length > 0 || structuralChanges.length > 0) {
183
+ recordClipboardHandoff(changes, structuralChanges);
184
+ }
133
185
  setCopied(true);
134
186
  window.setTimeout(() => setCopied(false), 1500);
135
187
  }
188
+ useEffect(() => {
189
+ function onKeydown(event) {
190
+ if (!isSendPromptShortcut(event) || !canSend || !hasChanges || disabled)
191
+ return;
192
+ event.preventDefault();
193
+ void onClick();
194
+ }
195
+ function onRendererHotkey() {
196
+ if (!canSend || !hasChanges || disabled)
197
+ return;
198
+ void onClick();
199
+ }
200
+ window.addEventListener("keydown", onKeydown, true);
201
+ window.addEventListener(SEND_PROMPT_HOTKEY_EVENT, onRendererHotkey);
202
+ return () => {
203
+ window.removeEventListener("keydown", onKeydown, true);
204
+ window.removeEventListener(SEND_PROMPT_HOTKEY_EVENT, onRendererHotkey);
205
+ };
206
+ }, [canSend, disabled, hasChanges, onClick]);
136
207
  function handleCustomInstructionsChange(value) {
137
208
  setCustomInstructions(value);
138
209
  saveCustomInstructions(runtimeConfig.projectId, value);
@@ -153,7 +224,12 @@ export function CopyPromptButton({ settingsOpen: controlledSettingsOpen, setting
153
224
  function openMcpConnection() {
154
225
  openSettings("mcp");
155
226
  }
156
- return (_jsxs("div", { className: "copy-prompt__stack", "data-test": "copy-prompt-control", children: [_jsxs(Button, { variant: "primary", className: "copy-prompt__main", "data-test": "copy-prompt", type: "button", disabled: disabled, "data-copied": copied ? "true" : "false", "data-agent-state": agent.state, "aria-busy": working || connecting ? "true" : undefined, title: agent.error, onClick: onClick, children: [icon, label] }), statusAction ? (_jsx(StatusCallout, { className: "copy-prompt__agent-status", tone: agentStatus.tone, "data-test": "agent-connection-status", children: _jsxs("div", { className: "copy-prompt__agent-status-content", children: [_jsx("span", { role: "status", children: agentStatus.label }), _jsx(Button, { size: "compact", variant: "quiet", "data-test": "agent-status-action", "data-action": statusAction.kind, type: "button", onClick: openMcpConnection, children: statusAction.label })] }) })) : agentStatus.kind === "listening" ? (_jsxs("div", { className: "copy-prompt__agent-status copy-prompt__agent-status--listening", "data-test": "agent-connection-status", role: "status", children: [_jsx(IconCheck, { size: "var(--icon-size-small)", stroke: 2, "aria-hidden": "true" }), _jsx("span", { children: agentStatus.label })] })) : null, reconciledCount > 0 ? (_jsxs("p", { className: "copy-prompt__hint", "data-test": "clipboard-reconciled-hint", role: "status", children: ["Removed ", reconciledCount, " implemented ", reconciledCount === 1 ? "change" : "changes", " from the next prompt."] })) : null, agentCompletionStatus === "verified" ? (_jsx("p", { className: "copy-prompt__hint", "data-test": "agent-verified-hint", role: "status", children: "Agent changes verified." })) : agentCompletionStatus === "completed" ? (_jsx("p", { className: "copy-prompt__hint", "data-test": "agent-completed-hint", role: "status", children: agentCompletionSent > 0
157
- ? "Agent completed. Remaining edits were preserved because they were not verified."
158
- : "Agent completed." })) : null, _jsx(SettingsDialog, { open: settingsOpen, initialSection: settingsSection, value: customInstructions, onValueChange: handleCustomInstructionsChange, onOpenChange: setSettingsOpen, projectId: runtimeConfig.projectId, origin: window.location.origin, snapshot: agent, onConnect: () => agentClient.connect(), onDisconnect: () => agentClient.disconnect(), onCheckAgain: () => agentClient.checkConnection() })] }));
227
+ return (_jsxs("div", { className: "copy-prompt__stack", "data-test": "copy-prompt-control", children: [_jsxs(Button, { variant: "primary", className: "copy-prompt__main", "data-test": "copy-prompt", type: "button", disabled: disabled, "data-copied": copied ? "true" : "false", "data-agent-state": agent.state, "data-agent-listening": agentStatus.kind === "listening" ? "true" : undefined, "aria-busy": working || connecting ? "true" : undefined, title: agent.error, onClick: onClick, children: [icon, label, changeCount > 0 ? (_jsx("span", { className: "copy-prompt__change-count", "data-test": "copy-prompt-change-count", children: changeCount })) : null, agentStatus.kind === "listening" ? (_jsx("span", { className: "copy-prompt__agent-listening", "data-test": "agent-listening-indicator", role: "status", "aria-label": "Agent listening" })) : null] }), _jsx(SketchLayersPanel, {}), statusAction ? (_jsx(StatusCallout, { className: "copy-prompt__agent-status", tone: agentStatus.tone, "data-test": "agent-connection-status", children: _jsxs("div", { className: "copy-prompt__agent-status-content", children: [_jsx("span", { role: "status", children: agentStatus.label }), _jsx(Button, { size: "compact", variant: "quiet", "data-test": "agent-status-action", "data-action": statusAction.kind, type: "button", onClick: openMcpConnection, children: statusAction.label })] }) })) : null, reconciledCount > 0 ? (_jsxs("p", { className: "copy-prompt__hint copy-prompt__hint--centered", "data-test": "clipboard-reconciled-hint", role: "status", children: [reconciledCount, " Implemented changes"] })) : null, agentCompletionStatus === "verified" ? (_jsx("p", { className: "copy-prompt__hint", "data-test": "agent-verified-hint", role: "status", children: "Agent changes verified." })) : null, sketchFallback ? (_jsxs(StatusCallout, { className: "copy-prompt__sketch-fallback", tone: "warning", "data-test": "sketch-dispatch-error", children: [_jsx("span", { children: sketchFallback.error }), _jsx(Button, { size: "compact", variant: "secondary", type: "button", "data-test": "sketch-copy-fallback", onClick: () => {
228
+ void copyToClipboard(sketchFallback.prompt).then(() => {
229
+ if (changes.length > 0 || structuralChanges.length > 0) {
230
+ recordClipboardHandoff(changes, structuralChanges);
231
+ }
232
+ setSketchFallback(null);
233
+ }).catch(() => undefined);
234
+ }, children: "Copy prompt" })] })) : null, _jsx(SettingsDialog, { open: settingsOpen, initialSection: settingsSection, value: customInstructions, onValueChange: handleCustomInstructionsChange, onOpenChange: setSettingsOpen, projectId: runtimeConfig.projectId, origin: window.location.origin, snapshot: agent, onConnect: () => agentClient.connect(), onDisconnect: () => agentClient.disconnect(), onCheckAgain: () => agentClient.checkConnection() })] }));
159
235
  }
@@ -11,5 +11,5 @@ export function EmptyState() {
11
11
  const modifierName = macPlatform ? "Command" : "Control";
12
12
  const optionKey = macPlatform ? "⌥" : "Alt";
13
13
  const optionName = macPlatform ? "Option" : "Alt";
14
- return (_jsxs("div", { className: "empty-state", "data-test": "empty-state", children: [_jsx("img", { className: "empty-state__icon", src: gridIllustration, alt: "", "aria-hidden": "true" }), _jsx("h2", { className: "empty-state__title", children: "Select an element to edit" }), _jsx("div", { className: "empty-state__shortcuts", children: _jsxs("ul", { className: "empty-state__shortcut-list", children: [_jsxs("li", { className: "empty-state__shortcut", "data-test": "empty-state-shortcut", children: [_jsx("span", { children: "Nudge" }), _jsxs("span", { className: "empty-state__shortcut-keys", "aria-label": "Arrow keys", children: [_jsx("kbd", { children: "\u2191" }), _jsx("kbd", { children: "\u2193" }), _jsx("kbd", { children: "\u2190" }), _jsx("kbd", { children: "\u2192" })] })] }), _jsxs("li", { className: "empty-state__shortcut", "data-test": "empty-state-shortcut", children: [_jsx("span", { children: "Big Nudge (8px)" }), _jsxs("span", { className: "empty-state__shortcut-keys", "aria-label": "Shift plus arrow keys", children: [_jsx("kbd", { children: "Shift" }), _jsx("span", { "aria-hidden": "true", children: "+" }), _jsx("kbd", { children: "Arrow" })] })] }), _jsxs("li", { className: "empty-state__shortcut", "data-test": "empty-state-shortcut", children: [_jsx("span", { children: "Select deeper" }), _jsxs("span", { className: "empty-state__shortcut-keys", "aria-label": `${modifierName} plus click`, children: [_jsx("kbd", { children: modifierKey }), _jsx("span", { "aria-hidden": "true", children: "+" }), _jsx("kbd", { children: "Click" })] })] }), _jsxs("li", { className: "empty-state__shortcut", "data-test": "empty-state-shortcut", children: [_jsx("span", { children: "Measure" }), _jsxs("span", { className: "empty-state__shortcut-keys", "aria-label": `${optionName} plus hover`, children: [_jsx("kbd", { children: optionKey }), _jsx("span", { "aria-hidden": "true", children: "+" }), _jsx("kbd", { children: "Hover" })] })] }), _jsxs("li", { className: "empty-state__shortcut", "data-test": "empty-state-shortcut", children: [_jsx("span", { children: "Hide UI" }), _jsxs("span", { className: "empty-state__shortcut-keys", "aria-label": `${modifierName} plus backslash`, children: [_jsx("kbd", { children: modifierKey }), _jsx("span", { "aria-hidden": "true", children: "+" }), _jsx("kbd", { children: "\\" })] })] }), _jsxs("li", { className: "empty-state__shortcut", "data-test": "empty-state-shortcut", children: [_jsx("span", { children: "Undo" }), _jsxs("span", { className: "empty-state__shortcut-keys", "aria-label": `${modifierName} plus Z`, children: [_jsx("kbd", { children: modifierKey }), _jsx("span", { "aria-hidden": "true", children: "+" }), _jsx("kbd", { children: "Z" })] })] }), _jsxs("li", { className: "empty-state__shortcut", "data-test": "empty-state-shortcut", children: [_jsx("span", { children: "Deselect" }), _jsx("span", { className: "empty-state__shortcut-keys", children: _jsx("kbd", { children: "Esc" }) })] })] }) })] }));
14
+ return (_jsxs("div", { className: "empty-state", "data-test": "empty-state", children: [_jsx("img", { className: "empty-state__icon", src: gridIllustration, alt: "", "aria-hidden": "true" }), _jsx("h2", { className: "empty-state__title", children: "Select an element to edit" }), _jsx("div", { className: "empty-state__shortcuts", children: _jsxs("ul", { className: "empty-state__shortcut-list", children: [_jsxs("li", { className: "empty-state__shortcut", "data-test": "empty-state-shortcut", children: [_jsx("span", { children: "Nudge" }), _jsxs("span", { className: "empty-state__shortcut-keys", "aria-label": "Arrow keys", children: [_jsx("kbd", { children: "\u2191" }), _jsx("kbd", { children: "\u2193" }), _jsx("kbd", { children: "\u2190" }), _jsx("kbd", { children: "\u2192" })] })] }), _jsxs("li", { className: "empty-state__shortcut", "data-test": "empty-state-shortcut", children: [_jsx("span", { children: "Big Nudge (8px)" }), _jsxs("span", { className: "empty-state__shortcut-keys", "aria-label": "Shift plus arrow keys", children: [_jsx("kbd", { children: "Shift" }), _jsx("span", { "aria-hidden": "true", children: "+" }), _jsx("kbd", { children: "Arrow" })] })] }), _jsxs("li", { className: "empty-state__shortcut", "data-test": "empty-state-shortcut", children: [_jsx("span", { children: "Select deeper" }), _jsxs("span", { className: "empty-state__shortcut-keys", "aria-label": `${modifierName} plus click`, children: [_jsx("kbd", { children: modifierKey }), _jsx("span", { "aria-hidden": "true", children: "+" }), _jsx("kbd", { children: "Click" })] })] }), _jsxs("li", { className: "empty-state__shortcut", "data-test": "empty-state-shortcut", children: [_jsx("span", { children: "Measure" }), _jsxs("span", { className: "empty-state__shortcut-keys", "aria-label": `${optionName} plus hover`, children: [_jsx("kbd", { children: optionKey }), _jsx("span", { "aria-hidden": "true", children: "+" }), _jsx("kbd", { children: "Hover" })] })] }), _jsxs("li", { className: "empty-state__shortcut", "data-test": "empty-state-shortcut", children: [_jsx("span", { children: "Hide UI" }), _jsxs("span", { className: "empty-state__shortcut-keys", "aria-label": `${modifierName} plus backslash`, children: [_jsx("kbd", { children: modifierKey }), _jsx("span", { "aria-hidden": "true", children: "+" }), _jsx("kbd", { children: "\\" })] })] }), _jsxs("li", { className: "empty-state__shortcut", "data-test": "empty-state-shortcut", children: [_jsx("span", { children: "Hold to view original" }), _jsx("span", { className: "empty-state__shortcut-keys", "aria-label": "Backslash", children: _jsx("kbd", { children: "\\" }) })] }), _jsxs("li", { className: "empty-state__shortcut", "data-test": "empty-state-shortcut", children: [_jsx("span", { children: "Undo" }), _jsxs("span", { className: "empty-state__shortcut-keys", "aria-label": `${modifierName} plus Z`, children: [_jsx("kbd", { children: modifierKey }), _jsx("span", { "aria-hidden": "true", children: "+" }), _jsx("kbd", { children: "Z" })] })] }), _jsxs("li", { className: "empty-state__shortcut", "data-test": "empty-state-shortcut", children: [_jsx("span", { children: "Deselect" }), _jsx("span", { className: "empty-state__shortcut-keys", children: _jsx("kbd", { children: "Esc" }) })] })] }) })] }));
15
15
  }
@@ -7,5 +7,4 @@ declare global {
7
7
  }
8
8
  export { toggleInspector, setInspectorOpen };
9
9
  export type { SelectedElement } from "../selection/selectionStore.js";
10
- export declare function setInspectorHost(host: HTMLElement | null): void;
11
10
  export declare function InspectorShell(): ReactElement;
@@ -1,14 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useState } from "react";
3
- import { IconArrowUpRight, IconColorSwatch, IconLayoutSidebarRight, IconSettings } from "@tabler/icons-react";
3
+ import { IconLayoutSidebarRight, IconSettings } from "@tabler/icons-react";
4
4
  import { useInspectorOpen, toggleInspector, setInspectorOpen } from "./openStore.js";
5
5
  import { useSelectedElement, useSelectedElements, useHierarchy, setSelectedElement, removeSelectedElement, } from "../selection/selectionStore.js";
6
- import { InspectorOverlay } from "../overlay/InspectorOverlay.js";
7
6
  import { findTokenRow } from "../styleEditors/rowLookup.js";
8
7
  import { useBrowserCssInspection } from "../inspection/useBrowserCssInspection.js";
9
8
  import { resolveSelectionFromElement } from "../selection/resolveSelection.js";
10
9
  import { SpacingBox } from "../styleEditors/SpacingBox.js";
11
10
  import { Typography } from "../styleEditors/Typography.js";
11
+ import { isTextRelevant } from "../styleEditors/textRelevance.js";
12
12
  import { ColorPicker } from "../styleEditors/ColorPicker.js";
13
13
  import { BorderEditor } from "../styleEditors/BorderEditor.js";
14
14
  import { AppearanceSection } from "../styleEditors/AppearanceSection.js";
@@ -20,27 +20,37 @@ import { countSourceSiteMatches, getEditScope, relinkElement, sourceSiteSelector
20
20
  import { canEditStyles } from "../tokens/editActions.js";
21
21
  import { Button } from "../ui/Button.js";
22
22
  import { CopyPromptButton } from "./CopyPromptButton.js";
23
+ import { PeekOriginalButton } from "./PeekOriginalButton.js";
23
24
  import { StatusCallout } from "../ui/StatusCallout.js";
24
25
  import { IconButton } from "../ui/IconButton.js";
25
26
  import { UI_STYLES } from "../ui/styles.js";
26
27
  import { getActiveStyleState, setActiveStyleState } from "./styleState.js";
27
28
  import { isEditableEvent } from "./shortcuts.js";
28
- import { clearInspectorLayout, setInspectorLayoutOpen } from "./panelLayout.js";
29
29
  import { formatInspectorLabel } from "../ui/labels.js";
30
30
  import { FieldRow } from "../ui/FieldRow.js";
31
31
  import { Select } from "../ui/Select.js";
32
- import { enterCanvas, useCanvasMode } from "../canvas/canvasStore.js";
33
32
  import { clearRestoreCount, clearSession } from "../canvas/sessionStore.js";
33
+ import { hasWriteLease } from "../canvas/workspaceLease.js";
34
+ import { useFocusedCardId, useSelectedCardId, } from "../canvas/canvasStore.js";
35
+ import { getActiveCanvasFrame } from "../canvas/activeCanvasDocument.js";
34
36
  import { getElementWindow } from "../runtime/domRealm.js";
35
37
  import { deleteElement, nudgeElement } from "../overlay/structuralGestures.js";
36
38
  import { AtRuleContextProvider } from "../ui/AtRuleContext.js";
37
39
  import { ComponentPropsSection } from "../componentSemantics/ComponentPropsSection.js";
38
- import { cancelInlineTextEdit, disposeInlineTextEdit, isInlineTextEditingActive, useInlineTextSession } from "../inline-text/inlineTextEditor.js";
40
+ import { cancelInlineTextEdit, isInlineTextEditingActive, useInlineTextDiagnostic, } from "../inline-text/inlineTextEditor.js";
41
+ import { getInlineTextFeedback } from "../inline-text/inlineTextFeedback.js";
39
42
  import { useNudgeUiRuntimeConfig } from "../runtime/useRuntimeConfig.js";
40
43
  import { DomNavigation } from "./DomNavigation.js";
41
44
  import { EmptyState } from "./EmptyState.js";
42
45
  import { createStyleSelection } from "../selection/styleSelection.js";
43
46
  import { intersectTokenEntries } from "../inspection/selectionProperty.js";
47
+ import { isNudgeUiDev } from "../runtime/devFlag.js";
48
+ import { isDemoRuntime } from "../runtime/runtimeConfig.js";
49
+ import { SketchWorkspace } from "../sketch/SketchWorkspace.js";
50
+ import { cancelSketchInteraction, useSketchInteractionActive } from "../sketch/interaction.js";
51
+ import { clearSketchClipboardHandoff } from "../sketch/handoff.js";
52
+ import { clearSketchesForProject } from "../sketch/store.js";
53
+ import { closeSketchNote } from "../sketch/sketchNote.js";
44
54
  function findFirstTokenRow(rows, properties) {
45
55
  for (const property of properties) {
46
56
  const row = findTokenRow(rows, property);
@@ -55,13 +65,6 @@ function findFirstTokenRow(rows, properties) {
55
65
  return null;
56
66
  }
57
67
  export { toggleInspector, setInspectorOpen };
58
- let inspectorHost = null;
59
- export function setInspectorHost(host) {
60
- inspectorHost = host;
61
- }
62
- function resolveHost() {
63
- return inspectorHost ?? document.getElementById("nudge-ui-root") ?? document.body;
64
- }
65
68
  function nodeMatchesSelector(node, selector) {
66
69
  if (!selector || node.nodeType !== node.ELEMENT_NODE)
67
70
  return false;
@@ -87,24 +90,22 @@ function scopeMutationAffectsSelection(records, selected) {
87
90
  export function InspectorShell() {
88
91
  const isOpen = useInspectorOpen();
89
92
  const runtimeConfig = useNudgeUiRuntimeConfig();
90
- const canvasEnabled = runtimeConfig.capabilities.canvas;
91
93
  const domNavigationEnabled = runtimeConfig.capabilities.domNavigation === true;
92
- const activeCanvasMode = useCanvasMode();
93
- const canvasMode = canvasEnabled ? activeCanvasMode : "inspect";
94
+ const sketchEnabled = isNudgeUiDev() && !isDemoRuntime();
95
+ const sketchActive = useSketchInteractionActive();
96
+ const selectedCardId = useSelectedCardId();
97
+ const focusedCardId = useFocusedCardId();
98
+ const sketchHost = selectedCardId || focusedCardId ? getActiveCanvasFrame() : null;
94
99
  const selected = useSelectedElement();
95
100
  const selectedElements = useSelectedElements();
96
101
  const hierarchy = useHierarchy();
97
- const inlineTextSession = useInlineTextSession();
102
+ const inlineTextFeedback = getInlineTextFeedback(useInlineTextDiagnostic());
98
103
  const [settingsOpen, setSettingsOpen] = useState(false);
99
104
  const [settingsSection, setSettingsSection] = useState("instructions");
100
105
  const [scopeRevision, refreshScope] = useState(0);
101
106
  const [styleState, setStyleState] = useState(getActiveStyleState());
102
107
  const cssInspection = useBrowserCssInspection(selectedElements, styleState);
103
108
  const isMultiSelection = selectedElements.length > 1;
104
- useEffect(() => {
105
- setInspectorLayoutOpen(isOpen);
106
- return clearInspectorLayout;
107
- }, [isOpen]);
108
109
  useEffect(() => {
109
110
  // A new selection should never inherit an incidental state from the
110
111
  // previous element. Base is the inspector's deliberate default.
@@ -147,7 +148,7 @@ export function InspectorShell() {
147
148
  return () => observers.forEach((observer) => observer.disconnect());
148
149
  }, [selectedElements, scopeRevision]);
149
150
  useEffect(() => {
150
- if (!isOpen)
151
+ if (sketchActive)
151
152
  return;
152
153
  function onKeydown(event) {
153
154
  if (isInlineTextEditingActive())
@@ -200,13 +201,14 @@ export function InspectorShell() {
200
201
  }
201
202
  window.addEventListener("keydown", onKeydown);
202
203
  return () => window.removeEventListener("keydown", onKeydown);
203
- }, [isOpen, isMultiSelection, selected]);
204
+ }, [isMultiSelection, selected, sketchActive]);
204
205
  const inspectionSnapshot = cssInspection.element;
205
206
  const styleSelection = useMemo(() => createStyleSelection(selectedElements, cssInspection.elements, selected), [cssInspection.elements, selected, selectedElements]);
206
207
  const selectionEditable = useMemo(() => styleSelection ? canEditStyles(styleSelection.target) : false, [scopeRevision, styleSelection]);
207
208
  const tokenEntries = useMemo(() => intersectTokenEntries(cssInspection.elements.map((snapshot) => snapshot.availableTokens)), [cssInspection.elements]);
208
209
  const tokenRows = useMemo(() => styleSelection ? [...styleSelection.primaryRows] : [], [styleSelection]);
209
210
  const availableInteractionStates = inspectionSnapshot?.availableStates ?? [];
211
+ const showText = selectedElements.length > 0 && selectedElements.every((element, index) => isTextRelevant(element.domElement, cssInspection.elements[index]?.properties ?? []));
210
212
  const showInteractionState = !isMultiSelection && availableInteractionStates.length > 2;
211
213
  const paintedBackgroundRow = findFirstTokenRow(tokenRows, ["background-color", "background"]);
212
214
  const backgroundTokenRow = useMemo(() => {
@@ -227,24 +229,24 @@ export function InspectorShell() {
227
229
  function refreshScopeState() {
228
230
  refreshScope((revision) => revision + 1);
229
231
  }
230
- function handleCanvasModeButton() {
231
- // Inline text editing is controller-owned. Canvas renderer documents
232
- // receive projections only, so dispose the active controller session
233
- // before mounting cards rather than probing a stale iframe document.
234
- disposeInlineTextEdit("frame-disposed");
235
- enterCanvas();
236
- }
237
232
  function openSettings(section) {
238
233
  setSettingsSection(section);
239
234
  setSettingsOpen(true);
240
235
  }
241
- return (_jsxs(_Fragment, { children: [_jsx("style", { "data-test": "inspector-styles", children: UI_STYLES }), canvasMode === "inspect" && _jsx(InspectorOverlay, { host: resolveHost() }), _jsxs("div", { className: "panel", "data-open": isOpen ? "true" : "false", children: [_jsxs("div", { className: "panel__tabs", "aria-label": "Inspector controls", children: [_jsxs("div", { className: "panel__header-row", "data-test": "inspect-tab", children: [_jsx(IconButton, { variant: "quiet", label: "Collapse inspector", "data-test": "collapse-inspector", style: { marginLeft: "-8px" }, onClick: () => {
236
+ function clearInspectorSession() {
237
+ clearSession();
238
+ clearRestoreCount();
239
+ if (!hasWriteLease())
240
+ return;
241
+ cancelSketchInteraction();
242
+ closeSketchNote();
243
+ clearSketchClipboardHandoff();
244
+ void clearSketchesForProject(runtimeConfig.projectId).catch(() => undefined);
245
+ }
246
+ return (_jsxs(_Fragment, { children: [_jsx("style", { "data-test": "inspector-styles", children: UI_STYLES }), _jsxs("div", { className: "panel", "data-open": isOpen ? "true" : "false", children: [_jsxs("div", { className: "panel__tabs", "aria-label": "Inspector controls", children: [_jsxs("div", { className: "panel__header-row", "data-test": "inspect-tab", children: [_jsx(IconButton, { variant: "quiet", label: "Collapse inspector", "data-test": "collapse-inspector", disabled: sketchActive, style: { marginLeft: "-8px" }, onClick: () => {
242
247
  cancelInlineTextEdit();
243
248
  setInspectorOpen(false);
244
- }, children: _jsx(IconLayoutSidebarRight, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) }), _jsxs("div", { className: "panel__header-actions", children: [_jsx(IconButton, { variant: "quiet", "data-test": "tokens-button", label: "Tokens", title: "Tokens", onClick: () => openSettings("tokens"), children: _jsx(IconColorSwatch, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) }), _jsx(IconButton, { variant: "quiet", label: "Settings", title: "Settings", "data-test": "settings-button", onClick: () => openSettings("instructions"), children: _jsx(IconSettings, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) }), canvasEnabled && canvasMode !== "canvas" ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "panel__header-divider", "aria-hidden": "true" }), _jsxs(Button, { variant: "quiet", className: "panel__canvas-button", "data-test": "mode-canvas", type: "button", onClick: handleCanvasModeButton, children: ["Canvas", _jsx(IconArrowUpRight, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" })] })] })) : null] })] }), _jsx("div", { className: "panel__copy-row", children: _jsx(CopyPromptButton, { settingsOpen: settingsOpen, settingsSection: settingsSection, onOpenSettings: openSettings, onSettingsOpenChange: setSettingsOpen }) })] }), _jsxs("div", { className: "panel__body", children: [inlineTextSession && (inlineTextSession.bindingChoices.length > 1 ||
245
- inlineTextSession.scopeChoices.length > 0) ? (_jsxs("section", { className: "inline-text-editor", "data-test": "inline-text-editor", children: [_jsx("div", { className: "inline-text-editor__binding", "data-test": "inline-text-binding", children: inlineTextSession.binding.kind === "component-prop"
246
- ? `${inlineTextSession.binding.target.componentName}.${inlineTextSession.binding.property}`
247
- : "Rendered text" }), inlineTextSession.bindingChoices.length > 1 ? (_jsxs("div", { className: "inline-text-editor__chooser", "data-test": "inline-binding-chooser", children: [_jsx("div", { className: "inline-text-editor__chooser-label", children: "Choose binding" }), _jsx("div", { className: "inline-text-editor__chooser-options", role: "group", "aria-label": "Text binding", children: inlineTextSession.bindingChoices.map((choice, index) => (_jsxs(Button, { size: "compact", variant: inlineTextSession.selectedBindingIndex === index ? "primary" : "quiet", "data-test": "inline-binding-choice", "data-index": index, "aria-pressed": inlineTextSession.selectedBindingIndex === index, onMouseDown: (event) => event.preventDefault(), onClick: () => inlineTextSession.chooseBinding(index), children: [choice.binding.target.componentName, ".", choice.binding.property] }, `${choice.binding.target.callsiteId}:${choice.binding.property}`))) })] })) : null, inlineTextSession.scopeChoices.length > 0 ? (_jsxs("div", { className: "inline-text-editor__chooser", "data-test": "inline-scope-chooser", children: [_jsx("div", { className: "inline-text-editor__chooser-label", children: "Apply to" }), _jsx("div", { className: "inline-text-editor__chooser-options", role: "group", "aria-label": "Text edit scope", children: inlineTextSession.scopeChoices.map((scope) => (_jsx(Button, { size: "compact", variant: inlineTextSession.scope === scope ? "primary" : "quiet", "data-test": `inline-scope-${scope}`, "aria-pressed": inlineTextSession.scope === scope, onMouseDown: (event) => event.preventDefault(), onClick: () => inlineTextSession.chooseScope(scope), children: scope === "source-site" ? "All outputs" : "This rendered item" }, scope))) })] })) : null, _jsxs("div", { className: "inline-text-editor__actions", children: [_jsx(Button, { size: "compact", variant: "secondary", "data-test": "inline-text-cancel", onClick: () => inlineTextSession.cancel(), children: "Cancel" }), _jsx(Button, { size: "compact", "data-test": "inline-text-commit", disabled: inlineTextSession.bindingChoices.length > 1 && inlineTextSession.selectedBindingIndex === null, onClick: () => inlineTextSession.commit(), children: "Done" })] })] })) : null, selected ? (_jsxs(_Fragment, { children: [domNavigationEnabled && !isMultiSelection ? _jsx(DomNavigation, { selected: selected, hierarchy: hierarchy }) : null, isMultiSelection || showInteractionState || hasEditScopeCallout ? (_jsxs("div", { className: `selection${hasEditScopeCallout ? "" : " selection--without-scope-callout"}`, "data-test": "selection", "data-selected-cid": selected.cid, "data-selected-src": selected.src, "data-selected-count": selectedElements.length, children: [isMultiSelection ? (_jsx("div", { className: "selection__summary", "data-test": "multi-selection-summary", children: _jsxs("span", { className: "selection__count", children: [selectedElements.length, " elements selected"] }) })) : null, hasEditScopeCallout ? (_jsx(StatusCallout, { tone: editScope === "rendered-instance" ? "neutral" : "accent", "data-test": "edit-scope", "data-lost": "false", children: editScope === "rendered-instance" ? (_jsxs("div", { className: "scope__unlinked", children: [_jsx("span", { children: "Element unlinked" }), _jsx(Button, { size: "compact", className: "scope__action", "data-test": "relink-element", onClick: () => {
249
+ }, children: _jsx(IconLayoutSidebarRight, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) }), _jsxs("div", { className: "panel__header-actions", children: [_jsx(PeekOriginalButton, {}), _jsx(IconButton, { variant: "quiet", label: "Settings", title: "Settings", "data-test": "settings-button", onClick: () => openSettings("instructions"), children: _jsx(IconSettings, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) })] })] }), _jsx("div", { className: "panel__copy-row", children: _jsx(CopyPromptButton, { settingsOpen: settingsOpen, settingsSection: settingsSection, onOpenSettings: openSettings, onSettingsOpenChange: setSettingsOpen }) })] }), _jsxs("div", { className: "panel__body", children: [inlineTextFeedback ? (_jsx(StatusCallout, { tone: "danger", className: "inline-text-diagnostic", "data-test": "inline-text-diagnostic", role: "status", "aria-live": "polite", "aria-atomic": "true", children: _jsx("span", { children: inlineTextFeedback.message }) })) : null, selected ? (_jsxs(_Fragment, { children: [domNavigationEnabled && !isMultiSelection ? _jsx(DomNavigation, { selected: selected, hierarchy: hierarchy }) : null, isMultiSelection || showInteractionState || hasEditScopeCallout ? (_jsxs("div", { className: `selection${hasEditScopeCallout ? "" : " selection--without-scope-callout"}`, "data-test": "selection", "data-selected-cid": selected.cid, "data-selected-src": selected.src, "data-selected-count": selectedElements.length, children: [isMultiSelection ? (_jsx("div", { className: "selection__summary", "data-test": "multi-selection-summary", children: _jsxs("span", { className: "selection__count", children: [selectedElements.length, " elements selected"] }) })) : null, hasEditScopeCallout ? (_jsx(StatusCallout, { tone: editScope === "rendered-instance" ? "neutral" : "accent", "data-test": "edit-scope", "data-lost": "false", children: editScope === "rendered-instance" ? (_jsxs("div", { className: "scope__unlinked", children: [_jsx("span", { children: "Element unlinked" }), _jsx(Button, { size: "compact", className: "scope__action", "data-test": "relink-element", onClick: () => {
248
250
  const overrideId = relinkElement(selected.domElement);
249
251
  if (overrideId)
250
252
  discardChangesForInstanceOverride(overrideId);
@@ -261,8 +263,5 @@ export function InspectorShell() {
261
263
  return;
262
264
  setActiveStyleState(nextState);
263
265
  setStyleState(nextState);
264
- } }) })) : null] })) : null, !isMultiSelection ? _jsx(ComponentPropsSection, { selected: selected }) : null, selectionEditable ? (_jsx(AtRuleContextProvider, { rows: tokenRows, children: _jsxs("div", { className: "style-editors", "data-test": "style-editors", children: [_jsx(LayoutSection, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `layout-${styleState}`), _jsx(SpacingBox, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `spacing-${styleState}`), _jsx(AppearanceSection, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `appearance-${styleState}`), _jsx(Typography, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `type-${styleState}`), _jsx(ColorPicker, { element: selected, selection: styleSelection, property: "color", entries: tokenEntries, tokenRow: findTokenRow(tokenRows, "color") }, `color-${styleState}`), _jsx(ColorPicker, { element: selected, selection: styleSelection, property: "background-color", entries: tokenEntries, tokenRow: backgroundTokenRow }, `background-${styleState}`), _jsx(BorderEditor, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `border-${styleState}`), _jsx(BoxShadowEditor, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `box-shadow-${styleState}`)] }) })) : (_jsx(StatusCallout, { tone: "neutral", "data-test": "multi-selection-uneditable", children: "These repeated items are indistinguishable, so Nudge UI cannot safely target only part of the group. Select every repeated item to edit them together." }))] })) : (_jsx(EmptyState, {})), _jsx(ChangesLog, { onClearSession: () => {
265
- clearSession();
266
- clearRestoreCount();
267
- } })] })] }), !isOpen ? (_jsx("div", { className: "panel__restore", children: _jsx(IconButton, { variant: "quiet", label: "Show inspector", className: "panel__restore-button", "data-test": "show-inspector", onClick: () => setInspectorOpen(true), children: _jsx(IconLayoutSidebarRight, { size: 16, stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }) }) })) : null] }));
266
+ } }) })) : null] })) : null, !isMultiSelection ? _jsx(ComponentPropsSection, { selected: selected }) : null, selectionEditable ? (_jsx(AtRuleContextProvider, { rows: tokenRows, children: _jsxs("div", { className: "style-editors", "data-test": "style-editors", children: [_jsx(LayoutSection, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `layout-${styleState}`), _jsx(SpacingBox, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `spacing-${styleState}`), _jsx(AppearanceSection, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `appearance-${styleState}`), showText ? _jsxs(_Fragment, { children: [_jsx(Typography, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `type-${styleState}`), _jsx(ColorPicker, { element: selected, selection: styleSelection, property: "color", entries: tokenEntries, tokenRow: findTokenRow(tokenRows, "color") }, `color-${styleState}`)] }) : null, _jsx(ColorPicker, { element: selected, selection: styleSelection, property: "background-color", entries: tokenEntries, tokenRow: backgroundTokenRow }, `background-${styleState}`), _jsx(BorderEditor, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `border-${styleState}`), _jsx(BoxShadowEditor, { element: selected, selection: styleSelection, entries: tokenEntries, tokenRows: tokenRows }, `box-shadow-${styleState}`)] }) })) : (_jsx(StatusCallout, { tone: "neutral", "data-test": "multi-selection-uneditable", children: "These repeated items are indistinguishable, so Nudge UI cannot safely target only part of the group. Select every repeated item to edit them together." }))] })) : (_jsx(EmptyState, {})), _jsx(ChangesLog, { onClearSession: clearInspectorSession })] })] }), sketchEnabled ? (_jsx(SketchWorkspace, { hostElement: sketchHost, projectId: runtimeConfig.projectId })) : null, !isOpen ? (_jsx("div", { className: "panel__restore", children: _jsx(IconButton, { variant: "quiet", label: "Show inspector", className: "panel__restore-button", "data-test": "show-inspector", onClick: () => setInspectorOpen(true), children: _jsx(IconLayoutSidebarRight, { size: 16, stroke: "var(--icon-stroke-width)", "aria-hidden": "true" }) }) })) : null] }));
268
267
  }
@@ -0,0 +1,7 @@
1
+ import { type ReactElement } from "react";
2
+ /**
3
+ * Press-and-hold button that previews the page without inspector changes
4
+ * (NUD-30). Releasing restores the edited preview; canonical intent is never
5
+ * cleared, so nothing is lost while held.
6
+ */
7
+ export declare function PeekOriginalButton(): ReactElement;
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useSyncExternalStore } from "react";
3
+ import { IconButton } from "../ui/IconButton.js";
4
+ import { PeekOriginalIcon } from "./PeekOriginalIcon.js";
5
+ import { workspaceChangeStore } from "../changes/workspaceChanges.js";
6
+ import { setOriginalPreviewActive, useOriginalPreviewActive, } from "./originalPreview.js";
7
+ function useWorkspaceChangeCount() {
8
+ return useSyncExternalStore(workspaceChangeStore.subscribe, () => {
9
+ const snapshot = workspaceChangeStore.getSnapshot();
10
+ return snapshot.changes.length + snapshot.structuralChanges.length;
11
+ }, () => 0);
12
+ }
13
+ /**
14
+ * Press-and-hold button that previews the page without inspector changes
15
+ * (NUD-30). Releasing restores the edited preview; canonical intent is never
16
+ * cleared, so nothing is lost while held.
17
+ */
18
+ export function PeekOriginalButton() {
19
+ const active = useOriginalPreviewActive();
20
+ const changeCount = useWorkspaceChangeCount();
21
+ const disabled = changeCount === 0;
22
+ useEffect(() => {
23
+ if (!active)
24
+ return;
25
+ const release = () => setOriginalPreviewActive(false);
26
+ window.addEventListener("pointerup", release);
27
+ window.addEventListener("pointercancel", release);
28
+ window.addEventListener("blur", release);
29
+ return () => {
30
+ window.removeEventListener("pointerup", release);
31
+ window.removeEventListener("pointercancel", release);
32
+ window.removeEventListener("blur", release);
33
+ };
34
+ }, [active]);
35
+ useEffect(() => () => {
36
+ setOriginalPreviewActive(false);
37
+ }, []);
38
+ return (_jsx(IconButton, { variant: "quiet", "data-test": "peek-original-button", label: "Hold to view original", title: disabled ? "No changes to preview" : "Hold to view original", "aria-pressed": active, "data-active": active ? "true" : "false", disabled: disabled, onPointerDown: (event) => {
39
+ if (disabled)
40
+ return;
41
+ if (event.pointerType === "mouse" && event.button !== 0)
42
+ return;
43
+ event.currentTarget.setPointerCapture?.(event.pointerId);
44
+ setOriginalPreviewActive(true);
45
+ }, onPointerUp: () => setOriginalPreviewActive(false), onPointerCancel: () => setOriginalPreviewActive(false), onLostPointerCapture: () => setOriginalPreviewActive(false), onKeyDown: (event) => {
46
+ if (disabled || event.repeat)
47
+ return;
48
+ if (event.key === " " || event.key === "Enter")
49
+ setOriginalPreviewActive(true);
50
+ }, onKeyUp: (event) => {
51
+ if (event.key === " " || event.key === "Enter")
52
+ setOriginalPreviewActive(false);
53
+ }, onBlur: () => setOriginalPreviewActive(false), onContextMenu: (event) => {
54
+ if (active)
55
+ event.preventDefault();
56
+ }, children: _jsx(PeekOriginalIcon, {}) }));
57
+ }
@@ -0,0 +1,7 @@
1
+ import type { ReactElement } from "react";
2
+ /**
3
+ * Before/after comparison glyph for the hold-to-view-original button.
4
+ * Custom artwork rendered with the shared inspector icon tokens so it
5
+ * matches the neighboring header icons.
6
+ */
7
+ export declare function PeekOriginalIcon(): ReactElement;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Before/after comparison glyph for the hold-to-view-original button.
4
+ * Custom artwork rendered with the shared inspector icon tokens so it
5
+ * matches the neighboring header icons.
6
+ */
7
+ export function PeekOriginalIcon() {
8
+ return (_jsxs("svg", { viewBox: "0 0 24 24", width: "var(--icon-size-small)", height: "var(--icon-size-small)", "aria-hidden": "true", children: [_jsx("path", { d: "M8.66667 5L6 5C4.34315 5 3 6.34315 3 8L3 16C3 17.6569 4.34315 19 6 19L12 19", fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "var(--icon-stroke-width)" }), _jsx("path", { d: "M13 5L18 5C19.6569 5 21 6.34315 21 8L21 16C21 17.6569 19.6569 19 18 19L16.3333 19", fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "var(--icon-stroke-width)" }), _jsx("line", { x1: "13.7721", y1: "22.2984", x2: "8.29835", y2: "2.22788", fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "var(--icon-stroke-width)" })] }));
9
+ }
@@ -0,0 +1,9 @@
1
+ export declare function isOriginalPreviewActive(): boolean;
2
+ export declare function subscribeOriginalPreview(listener: () => void): () => void;
3
+ export declare function useOriginalPreviewActive(): boolean;
4
+ /**
5
+ * Hold-to-view-original (NUD-30). While active the host document shows the
6
+ * page without any inspector changes. Canonical intent stays in the workspace
7
+ * store, so releasing restores the edited preview without loss.
8
+ */
9
+ export declare function setOriginalPreviewActive(next: boolean): void;
@@ -0,0 +1,61 @@
1
+ import { useSyncExternalStore } from "react";
2
+ import { workspaceChangeStore } from "../changes/workspaceChanges.js";
3
+ import { applyHostWorkspaceProjection, compileWorkspaceProjection, } from "../projection/workspaceProjection.js";
4
+ import { isEditorShellDocument } from "../runtime/editorShell.js";
5
+ let previewingOriginal = false;
6
+ const listeners = new Set();
7
+ export function isOriginalPreviewActive() {
8
+ return previewingOriginal;
9
+ }
10
+ export function subscribeOriginalPreview(listener) {
11
+ listeners.add(listener);
12
+ return () => {
13
+ listeners.delete(listener);
14
+ };
15
+ }
16
+ export function useOriginalPreviewActive() {
17
+ return useSyncExternalStore(subscribeOriginalPreview, () => previewingOriginal, () => false);
18
+ }
19
+ function notifyOriginalPreviewListeners() {
20
+ for (const listener of listeners) {
21
+ try {
22
+ listener();
23
+ }
24
+ catch {
25
+ /* Subscriber errors must not break the preview toggle. */
26
+ }
27
+ }
28
+ }
29
+ function applyEmptyHostProjection() {
30
+ if (isEditorShellDocument())
31
+ return;
32
+ const { revision } = workspaceChangeStore.getSnapshot();
33
+ applyHostWorkspaceProjection({
34
+ sourceRevision: revision,
35
+ managedStyles: { rules: [], css: "" },
36
+ instanceOverrides: [],
37
+ structuralChanges: [],
38
+ textContentChanges: [],
39
+ componentOverrides: [],
40
+ });
41
+ }
42
+ function applyCanonicalHostProjection() {
43
+ if (isEditorShellDocument())
44
+ return;
45
+ applyHostWorkspaceProjection(compileWorkspaceProjection(workspaceChangeStore.getSnapshot()));
46
+ }
47
+ /**
48
+ * Hold-to-view-original (NUD-30). While active the host document shows the
49
+ * page without any inspector changes. Canonical intent stays in the workspace
50
+ * store, so releasing restores the edited preview without loss.
51
+ */
52
+ export function setOriginalPreviewActive(next) {
53
+ if (next === previewingOriginal)
54
+ return;
55
+ previewingOriginal = next;
56
+ if (previewingOriginal)
57
+ applyEmptyHostProjection();
58
+ else
59
+ applyCanonicalHostProjection();
60
+ notifyOriginalPreviewListeners();
61
+ }
@@ -1,3 +1,6 @@
1
1
  export declare function isEditableTarget(target: EventTarget | null): boolean;
2
2
  export declare function isEditableEvent(event: KeyboardEvent): boolean;
3
+ export declare const SEND_PROMPT_HOTKEY_EVENT = "nudge-ui-send-prompt-hotkey";
4
+ export declare function isSendPromptShortcut(event: KeyboardEvent): boolean;
3
5
  export declare function isInspectorToggleShortcut(event: KeyboardEvent): boolean;
6
+ export declare function isOriginalPreviewShortcut(event: KeyboardEvent): boolean;