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.
- 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 +872 -12
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
- package/dist/hosts/astro/clientTransport.js +60 -11
- package/dist/hosts/next/sidecar.d.ts +4 -0
- package/dist/hosts/next/sidecar.js +49 -6
- package/dist/hosts/next/wrapper.js +30 -2
- package/dist/hosts/projectBridge.d.ts +20 -0
- package/dist/hosts/projectBridge.js +97 -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 +1 -1
- package/dist/hosts/static/server.js +44 -3
- package/dist/hosts/vite/vite.d.ts +5 -1
- package/dist/hosts/vite/vite.js +78 -14
- package/dist/inspector/agent/McpConnectionDialog.d.ts +2 -2
- package/dist/inspector/agent/McpConnectionDialog.js +11 -35
- package/dist/inspector/agent/client.d.ts +10 -3
- package/dist/inspector/agent/client.js +80 -9
- package/dist/inspector/agent/connectionStatus.js +3 -3
- package/dist/inspector/agent/httpTransport.d.ts +2 -0
- package/dist/inspector/agent/httpTransport.js +37 -17
- package/dist/inspector/agent/protocol.d.ts +8 -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 +246 -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 +392 -73
- 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 +69 -5
- package/dist/inspector/canvas/frameProtocol.js +56 -2
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -0
- package/dist/inspector/canvas/keyboardShortcuts.js +14 -0
- package/dist/inspector/canvas/projection.d.ts +7 -0
- package/dist/inspector/canvas/projection.js +118 -25
- package/dist/inspector/canvas/rendererBootstrap.js +29 -29
- package/dist/inspector/canvas/rendererElementSelector.js +341 -30
- 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/clientManifest.d.ts +5 -0
- package/dist/inspector/clientManifest.js +11 -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 +74 -24
- 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/selectedGeometry.js +21 -2
- package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
- package/dist/inspector/overlay/spacingGestures.js +353 -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 +4 -0
- package/dist/inspector/runtime/runtimeConfig.js +4 -0
- package/dist/inspector/selection/selectionStore.js +2 -0
- package/dist/inspector/settings/SettingsDialog.js +10 -0
- package/dist/inspector/shell/AppShell.js +5 -1
- package/dist/inspector/shell/ChangesLog.js +8 -4
- package/dist/inspector/shell/CopyPromptButton.js +95 -19
- package/dist/inspector/shell/EmptyState.js +1 -1
- package/dist/inspector/shell/InspectorShell.d.ts +0 -1
- package/dist/inspector/shell/InspectorShell.js +37 -38
- 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 +409 -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 +23 -30
- 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 +91 -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 +111 -7
- 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/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/styles.js +13 -11
- package/dist/internal/agent-protocol.d.ts +65 -1
- package/dist/internal/agent-protocol.js +183 -3
- package/dist/nudge-ui.mjs +261 -57
- package/dist/transport/editor.d.ts +28 -0
- package/dist/transport/editor.js +132 -0
- package/dist/transport/index.d.ts +1 -0
- package/dist/transport/index.js +1 -0
- package/dist/transport/routes.d.ts +7 -0
- package/dist/transport/routes.js +7 -0
- package/package.json +3 -1
- package/dist/inspector/shell/panelLayout.d.ts +0 -2
- 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,
|
|
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,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 })] }) })) :
|
|
157
|
-
|
|
158
|
-
|
|
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 {
|
|
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,
|
|
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
|
|
93
|
-
const
|
|
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
|
|
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 (
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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",
|
|
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;
|