nudge-ui 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/nudge-ui.mjs +3 -1
- package/dist/cli/agentCommand.d.ts +8 -0
- package/dist/cli/agentCommand.js +59 -0
- package/dist/client.mjs +878 -11
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
- package/dist/hosts/astro/clientTransport.js +63 -11
- package/dist/hosts/astro/integration.d.ts +2 -2
- package/dist/hosts/astro/integration.js +4 -3
- package/dist/hosts/environment.d.ts +15 -0
- package/dist/hosts/environment.js +16 -0
- package/dist/hosts/next/sidecar.d.ts +4 -0
- package/dist/hosts/next/sidecar.js +56 -7
- package/dist/hosts/next/wrapper.d.ts +2 -0
- package/dist/hosts/next/wrapper.js +32 -3
- package/dist/hosts/projectBridge.d.ts +21 -0
- package/dist/hosts/projectBridge.js +98 -0
- package/dist/hosts/static/cli.d.ts +1 -1
- package/dist/hosts/static/cli.js +6 -1
- package/dist/hosts/static/manifest.d.ts +1 -1
- package/dist/hosts/static/manifest.js +3 -1
- package/dist/hosts/static/server.js +45 -3
- package/dist/hosts/vite/vite.d.ts +6 -1
- package/dist/hosts/vite/vite.js +82 -15
- package/dist/inspector/agent/McpConnectionDialog.d.ts +5 -4
- package/dist/inspector/agent/McpConnectionDialog.js +20 -37
- package/dist/inspector/agent/client.d.ts +16 -3
- package/dist/inspector/agent/client.js +114 -11
- package/dist/inspector/agent/connectionStatus.d.ts +3 -3
- package/dist/inspector/agent/connectionStatus.js +11 -4
- package/dist/inspector/agent/httpTransport.d.ts +8 -0
- package/dist/inspector/agent/httpTransport.js +78 -18
- package/dist/inspector/agent/protocol.d.ts +11 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +1 -1
- package/dist/inspector/agent/verification.js +69 -12
- package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
- package/dist/inspector/canvas/CanvasCard.js +181 -91
- package/dist/inspector/canvas/CanvasElementOverlay.js +288 -52
- package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
- package/dist/inspector/canvas/CanvasToolbar.js +24 -0
- package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
- package/dist/inspector/canvas/CanvasWorkspace.js +410 -72
- package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
- package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
- package/dist/inspector/canvas/canvasResize.d.ts +30 -0
- package/dist/inspector/canvas/canvasResize.js +52 -0
- package/dist/inspector/canvas/canvasStore.d.ts +30 -6
- package/dist/inspector/canvas/canvasStore.js +128 -61
- package/dist/inspector/canvas/frameProtocol.d.ts +82 -5
- package/dist/inspector/canvas/frameProtocol.js +63 -2
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -0
- package/dist/inspector/canvas/keyboardShortcuts.js +16 -0
- package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
- package/dist/inspector/canvas/linkEligibility.js +0 -29
- package/dist/inspector/canvas/projection.d.ts +7 -0
- package/dist/inspector/canvas/projection.js +118 -25
- package/dist/inspector/canvas/rendererBootstrap.js +42 -29
- package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
- package/dist/inspector/canvas/rendererElementSelector.js +385 -58
- package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
- package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
- package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
- package/dist/inspector/canvas/sessionStore.d.ts +1 -0
- package/dist/inspector/canvas/sessionStore.js +12 -4
- package/dist/inspector/canvas/staleChangeDetector.js +16 -14
- package/dist/inspector/changes/changesLog.js +31 -1
- package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
- package/dist/inspector/changes/managedStyleProjection.js +3 -3
- package/dist/inspector/changes/structuralTypes.d.ts +4 -0
- package/dist/inspector/changes/workspaceChanges.d.ts +4 -0
- package/dist/inspector/changes/workspaceChanges.js +29 -3
- package/dist/inspector/clientManifest.d.ts +7 -0
- package/dist/inspector/clientManifest.js +12 -0
- package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
- package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
- package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
- package/dist/inspector/index.d.ts +2 -0
- package/dist/inspector/index.js +80 -32
- package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
- package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
- package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
- package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
- package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
- package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
- package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
- package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
- package/dist/inspector/inline-text/textEditChange.js +165 -0
- package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
- package/dist/inspector/inspection/selectionProperty.js +1 -0
- package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
- package/dist/inspector/overlay/DomNavigator.js +40 -0
- package/dist/inspector/overlay/InspectorOverlay.js +6 -2
- package/dist/inspector/overlay/clickPolicy.d.ts +18 -2
- package/dist/inspector/overlay/clickPolicy.js +34 -3
- package/dist/inspector/overlay/elementSelector.d.ts +1 -1
- package/dist/inspector/overlay/elementSelector.js +18 -7
- package/dist/inspector/overlay/selectedGeometry.js +21 -2
- package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
- package/dist/inspector/overlay/spacingGestures.js +351 -0
- package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
- package/dist/inspector/overlay/structuralGestures.js +12 -10
- package/dist/inspector/projection/managedStylesheet.js +4 -1
- package/dist/inspector/projection/renderedInstance.d.ts +2 -0
- package/dist/inspector/projection/renderedInstance.js +5 -1
- package/dist/inspector/projection/structuralProjection.d.ts +2 -0
- package/dist/inspector/projection/structuralProjection.js +85 -1
- package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
- package/dist/inspector/projection/textProjection.d.ts +2 -0
- package/dist/inspector/projection/textProjection.js +10 -4
- package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
- package/dist/inspector/prompt/clipboardHandoff.js +3 -3
- package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
- package/dist/inspector/prompt/generatePrompt.js +19 -2
- package/dist/inspector/runtime/editorShell.d.ts +2 -0
- package/dist/inspector/runtime/editorShell.js +4 -0
- package/dist/inspector/runtime/runtimeConfig.d.ts +5 -1
- package/dist/inspector/runtime/runtimeConfig.js +4 -0
- package/dist/inspector/selection/hierarchy.d.ts +6 -0
- package/dist/inspector/selection/hierarchy.js +8 -0
- package/dist/inspector/selection/selectionStore.js +2 -0
- package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
- package/dist/inspector/settings/SettingsDialog.js +12 -2
- package/dist/inspector/shell/AppShell.js +5 -1
- package/dist/inspector/shell/ChangesLog.js +26 -22
- package/dist/inspector/shell/CopyPromptButton.js +102 -19
- package/dist/inspector/shell/EmptyState.js +1 -1
- package/dist/inspector/shell/InspectorShell.d.ts +0 -1
- package/dist/inspector/shell/InspectorShell.js +38 -42
- package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalButton.js +57 -0
- package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
- package/dist/inspector/shell/originalPreview.d.ts +9 -0
- package/dist/inspector/shell/originalPreview.js +61 -0
- package/dist/inspector/shell/shortcuts.d.ts +3 -0
- package/dist/inspector/shell/shortcuts.js +15 -0
- package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
- package/dist/inspector/sketch/SketchChanges.js +72 -0
- package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
- package/dist/inspector/sketch/SketchEditor.js +402 -0
- package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
- package/dist/inspector/sketch/SketchEntryButton.js +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
- package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
- package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
- package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
- package/dist/inspector/sketch/SketchOverlay.js +420 -0
- package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
- package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
- package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
- package/dist/inspector/sketch/SketchWorkspace.js +152 -0
- package/dist/inspector/sketch/capture.d.ts +55 -0
- package/dist/inspector/sketch/capture.js +350 -0
- package/dist/inspector/sketch/freehand.d.ts +13 -0
- package/dist/inspector/sketch/freehand.js +64 -0
- package/dist/inspector/sketch/geometry.d.ts +17 -0
- package/dist/inspector/sketch/geometry.js +98 -0
- package/dist/inspector/sketch/handoff.d.ts +21 -0
- package/dist/inspector/sketch/handoff.js +72 -0
- package/dist/inspector/sketch/interaction.d.ts +21 -0
- package/dist/inspector/sketch/interaction.js +98 -0
- package/dist/inspector/sketch/model.d.ts +104 -0
- package/dist/inspector/sketch/model.js +282 -0
- package/dist/inspector/sketch/persistence.d.ts +21 -0
- package/dist/inspector/sketch/persistence.js +205 -0
- package/dist/inspector/sketch/raster.d.ts +22 -0
- package/dist/inspector/sketch/raster.js +203 -0
- package/dist/inspector/sketch/sketchNote.d.ts +10 -0
- package/dist/inspector/sketch/sketchNote.js +30 -0
- package/dist/inspector/sketch/store.d.ts +47 -0
- package/dist/inspector/sketch/store.js +434 -0
- package/dist/inspector/styleEditors/BorderEditor.js +15 -19
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
- package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
- package/dist/inspector/styleEditors/ColorPicker.js +11 -36
- package/dist/inspector/styleEditors/GapField.d.ts +3 -2
- package/dist/inspector/styleEditors/GapField.js +2 -2
- package/dist/inspector/styleEditors/GridSection.js +1 -1
- package/dist/inspector/styleEditors/LayoutSection.js +11 -8
- package/dist/inspector/styleEditors/SpacingBox.js +31 -33
- package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
- package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
- package/dist/inspector/styleEditors/layoutValue.js +3 -0
- package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
- package/dist/inspector/styleEditors/nudgeValue.js +93 -5
- package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
- package/dist/inspector/styleEditors/stylePresence.js +22 -0
- package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
- package/dist/inspector/styleEditors/textRelevance.js +35 -0
- package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
- package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
- package/dist/inspector/tokens/TokenField.js +130 -7
- package/dist/inspector/tokens/TokensPanel.js +2 -1
- package/dist/inspector/tokens/dragPointerLock.d.ts +8 -0
- package/dist/inspector/tokens/dragPointerLock.js +70 -0
- package/dist/inspector/tokens/editActions.d.ts +2 -0
- package/dist/inspector/tokens/editActions.js +2 -2
- package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
- package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
- package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
- package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
- package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
- package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
- package/dist/inspector/tokens/resolution.d.ts +3 -26
- package/dist/inspector/tokens/resolution.js +44 -735
- package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
- package/dist/inspector/tokens/tokenAvailability.js +204 -0
- package/dist/inspector/ui/Button.d.ts +1 -1
- package/dist/inspector/ui/Disclosure.d.ts +22 -0
- package/dist/inspector/ui/Disclosure.js +13 -0
- package/dist/inspector/ui/SideValuesField.js +2 -2
- package/dist/inspector/ui/StatusCallout.d.ts +4 -1
- package/dist/inspector/ui/StatusCallout.js +2 -2
- package/dist/inspector/ui/dragCursor.d.ts +8 -0
- package/dist/inspector/ui/dragCursor.js +21 -0
- package/dist/inspector/ui/styles.js +21 -17
- package/dist/internal/agent-protocol.d.ts +65 -1
- package/dist/internal/agent-protocol.js +183 -3
- package/dist/nudge-ui.mjs +276 -58
- package/dist/transport/editor.d.ts +47 -0
- package/dist/transport/editor.js +163 -0
- package/dist/transport/index.d.ts +1 -0
- package/dist/transport/index.js +1 -0
- package/dist/transport/routes.d.ts +10 -0
- package/dist/transport/routes.js +10 -0
- package/package.json +3 -1
- package/dist/inspector/shell/DomNavigation.d.ts +0 -8
- package/dist/inspector/shell/DomNavigation.js +0 -30
- package/dist/inspector/shell/panelLayout.d.ts +0 -2
- package/dist/inspector/shell/panelLayout.js +0 -29
|
@@ -137,7 +137,7 @@ function pruneCheckpoint() {
|
|
|
137
137
|
* Checks a copied prompt against source-rendered output and reconciles only
|
|
138
138
|
* exact records that the browser can positively verify.
|
|
139
139
|
*/
|
|
140
|
-
export async function reconcileClipboardHandoff() {
|
|
140
|
+
export async function reconcileClipboardHandoff(doc = document) {
|
|
141
141
|
const snapshot = checkpoint;
|
|
142
142
|
if (!snapshot)
|
|
143
143
|
return 0;
|
|
@@ -147,7 +147,7 @@ export async function reconcileClipboardHandoff() {
|
|
|
147
147
|
pruneCheckpoint();
|
|
148
148
|
return 0;
|
|
149
149
|
}
|
|
150
|
-
const removed = await verifyAndReconcileHandoff({ changes, structuralChanges });
|
|
150
|
+
const removed = await verifyAndReconcileHandoff({ changes, structuralChanges }, doc);
|
|
151
151
|
pruneCheckpoint();
|
|
152
152
|
if (removed > 0) {
|
|
153
153
|
lastReconciledCount = removed;
|
|
@@ -175,7 +175,7 @@ export function startClipboardHandoffController(doc = document) {
|
|
|
175
175
|
if (stopped || checking || !checkpoint)
|
|
176
176
|
return;
|
|
177
177
|
checking = true;
|
|
178
|
-
void reconcileClipboardHandoff()
|
|
178
|
+
void reconcileClipboardHandoff(doc)
|
|
179
179
|
.catch(() => undefined)
|
|
180
180
|
.finally(() => {
|
|
181
181
|
// Keep the guard through the MutationObserver microtask generated by
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ChangeRecord } from "../changes/changesLog.js";
|
|
2
2
|
import type { StructuralChange } from "../projection/structuralProjection.js";
|
|
3
|
+
import type { AgentSketchMetadata } from "../agent/protocol.js";
|
|
3
4
|
export interface FrameworkHints {
|
|
4
5
|
framework?: string;
|
|
5
6
|
stylingSystem?: string;
|
|
6
7
|
}
|
|
7
|
-
export declare function generatePrompt(changes: ChangeRecord[], frameworkHints?: FrameworkHints, structuralChanges?: readonly StructuralChange[], customInstructions?: string): string;
|
|
8
|
+
export declare function generatePrompt(changes: ChangeRecord[], frameworkHints?: FrameworkHints, structuralChanges?: readonly StructuralChange[], customInstructions?: string, sketches?: readonly AgentSketchMetadata[]): string;
|
|
@@ -244,6 +244,17 @@ function structuralChangeLine(change) {
|
|
|
244
244
|
? `- Move ${target} before ${renderedInstanceDescription(before)} in ${destinationParent}.`
|
|
245
245
|
: `- Move ${target} to the end of ${destinationParent}.`;
|
|
246
246
|
}
|
|
247
|
+
function sketchLines(sketches) {
|
|
248
|
+
const titles = [...new Set(sketches.map((sketch) => sketch.capture.title || "(untitled)"))];
|
|
249
|
+
return [
|
|
250
|
+
...titles.map((title) => `- Page title: ${promptText(title)}`),
|
|
251
|
+
...sketches.flatMap((sketch) => [
|
|
252
|
+
...(sketch.description ? [`- Description: ${promptText(sketch.description)}`] : []),
|
|
253
|
+
...(sketch.annotations ?? []).map((annotation) => `- Annotation ${annotation.number}: ${promptText(annotation.description)}`),
|
|
254
|
+
]),
|
|
255
|
+
"- Interpret the attached frozen screenshot, blue strokes, and numbered annotations as visual feedback. Do not derive selectors, source coordinates, or semantics from the strokes. Use each annotation number to match the note to its numbered dot.",
|
|
256
|
+
];
|
|
257
|
+
}
|
|
247
258
|
function renderedInstanceKey(ref) {
|
|
248
259
|
const { sourceSite, locator } = ref;
|
|
249
260
|
return JSON.stringify([
|
|
@@ -304,10 +315,10 @@ function canonicalizeStructuralChanges(changes) {
|
|
|
304
315
|
}
|
|
305
316
|
return canonical;
|
|
306
317
|
}
|
|
307
|
-
export function generatePrompt(changes, frameworkHints, structuralChanges = [], customInstructions = DEFAULT_CUSTOM_INSTRUCTIONS) {
|
|
318
|
+
export function generatePrompt(changes, frameworkHints, structuralChanges = [], customInstructions = DEFAULT_CUSTOM_INSTRUCTIONS, sketches = []) {
|
|
308
319
|
const deduplicated = canonicalizeChanges(changes);
|
|
309
320
|
const structuralIntent = canonicalizeStructuralChanges(structuralChanges);
|
|
310
|
-
if (deduplicated.length === 0 && structuralIntent.length === 0)
|
|
321
|
+
if (deduplicated.length === 0 && structuralIntent.length === 0 && sketches.length === 0)
|
|
311
322
|
return EMPTY_SENTINEL;
|
|
312
323
|
const tokenChanges = deduplicated.filter(isTokenChange);
|
|
313
324
|
const componentChanges = deduplicated.filter(isComponentChange);
|
|
@@ -387,5 +398,11 @@ export function generatePrompt(changes, frameworkHints, structuralChanges = [],
|
|
|
387
398
|
lines: structuralIntent.map(structuralChangeLine),
|
|
388
399
|
});
|
|
389
400
|
}
|
|
401
|
+
if (sketches.length > 0) {
|
|
402
|
+
sections.push({
|
|
403
|
+
heading: "Sketch annotations",
|
|
404
|
+
lines: sketchLines(sketches),
|
|
405
|
+
});
|
|
406
|
+
}
|
|
390
407
|
return renderPrompt(sections, customInstructions);
|
|
391
408
|
}
|
|
@@ -24,7 +24,7 @@ export interface NudgeUiRuntimeCapabilities {
|
|
|
24
24
|
readonly canvas: boolean;
|
|
25
25
|
/** Whether framework component inspection and prop overrides are available. */
|
|
26
26
|
readonly componentSemantics: boolean;
|
|
27
|
-
/** Whether the
|
|
27
|
+
/** Whether the DOM navigator overlay is available. */
|
|
28
28
|
readonly domNavigation?: boolean;
|
|
29
29
|
/**
|
|
30
30
|
* Source-coordinate precision policy for prompts; every source site is
|
|
@@ -60,6 +60,10 @@ export interface NudgeUiRuntimeConfig {
|
|
|
60
60
|
readonly componentContracts: readonly ComponentContract[];
|
|
61
61
|
/** Whether this runtime is the explicit public landing-page demo. */
|
|
62
62
|
readonly demo?: boolean;
|
|
63
|
+
/** Additional same-origin pages seeded into the public demo canvas. */
|
|
64
|
+
readonly demoPages?: readonly string[];
|
|
65
|
+
/** Labels for demo cards in display order, including the primary card. */
|
|
66
|
+
readonly demoCardLabels?: readonly string[];
|
|
63
67
|
}
|
|
64
68
|
/**
|
|
65
69
|
* Validates a host-supplied configuration and fills safe defaults.
|
|
@@ -170,6 +170,8 @@ export function normalizeNudgeUiRuntimeConfig(input) {
|
|
|
170
170
|
if (isNormalizedRuntimeConfig(input))
|
|
171
171
|
return input;
|
|
172
172
|
const demo = optionalDemoFlag(input);
|
|
173
|
+
const demoPages = optionalStringArray(input, "demoPages");
|
|
174
|
+
const demoCardLabels = optionalStringArray(input, "demoCardLabels");
|
|
173
175
|
const normalized = cloneAndFreeze({
|
|
174
176
|
projectId: requireString(input, "projectId"),
|
|
175
177
|
host: requireEnum(input, "host", RUNTIME_HOSTS),
|
|
@@ -182,6 +184,8 @@ export function normalizeNudgeUiRuntimeConfig(input) {
|
|
|
182
184
|
tokenGeneration: optionalString(input, "tokenGeneration"),
|
|
183
185
|
componentContracts: optionalArray(input, "componentContracts"),
|
|
184
186
|
...(demo === true ? { demo: true } : {}),
|
|
187
|
+
...(demoPages === undefined ? {} : { demoPages }),
|
|
188
|
+
...(demoCardLabels === undefined ? {} : { demoCardLabels }),
|
|
185
189
|
});
|
|
186
190
|
normalizedRuntimeConfigs.add(normalized);
|
|
187
191
|
return normalized;
|
|
@@ -3,5 +3,11 @@ export interface DescendantNode {
|
|
|
3
3
|
element: HTMLElement;
|
|
4
4
|
depth: number;
|
|
5
5
|
}
|
|
6
|
+
export interface NavigationNode {
|
|
7
|
+
element: HTMLElement;
|
|
8
|
+
direction: "up" | "down";
|
|
9
|
+
depth: number;
|
|
10
|
+
}
|
|
6
11
|
/** Returns the nearest tracked descendants, breadth-first and document-ordered. */
|
|
7
12
|
export declare function computeDescendants(el: HTMLElement, maxDepth?: number, maxNodes?: number): DescendantNode[];
|
|
13
|
+
export declare function computeNavigationNodes(el: HTMLElement, hierarchy?: HTMLElement[]): NavigationNode[];
|
|
@@ -40,3 +40,11 @@ export function computeDescendants(el, maxDepth = 2, maxNodes = 2) {
|
|
|
40
40
|
.sort((left, right) => left.depth - right.depth || left.order - right.order);
|
|
41
41
|
return descendants.slice(0, maxNodes).map(({ element, depth }) => ({ element, depth }));
|
|
42
42
|
}
|
|
43
|
+
export function computeNavigationNodes(el, hierarchy = computeHierarchy(el)) {
|
|
44
|
+
const children = computeDescendants(el, 2, 2);
|
|
45
|
+
const parents = hierarchy.slice(1, (children.length > 0 ? 2 : 4) + 1);
|
|
46
|
+
return [
|
|
47
|
+
...parents.map((element, index) => ({ element, direction: "up", depth: index + 1 })),
|
|
48
|
+
...children.map(({ element, depth }) => ({ element, direction: "down", depth })),
|
|
49
|
+
];
|
|
50
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useSyncExternalStore } from "react";
|
|
2
2
|
import { computeHierarchy } from "./hierarchy.js";
|
|
3
3
|
import { resolveSelectionFromElement } from "./resolveSelection.js";
|
|
4
|
+
import { clearInlineTextDiagnosticsForSelection } from "../inline-text/inlineTextDiagnostics.js";
|
|
4
5
|
let currentSelection = null;
|
|
5
6
|
const EMPTY_SELECTION = [];
|
|
6
7
|
let chain = [];
|
|
@@ -28,6 +29,7 @@ function getHierarchyIndex() {
|
|
|
28
29
|
return index;
|
|
29
30
|
}
|
|
30
31
|
function notify() {
|
|
32
|
+
clearInlineTextDiagnosticsForSelection(getSelectedElements().map((element) => element.domElement));
|
|
31
33
|
listeners.forEach((l) => l());
|
|
32
34
|
}
|
|
33
35
|
function sameComponentTargets(a, b) {
|
|
@@ -9,4 +9,4 @@ export interface SettingsDialogProps extends McpConnectionContentProps {
|
|
|
9
9
|
readonly onOpenChange: (open: boolean) => void;
|
|
10
10
|
}
|
|
11
11
|
/** Provides one settings surface for prompt, MCP, and token configuration. */
|
|
12
|
-
export declare function SettingsDialog({ open, initialSection, value, onValueChange, onOpenChange, projectId, origin, snapshot, onConnect, onDisconnect, onCheckAgain, }: SettingsDialogProps): ReactElement;
|
|
12
|
+
export declare function SettingsDialog({ open, initialSection, value, onValueChange, onOpenChange, projectId, origin, snapshot, onConnect, onTakeOver, onDisconnect, onCheckAgain, }: SettingsDialogProps): ReactElement;
|
|
@@ -6,6 +6,10 @@ import { useBrowserCssInspection } from "../inspection/useBrowserCssInspection.j
|
|
|
6
6
|
import { McpConnectionContent, } from "../agent/McpConnectionDialog.js";
|
|
7
7
|
import { PromptSettingsFields, } from "../prompt/PromptSettingsDialog.js";
|
|
8
8
|
import { TokensPanel } from "../tokens/TokensPanel.js";
|
|
9
|
+
import { useSelectedElement } from "../selection/selectionStore.js";
|
|
10
|
+
import { useFocusedCardId, useSelectedCardId } from "../canvas/canvasStore.js";
|
|
11
|
+
import { getRegisteredFrames } from "../canvas/projection.js";
|
|
12
|
+
import { getBrowserCssInspection } from "../inspection/browserCssInspectionRegistry.js";
|
|
9
13
|
const SETTINGS_SECTIONS = [
|
|
10
14
|
{
|
|
11
15
|
id: "instructions",
|
|
@@ -29,12 +33,18 @@ function portalContainer() {
|
|
|
29
33
|
: null;
|
|
30
34
|
}
|
|
31
35
|
/** Provides one settings surface for prompt, MCP, and token configuration. */
|
|
32
|
-
export function SettingsDialog({ open, initialSection, value, onValueChange, onOpenChange, projectId, origin, snapshot, onConnect, onDisconnect, onCheckAgain, }) {
|
|
36
|
+
export function SettingsDialog({ open, initialSection, value, onValueChange, onOpenChange, projectId, origin, snapshot, onConnect, onTakeOver, onDisconnect, onCheckAgain, }) {
|
|
33
37
|
const [activeSection, setActiveSection] = useState(initialSection);
|
|
34
38
|
const closeRef = useRef(null);
|
|
35
39
|
const instructionsRef = useRef(null);
|
|
40
|
+
const selectedElement = useSelectedElement();
|
|
41
|
+
const selectedCardId = useSelectedCardId();
|
|
42
|
+
const focusedCardId = useFocusedCardId();
|
|
43
|
+
const activeFrame = getRegisteredFrames().get(selectedCardId ?? focusedCardId ?? "");
|
|
44
|
+
const activeDocument = selectedElement?.domElement.ownerDocument ?? activeFrame?.contentDocument ?? document;
|
|
36
45
|
const tokenInspection = useBrowserCssInspection(null, "base", {
|
|
37
46
|
includeDocumentTokens: activeSection === "tokens",
|
|
47
|
+
session: getBrowserCssInspection(activeDocument),
|
|
38
48
|
});
|
|
39
49
|
const tokenRows = tokenInspection.documentTokens?.tokens ?? [];
|
|
40
50
|
const activeDefinition = SETTINGS_SECTIONS.find((section) => section.id === activeSection) ?? SETTINGS_SECTIONS[0];
|
|
@@ -42,5 +52,5 @@ export function SettingsDialog({ open, initialSection, value, onValueChange, onO
|
|
|
42
52
|
if (open)
|
|
43
53
|
setActiveSection(initialSection);
|
|
44
54
|
}, [initialSection, open]);
|
|
45
|
-
return (_jsx(Dialog.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(Dialog.Portal, { container: portalContainer(), children: [_jsx(Dialog.Backdrop, { className: "settings__backdrop", "data-test": "settings-backdrop" }), _jsxs(Dialog.Popup, { className: "settings__popup", "data-test": "settings-dialog", initialFocus: closeRef, children: [_jsx(Dialog.Title, { className: "settings__visually-hidden", children: "Settings" }), _jsx(Dialog.Description, { className: "settings__visually-hidden", children: "Configure how Nudge connects to your coding agent and uses your design system." }), _jsx(Dialog.Close, { ref: closeRef, className: "icon-button icon-button--quiet settings__close", "aria-label": "Close settings", "data-test": "settings-close", type: "button", children: _jsx(IconX, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) }), _jsxs("div", { className: "settings__body", children: [_jsxs("nav", { className: "settings__nav", "aria-label": "Settings sections", children: [_jsx("span", { className: "settings__nav-label", children: "Settings" }), _jsx("div", { className: "settings__nav-items", children: SETTINGS_SECTIONS.map((section) => (_jsxs("button", { className: `settings__nav-item${activeSection === section.id ? " settings__nav-item--active" : ""}`, "data-test": `settings-nav-${section.id}`, type: "button", "aria-current": activeSection === section.id ? "page" : undefined, onClick: () => setActiveSection(section.id), children: [section.icon, _jsx("span", { className: "settings__nav-item-label", children: section.label })] }, section.id))) })] }), _jsxs("main", { className: "settings__content", "data-test": "settings-content", children: [activeSection === "instructions" ? (_jsxs("section", { className: "settings__section", "data-test": "prompt-settings-dialog", "data-settings-section": "instructions", children: [_jsx("h2", { className: "settings__section-title", children: activeDefinition.label }), _jsx("p", { className: "settings__section-description", children: "These instructions are added to the end of every copied prompt." }), _jsx(PromptSettingsFields, { value: value, onChange: onValueChange, instructionsRef: instructionsRef })] })) : null, activeSection === "mcp" ? (_jsxs("section", { className: "settings__section", "data-test": "settings-section-mcp", children: [_jsx("h2", { className: "settings__section-title", children: activeDefinition.label }), _jsx("div", { "data-test": "mcp-connection-dialog", children: _jsx(McpConnectionContent, { projectId: projectId, origin: origin, snapshot: snapshot, onConnect: onConnect, onDisconnect: onDisconnect, onCheckAgain: onCheckAgain }) })] })) : null, activeSection === "tokens" ? (_jsxs("section", { className: "settings__section", "data-test": "settings-section-tokens", children: [_jsx("h2", { className: "settings__section-title", children: activeDefinition.label }), _jsx("p", { className: "settings__section-description", children: "Inspect and edit the design tokens available in this page." }), _jsx(TokensPanel, { rows: tokenRows })] })) : null] })] })] })] }) }));
|
|
55
|
+
return (_jsx(Dialog.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(Dialog.Portal, { container: portalContainer(), children: [_jsx(Dialog.Backdrop, { className: "settings__backdrop", "data-test": "settings-backdrop" }), _jsxs(Dialog.Popup, { className: "settings__popup", "data-test": "settings-dialog", initialFocus: closeRef, children: [_jsx(Dialog.Title, { className: "settings__visually-hidden", children: "Settings" }), _jsx(Dialog.Description, { className: "settings__visually-hidden", children: "Configure how Nudge connects to your coding agent and uses your design system." }), _jsx(Dialog.Close, { ref: closeRef, className: "icon-button icon-button--quiet settings__close", "aria-label": "Close settings", "data-test": "settings-close", type: "button", children: _jsx(IconX, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) }), _jsxs("div", { className: "settings__body", children: [_jsxs("nav", { className: "settings__nav", "aria-label": "Settings sections", children: [_jsx("span", { className: "settings__nav-label", children: "Settings" }), _jsx("div", { className: "settings__nav-items", children: SETTINGS_SECTIONS.map((section) => (_jsxs("button", { className: `settings__nav-item${activeSection === section.id ? " settings__nav-item--active" : ""}`, "data-test": `settings-nav-${section.id}`, type: "button", "aria-current": activeSection === section.id ? "page" : undefined, onClick: () => setActiveSection(section.id), children: [section.icon, _jsx("span", { className: "settings__nav-item-label", children: section.label })] }, section.id))) })] }), _jsxs("main", { className: "settings__content", "data-test": "settings-content", children: [activeSection === "instructions" ? (_jsxs("section", { className: "settings__section", "data-test": "prompt-settings-dialog", "data-settings-section": "instructions", children: [_jsx("h2", { className: "settings__section-title", children: activeDefinition.label }), _jsx("p", { className: "settings__section-description", children: "These instructions are added to the end of every copied prompt." }), _jsx(PromptSettingsFields, { value: value, onChange: onValueChange, instructionsRef: instructionsRef })] })) : null, activeSection === "mcp" ? (_jsxs("section", { className: "settings__section", "data-test": "settings-section-mcp", children: [_jsx("h2", { className: "settings__section-title", children: activeDefinition.label }), _jsx("div", { "data-test": "mcp-connection-dialog", children: _jsx(McpConnectionContent, { projectId: projectId, origin: origin, snapshot: snapshot, onConnect: onConnect, onTakeOver: onTakeOver, onDisconnect: onDisconnect, onCheckAgain: onCheckAgain }) })] })) : null, activeSection === "tokens" ? (_jsxs("section", { className: "settings__section", "data-test": "settings-section-tokens", children: [_jsx("h2", { className: "settings__section-title", children: activeDefinition.label }), _jsx("p", { className: "settings__section-description", children: "Inspect and edit the design tokens available in this page." }), _jsx(TokensPanel, { rows: tokenRows })] })) : null] })] })] })] }) }));
|
|
46
56
|
}
|
|
@@ -3,7 +3,11 @@ import { InspectorShell } from "./InspectorShell.js";
|
|
|
3
3
|
import { CanvasWorkspace } from "../canvas/CanvasWorkspace.js";
|
|
4
4
|
import { CanvasElementOverlay } from "../canvas/CanvasElementOverlay.js";
|
|
5
5
|
import { useNudgeUiRuntimeConfig } from "../runtime/useRuntimeConfig.js";
|
|
6
|
+
import { readNudgeUiEditorTarget } from "../../transport/editor.js";
|
|
6
7
|
export function AppShell() {
|
|
7
8
|
const canvasEnabled = useNudgeUiRuntimeConfig().capabilities.canvas;
|
|
8
|
-
|
|
9
|
+
const primaryUrl = typeof window === "undefined"
|
|
10
|
+
? null
|
|
11
|
+
: readNudgeUiEditorTarget(window.location.href);
|
|
12
|
+
return (_jsxs(_Fragment, { children: [canvasEnabled ? _jsx(CanvasWorkspace, { primaryUrl: primaryUrl }) : null, canvasEnabled ? _jsx(CanvasElementOverlay, {}) : null, _jsx(InspectorShell, {})] }));
|
|
9
13
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo, useSyncExternalStore } from "react";
|
|
3
|
-
import { IconChevronDown } from "@tabler/icons-react";
|
|
4
3
|
import { isElementChange, useChanges, revertChange } from "../changes/changesLog.js";
|
|
5
4
|
import { StaleChangeIndicator } from "../canvas/StaleChangeIndicator.js";
|
|
6
5
|
import { Button } from "../ui/Button.js";
|
|
6
|
+
import { Disclosure } from "../ui/Disclosure.js";
|
|
7
7
|
import { presentChange } from "../changes/presentation.js";
|
|
8
8
|
import { getStructuralChanges, getStructuralChangeDiagnostics, getStructuralDiagnosticRevision, revertStructuralChange, subscribeStructuralChanges, subscribeStructuralDiagnostics, } from "../projection/structuralProjection.js";
|
|
9
9
|
import { getRenderedInstanceChangeDiagnostics, getRenderedInstanceDiagnosticRevision, subscribeRenderedInstanceDiagnostics, } from "../projection/renderedInstance.js";
|
|
10
10
|
import { getTextContentChangeDiagnostics, getTextProjectionDiagnosticRevision, subscribeTextProjectionDiagnostics, } from "../projection/textProjection.js";
|
|
11
|
+
import { SketchChanges } from "../sketch/SketchChanges.js";
|
|
12
|
+
import { useSketchStore } from "../sketch/store.js";
|
|
11
13
|
function groupChanges(changes) {
|
|
12
14
|
const map = new Map();
|
|
13
15
|
for (const change of changes) {
|
|
@@ -60,6 +62,8 @@ function structuralDiagnosticText(diagnostic) {
|
|
|
60
62
|
}
|
|
61
63
|
export function ChangesLog({ onClearSession }) {
|
|
62
64
|
const changes = useChanges();
|
|
65
|
+
const sketchStore = useSketchStore();
|
|
66
|
+
const sketches = sketchStore.items;
|
|
63
67
|
const structuralChanges = useSyncExternalStore(subscribeStructuralChanges, getStructuralChanges, getStructuralChanges);
|
|
64
68
|
// Diagnostics arrive independently from structural state when a renderer
|
|
65
69
|
// resolves a snapshot or its document observer detects a reconciliation.
|
|
@@ -67,26 +71,26 @@ export function ChangesLog({ onClearSession }) {
|
|
|
67
71
|
useSyncExternalStore(subscribeRenderedInstanceDiagnostics, getRenderedInstanceDiagnosticRevision, getRenderedInstanceDiagnosticRevision);
|
|
68
72
|
useSyncExternalStore(subscribeTextProjectionDiagnostics, getTextProjectionDiagnosticRevision, getTextProjectionDiagnosticRevision);
|
|
69
73
|
const groups = useMemo(() => groupChanges(changes), [changes]);
|
|
70
|
-
const total = changes.length + structuralChanges.length;
|
|
71
|
-
if (total === 0)
|
|
74
|
+
const total = changes.length + structuralChanges.length + sketches.length;
|
|
75
|
+
if (total === 0 && sketchStore.error === null)
|
|
72
76
|
return null;
|
|
73
|
-
return (_jsxs(_Fragment, { children: [
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
77
|
+
return (_jsxs(_Fragment, { children: [_jsx(Disclosure, { className: "changes", "data-test": "changes-log", triggerDataTest: "changes-toggle", title: "Changes", badge: total > 0 ? _jsx("span", { className: "changes__count", children: total }) : null, panelClassName: "changes__content", children: groups.length === 0 && structuralChanges.length === 0 && sketches.length === 0 && sketchStore.error === null ? (_jsx("div", { className: "changes__empty", "data-test": "changes-empty", children: "No changes yet" })) : (_jsxs(_Fragment, { children: [groups.map((group) => (_jsxs("div", { className: "changes__group", "data-test": "changes-group", "data-cid": group.label, children: [_jsxs("div", { className: "changes__group-title", children: [_jsx("span", { children: group.label }), _jsx("span", { className: "changes__group-file", children: group.file })] }), group.changes.map((change, i) => {
|
|
78
|
+
const presentation = presentChange(change);
|
|
79
|
+
const instanceDiagnostics = isElementChange(change) && change.scope === "rendered-instance"
|
|
80
|
+
&& change.instanceOverride
|
|
81
|
+
? getRenderedInstanceChangeDiagnostics(change.instanceOverride.id)
|
|
82
|
+
: [];
|
|
83
|
+
const textDiagnostics = change.kind === "text-content"
|
|
84
|
+
? getTextContentChangeDiagnostics(change.id)
|
|
85
|
+
: [];
|
|
86
|
+
return (_jsxs("div", { className: "changes__row", "data-test": "change-row", "data-property": presentation.property, children: [_jsx("span", { className: "changes__prop", children: presentation.propertyLabel }), _jsxs("span", { className: "changes__value", children: [_jsx("span", { className: "changes__before", children: presentation.before }), _jsx("span", { className: "changes__arrow", children: "\u2192" }), _jsx("span", { className: "changes__after", children: presentation.after })] }), presentation.scope ? (_jsx("span", { className: "changes__scope", "data-test": "change-scope", children: presentation.scope === "source-site" ? "All outputs at source site" : "This rendered item only" })) : null, presentation.evidence ? (_jsx("span", { className: "changes__evidence", "data-test": "change-evidence", children: presentation.evidence })) : null, _jsx(StaleChangeIndicator, { change: change }), instanceDiagnostics.map((diagnostic) => (_jsxs("span", { className: "changes__diagnostic", "data-test": "instance-diagnostic", "data-document": diagnostic.document, "data-status": diagnostic.status, children: [diagnostic.document, ": ", diagnostic.status] }, `${diagnostic.document}:${diagnostic.status}`))), textDiagnostics.map((diagnostic) => (_jsxs("span", { className: "changes__diagnostic", "data-test": "text-projection-diagnostic", "data-document": diagnostic.document, "data-status": diagnostic.status, children: [diagnostic.document, ": ", diagnostic.status] }, `${diagnostic.document}:${diagnostic.status}`))), _jsx(Button, { size: "compact", className: "changes__revert", "data-test": "change-revert", "data-property": presentation.property, onClick: () => revertChange(change), children: "Revert" })] }, `${group.key}\u0000${presentation.property}\u0000${i}`));
|
|
87
|
+
})] }, group.key))), structuralChanges.map((change) => (_jsxs("div", { className: "changes__group", "data-test": "dom-change", "data-cid": change.target.sourceSite.cid, children: [_jsxs("div", { className: "changes__group-title", children: [_jsx("span", { children: change.target.sourceSite.cid }), _jsx("span", { className: "changes__group-file", children: sourceFile(change) })] }), _jsxs("div", { className: "changes__row", "data-test": "dom-change-row", "data-action": change.kind, children: [_jsxs("span", { className: "changes__source-site", "data-test": "structural-source-site", children: ["Source site: ", change.target.sourceSite.cid] }), _jsx("span", { className: "changes__prop", children: change.kind === "move" ? "Move in DOM" : "Delete from DOM" }), _jsxs("span", { className: "changes__value", children: [_jsx("span", { className: "changes__before", children: change.kind === "move"
|
|
88
|
+
? structuralParentIdentityKey(change.source.parent) === structuralParentIdentityKey(change.destination.parent)
|
|
89
|
+
? `${change.presentation.sourceParentTag} position ${change.presentation.fromIndex + 1}`
|
|
90
|
+
: `${structuralParentLabel(change, "source")} position ${change.presentation.fromIndex + 1}`
|
|
91
|
+
: "Visible" }), _jsx("span", { className: "changes__arrow", children: "\u2192" }), _jsx("span", { className: "changes__after", children: change.kind === "move"
|
|
92
|
+
? structuralParentIdentityKey(change.source.parent) === structuralParentIdentityKey(change.destination.parent)
|
|
93
|
+
? `${change.presentation.destinationParentTag} position ${change.presentation.toIndex + 1}`
|
|
94
|
+
: `${structuralParentLabel(change, "destination")} position ${change.presentation.toIndex + 1}`
|
|
95
|
+
: "Removed" })] }), _jsx("span", { className: "changes__scope", "data-test": "structural-scope", children: "This rendered item only" }), getStructuralChangeDiagnostics(change.id).map((diagnostic) => (_jsx("span", { className: "changes__diagnostic", "data-test": "structural-diagnostic", "data-document": diagnostic.document, "data-status": diagnostic.status, "data-reason": diagnostic.reason, children: structuralDiagnosticText(diagnostic) }, `${diagnostic.document}:${diagnostic.status}:${diagnostic.reason ?? ""}`))), _jsx(Button, { size: "compact", className: "changes__revert", "data-test": "dom-change-revert", onClick: () => revertStructuralChange(change.id), children: "Revert" })] })] }, change.id))), _jsx(SketchChanges, {})] })) }), onClearSession ? (_jsx("div", { className: "changes__session-action", "data-test": "session-actions", children: _jsx(Button, { variant: "secondary", type: "button", "data-test": "clear-session", onClick: onClearSession, children: "Clear Changes" }) })) : null] }));
|
|
92
96
|
}
|
|
@@ -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,
|
|
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 [
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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,19 @@ export function CopyPromptButton({ settingsOpen: controlledSettingsOpen, setting
|
|
|
153
224
|
function openMcpConnection() {
|
|
154
225
|
openSettings("mcp");
|
|
155
226
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
227
|
+
async function handleAgentStatusAction() {
|
|
228
|
+
if (statusAction?.kind === "takeover") {
|
|
229
|
+
await agentClient.takeOver();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
openMcpConnection();
|
|
233
|
+
}
|
|
234
|
+
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 || agentStatus.kind === "connected-not-listening" ? (_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 }), statusAction ? (_jsx(Button, { size: "compact", variant: "quiet", "data-test": "agent-status-action", "data-action": statusAction.kind, type: "button", onClick: () => void handleAgentStatusAction(), children: statusAction.label })) : null] }) })) : 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: () => {
|
|
235
|
+
void copyToClipboard(sketchFallback.prompt).then(() => {
|
|
236
|
+
if (changes.length > 0 || structuralChanges.length > 0) {
|
|
237
|
+
recordClipboardHandoff(changes, structuralChanges);
|
|
238
|
+
}
|
|
239
|
+
setSketchFallback(null);
|
|
240
|
+
}).catch(() => undefined);
|
|
241
|
+
}, 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(), onTakeOver: () => agentClient.takeOver(), onDisconnect: () => agentClient.disconnect(), onCheckAgain: () => agentClient.checkConnection() })] }));
|
|
159
242
|
}
|
|
@@ -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;
|