nudge-ui 0.2.1 → 0.2.3
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/README.md +164 -46
- package/dist/client.mjs +252 -54
- package/dist/css/value-semantics/colorSemantics.d.ts +4 -0
- package/dist/css/value-semantics/colorSemantics.js +17 -0
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -0
- package/dist/hosts/astro/clientTransport.js +41 -32
- package/dist/hosts/astro/integration.d.ts +2 -2
- package/dist/hosts/astro/integration.js +9 -4
- package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
- package/dist/hosts/astro/markdownIdentity.js +81 -0
- package/dist/hosts/environment.d.ts +15 -0
- package/dist/hosts/environment.js +16 -0
- package/dist/hosts/next/manifest.js +1 -1
- package/dist/hosts/next/sidecar.js +12 -2
- package/dist/hosts/next/wrapper.d.ts +2 -0
- package/dist/hosts/next/wrapper.js +2 -1
- package/dist/hosts/projectBridge.d.ts +1 -0
- package/dist/hosts/projectBridge.js +1 -0
- package/dist/hosts/static/manifest.js +3 -1
- package/dist/hosts/static/server.js +4 -0
- package/dist/hosts/vite/vite.d.ts +4 -0
- package/dist/hosts/vite/vite.js +28 -5
- package/dist/inspector/agent/McpConnectionDialog.d.ts +3 -2
- package/dist/inspector/agent/McpConnectionDialog.js +23 -22
- package/dist/inspector/agent/activity.d.ts +18 -0
- package/dist/inspector/agent/activity.js +70 -0
- package/dist/inspector/agent/client.d.ts +8 -0
- package/dist/inspector/agent/client.js +65 -5
- package/dist/inspector/agent/connectionStatus.d.ts +3 -3
- package/dist/inspector/agent/connectionStatus.js +9 -2
- package/dist/inspector/agent/httpTransport.d.ts +6 -0
- package/dist/inspector/agent/httpTransport.js +50 -3
- package/dist/inspector/agent/protocol.d.ts +9 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +11 -1
- package/dist/inspector/agent/verification.js +53 -25
- package/dist/inspector/canvas/AgentActivityOverlay.d.ts +13 -0
- package/dist/inspector/canvas/AgentActivityOverlay.js +111 -0
- package/dist/inspector/canvas/CanvasCard.d.ts +9 -2
- package/dist/inspector/canvas/CanvasCard.js +133 -38
- package/dist/inspector/canvas/CanvasElementOverlay.js +200 -22
- package/dist/inspector/canvas/CanvasToolIcons.d.ts +10 -0
- package/dist/inspector/canvas/CanvasToolIcons.js +20 -0
- package/dist/inspector/canvas/CanvasToolbar.js +8 -4
- package/dist/inspector/canvas/CanvasWorkspace.js +243 -56
- package/dist/inspector/canvas/StaleChangeIndicator.js +3 -13
- package/dist/inspector/canvas/agentPresentation.d.ts +8 -8
- package/dist/inspector/canvas/agentPresentation.js +64 -52
- package/dist/inspector/canvas/canvasGestures.d.ts +1 -1
- package/dist/inspector/canvas/canvasGestures.js +1 -1
- package/dist/inspector/canvas/canvasStore.d.ts +46 -82
- package/dist/inspector/canvas/canvasStore.js +251 -284
- package/dist/inspector/canvas/frameContent.d.ts +21 -0
- package/dist/inspector/canvas/frameContent.js +44 -0
- package/dist/inspector/canvas/frameProtocol.d.ts +34 -4
- package/dist/inspector/canvas/frameProtocol.js +9 -2
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -1
- package/dist/inspector/canvas/keyboardShortcuts.js +5 -3
- package/dist/inspector/canvas/layout.d.ts +52 -0
- package/dist/inspector/canvas/layout.js +106 -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 +15 -5
- package/dist/inspector/canvas/projection.js +69 -13
- package/dist/inspector/canvas/rendererBootstrap.js +45 -4
- package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
- package/dist/inspector/canvas/rendererElementSelector.js +102 -45
- package/dist/inspector/canvas/rendererStylesheet.js +2 -2
- package/dist/inspector/canvas/resizeHandleScale.js +1 -1
- package/dist/inspector/canvas/sessionStore.d.ts +11 -26
- package/dist/inspector/canvas/sessionStore.js +43 -183
- package/dist/inspector/canvas/staleChangeDetector.js +9 -3
- package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
- package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
- package/dist/inspector/canvas/viewStore.d.ts +38 -0
- package/dist/inspector/canvas/viewStore.js +104 -0
- package/dist/inspector/canvas/wheelPan.d.ts +8 -0
- package/dist/inspector/canvas/wheelPan.js +8 -0
- package/dist/inspector/changes/changesLog.d.ts +6 -5
- package/dist/inspector/changes/changesLog.js +40 -32
- package/dist/inspector/changes/draftChanges.d.ts +56 -0
- package/dist/inspector/changes/draftChanges.js +300 -0
- package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
- package/dist/inspector/changes/previewDiagnostics.js +11 -25
- package/dist/inspector/clientManifest.d.ts +3 -1
- package/dist/inspector/clientManifest.js +1 -0
- package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
- package/dist/inspector/comments/CommentsOverlay.js +250 -0
- package/dist/inspector/comments/element.d.ts +6 -0
- package/dist/inspector/comments/element.js +47 -0
- package/dist/inspector/comments/store.d.ts +29 -0
- package/dist/inspector/comments/store.js +121 -0
- package/dist/inspector/comments/verification.d.ts +5 -0
- package/dist/inspector/comments/verification.js +52 -0
- package/dist/inspector/drafts/model.d.ts +35 -0
- package/dist/inspector/drafts/model.js +45 -0
- package/dist/inspector/drafts/store.d.ts +28 -0
- package/dist/inspector/drafts/store.js +231 -0
- package/dist/inspector/index.js +26 -35
- package/dist/inspector/inline-text/inlineTextEditor.js +7 -0
- package/dist/inspector/inspection/useBrowserCssInspection.js +15 -1
- package/dist/inspector/iterations/capture.d.ts +2 -0
- package/dist/inspector/iterations/capture.js +173 -0
- package/dist/inspector/iterations/client.d.ts +7 -0
- package/dist/inspector/iterations/client.js +52 -0
- package/dist/inspector/iterations/coordinator.d.ts +15 -0
- package/dist/inspector/iterations/coordinator.js +130 -0
- package/dist/inspector/iterations/prompt.d.ts +3 -0
- package/dist/inspector/iterations/prompt.js +26 -0
- package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
- package/dist/inspector/overlay/DomNavigator.js +46 -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/interactionStyles.js +3 -1
- package/dist/inspector/overlay/spacingGestures.d.ts +15 -3
- package/dist/inspector/overlay/spacingGestures.js +88 -13
- package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
- package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
- package/dist/inspector/projection/renderedInstance.d.ts +2 -1
- package/dist/inspector/projection/renderedInstance.js +9 -5
- package/dist/inspector/projection/structuralProjection.d.ts +2 -1
- package/dist/inspector/projection/structuralProjection.js +19 -15
- package/dist/inspector/projection/textProjection.d.ts +2 -1
- package/dist/inspector/projection/textProjection.js +8 -4
- package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
- package/dist/inspector/prompt/clipboardHandoff.d.ts +10 -18
- package/dist/inspector/prompt/clipboardHandoff.js +92 -143
- package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
- package/dist/inspector/prompt/generatePrompt.js +17 -16
- package/dist/inspector/runtime/runtimeConfig.d.ts +10 -1
- package/dist/inspector/runtime/runtimeConfig.js +24 -0
- package/dist/inspector/selection/hierarchy.d.ts +6 -0
- package/dist/inspector/selection/hierarchy.js +8 -0
- package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
- package/dist/inspector/settings/SettingsDialog.js +22 -9
- package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
- package/dist/inspector/shell/AgentStatusButton.js +36 -0
- package/dist/inspector/shell/AppShell.js +4 -2
- package/dist/inspector/shell/ChangesLog.js +47 -27
- package/dist/inspector/shell/CopyPromptButton.js +146 -90
- package/dist/inspector/shell/EmptyState.js +9 -8
- package/dist/inspector/shell/InspectorShell.js +14 -25
- package/dist/inspector/shell/PeekOriginalButton.js +24 -23
- package/dist/inspector/shell/originalPreview.js +5 -5
- package/dist/inspector/shell/shortcuts.d.ts +3 -0
- package/dist/inspector/shell/shortcuts.js +11 -0
- package/dist/inspector/sketch/SketchEditor.js +14 -14
- package/dist/inspector/sketch/SketchEntryButton.js +1 -1
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
- package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
- package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
- package/dist/inspector/sketch/SketchOverlay.js +7 -7
- package/dist/inspector/sketch/SketchWorkspace.js +18 -12
- package/dist/inspector/sketch/interaction.d.ts +1 -0
- package/dist/inspector/sketch/interaction.js +8 -4
- package/dist/inspector/sketch/model.d.ts +5 -0
- package/dist/inspector/sketch/model.js +6 -2
- package/dist/inspector/sketch/raster.d.ts +1 -2
- package/dist/inspector/sketch/raster.js +68 -4
- package/dist/inspector/sketch/store.js +48 -23
- package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
- package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
- package/dist/inspector/styleEditors/BorderEditor.js +49 -78
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
- package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
- package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
- package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
- package/dist/inspector/styleEditors/ColorPicker.js +10 -4
- package/dist/inspector/styleEditors/GapField.js +3 -5
- package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
- package/dist/inspector/styleEditors/GridChildSection.js +18 -9
- package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
- package/dist/inspector/styleEditors/GridPicker.js +8 -2
- package/dist/inspector/styleEditors/GridSection.js +1 -1
- package/dist/inspector/styleEditors/GridValueField.js +19 -14
- package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
- package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
- package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
- package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
- package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
- package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
- package/dist/inspector/styleEditors/LayoutSection.js +32 -11
- package/dist/inspector/styleEditors/SpacingBox.js +16 -9
- package/dist/inspector/styleEditors/Typography.js +11 -3
- package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
- package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
- package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
- package/dist/inspector/styleEditors/gradientValue.js +114 -0
- package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
- package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
- package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
- package/dist/inspector/styleEditors/layoutValue.js +0 -42
- package/dist/inspector/styleEditors/nudgeValue.d.ts +4 -4
- package/dist/inspector/styleEditors/nudgeValue.js +41 -11
- package/dist/inspector/tokens/TokenField.d.ts +7 -0
- package/dist/inspector/tokens/TokenField.js +49 -22
- package/dist/inspector/tokens/TokenLabel.js +5 -14
- 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 +1 -1
- package/dist/inspector/tokens/editActions.js +1 -1
- package/dist/inspector/ui/AtRuleContext.js +4 -6
- package/dist/inspector/ui/Button.d.ts +6 -2
- package/dist/inspector/ui/Button.js +4 -3
- package/dist/inspector/ui/ControlSurface.d.ts +2 -2
- package/dist/inspector/ui/ControlSurface.js +4 -3
- package/dist/inspector/ui/Disclosure.d.ts +22 -0
- package/dist/inspector/ui/Disclosure.js +13 -0
- package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
- package/dist/inspector/ui/InlineStyleWarning.js +13 -0
- package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
- package/dist/inspector/ui/InspectorPopover.js +4 -5
- package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
- package/dist/inspector/ui/PopoverListbox.js +8 -7
- package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
- package/dist/inspector/ui/SegmentedControl.js +2 -2
- package/dist/inspector/ui/Select.d.ts +6 -1
- package/dist/inspector/ui/Select.js +7 -11
- package/dist/inspector/ui/SideValuesField.js +1 -1
- package/dist/inspector/ui/Tooltip.d.ts +26 -0
- package/dist/inspector/ui/Tooltip.js +32 -0
- package/dist/inspector/ui/classNames.d.ts +2 -0
- package/dist/inspector/ui/classNames.js +4 -0
- package/dist/inspector/ui/customCursors.d.ts +23 -0
- package/dist/inspector/ui/customCursors.js +122 -0
- package/dist/inspector/ui/dragCursor.d.ts +6 -0
- package/dist/inspector/ui/dragCursor.js +13 -0
- package/dist/inspector/ui/portalContainer.d.ts +2 -0
- package/dist/inspector/ui/portalContainer.js +6 -0
- package/dist/inspector/ui/styles.js +34 -24
- package/dist/inspector/workspace/commands.d.ts +9 -0
- package/dist/inspector/workspace/commands.js +82 -0
- package/dist/inspector/workspace/controller.d.ts +4 -0
- package/dist/inspector/workspace/controller.js +66 -0
- package/dist/inspector/workspace/handoff.d.ts +42 -0
- package/dist/inspector/workspace/handoff.js +115 -0
- package/dist/inspector/workspace/timeline.d.ts +30 -0
- package/dist/inspector/workspace/timeline.js +108 -0
- package/dist/internal/agent-protocol.d.ts +19 -2
- package/dist/internal/agent-protocol.js +13 -0
- package/dist/nudge-ui.mjs +560 -299
- package/dist/project/artifacts.d.ts +13 -0
- package/dist/project/artifacts.js +253 -0
- package/dist/transport/artifacts.d.ts +8 -0
- package/dist/transport/artifacts.js +9 -0
- package/dist/transport/editor.d.ts +20 -1
- package/dist/transport/editor.js +44 -11
- package/dist/transport/routes.d.ts +6 -1
- package/dist/transport/routes.js +6 -1
- package/package.json +6 -2
- package/dist/inspector/changes/workspaceChanges.d.ts +0 -43
- package/dist/inspector/changes/workspaceChanges.js +0 -239
- package/dist/inspector/shell/DomNavigation.d.ts +0 -8
- package/dist/inspector/shell/DomNavigation.js +0 -30
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
export interface HtmlArtifactMetadata {
|
|
3
|
+
id: string;
|
|
4
|
+
sourceUrl: string;
|
|
5
|
+
title: string;
|
|
6
|
+
capturedAt: number;
|
|
7
|
+
viewport: {
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/** Handles only the reserved artifact namespace. A false return lets the host continue. */
|
|
13
|
+
export declare function handleHtmlArtifactRequest(request: IncomingMessage, response: ServerResponse, root: string, transformHtml?: (html: string) => Promise<string>): Promise<boolean>;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { HTML_ARTIFACT_REVISION_ATTRIBUTE, HTML_ITERATION_CONFLICT, isHtmlArtifactRevision } from "../transport/artifacts.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { mkdir, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { isAbsolute, join, relative, sep } from "node:path";
|
|
6
|
+
import { NUDGE_UI_ARTIFACTS_PATH } from "../transport/routes.js";
|
|
7
|
+
import { injectStandaloneBootstrap } from "../hosts/static/html/bootstrap.js";
|
|
8
|
+
const artifactOperations = new Map();
|
|
9
|
+
async function withArtifactLock(path, operation) {
|
|
10
|
+
const previous = artifactOperations.get(path) ?? Promise.resolve();
|
|
11
|
+
const pending = previous.catch(() => undefined).then(operation);
|
|
12
|
+
artifactOperations.set(path, pending);
|
|
13
|
+
try {
|
|
14
|
+
return await pending;
|
|
15
|
+
}
|
|
16
|
+
finally {
|
|
17
|
+
if (artifactOperations.get(path) === pending)
|
|
18
|
+
artifactOperations.delete(path);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function revision(html) {
|
|
22
|
+
return createHash("sha256").update(html).digest("hex");
|
|
23
|
+
}
|
|
24
|
+
async function readRevision(target) {
|
|
25
|
+
const [document, preview] = await Promise.all([
|
|
26
|
+
readFile(join(target, "document.html")), readFile(join(target, "preview.html")),
|
|
27
|
+
]);
|
|
28
|
+
return { document: revision(document), preview: revision(preview) };
|
|
29
|
+
}
|
|
30
|
+
function sameRevision(left, right) {
|
|
31
|
+
return left.document === right.document && left.preview === right.preview;
|
|
32
|
+
}
|
|
33
|
+
const ID = /^[a-f0-9-]{36}$/i;
|
|
34
|
+
const MAX_BYTES = 12 * 1024 * 1024;
|
|
35
|
+
const INERT_INTERACTIONS = `<script data-nudge-artifact-guard>
|
|
36
|
+
document.addEventListener("submit", event => event.preventDefault(), true);
|
|
37
|
+
document.addEventListener("click", event => {
|
|
38
|
+
if (event.target instanceof Element && event.target.closest("a")) event.preventDefault();
|
|
39
|
+
}, true);
|
|
40
|
+
</script>`;
|
|
41
|
+
async function directory(root, id) {
|
|
42
|
+
const project = await realpath(root);
|
|
43
|
+
const artifacts = join(project, ".nudge", "artifacts");
|
|
44
|
+
await mkdir(artifacts, { recursive: true });
|
|
45
|
+
const actual = await realpath(artifacts);
|
|
46
|
+
const pathFromRoot = relative(project, actual);
|
|
47
|
+
if (pathFromRoot === ".." || pathFromRoot.startsWith(`..${sep}`) || isAbsolute(pathFromRoot)) {
|
|
48
|
+
throw new Error("Artifact storage must remain inside the project root");
|
|
49
|
+
}
|
|
50
|
+
return join(actual, id);
|
|
51
|
+
}
|
|
52
|
+
async function body(request) {
|
|
53
|
+
const chunks = [];
|
|
54
|
+
let size = 0;
|
|
55
|
+
for await (const chunk of request) {
|
|
56
|
+
const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
57
|
+
size += data.byteLength;
|
|
58
|
+
if (size > MAX_BYTES)
|
|
59
|
+
throw new Error("HTML artifact exceeds 12 MB");
|
|
60
|
+
chunks.push(data);
|
|
61
|
+
}
|
|
62
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
63
|
+
return text.length === 0 ? {} : JSON.parse(text);
|
|
64
|
+
}
|
|
65
|
+
function respond(response, status, content, type = "text/plain; charset=utf-8") {
|
|
66
|
+
response.writeHead(status, {
|
|
67
|
+
"content-type": type,
|
|
68
|
+
"cache-control": "no-store",
|
|
69
|
+
"x-content-type-options": "nosniff",
|
|
70
|
+
});
|
|
71
|
+
response.end(content);
|
|
72
|
+
}
|
|
73
|
+
/** Handles only the reserved artifact namespace. A false return lets the host continue. */
|
|
74
|
+
export async function handleHtmlArtifactRequest(request, response, root, transformHtml) {
|
|
75
|
+
const pathname = new URL(request.url ?? "/", "http://nudge-ui.local").pathname;
|
|
76
|
+
if (pathname !== NUDGE_UI_ARTIFACTS_PATH && !pathname.startsWith(`${NUDGE_UI_ARTIFACTS_PATH}/`))
|
|
77
|
+
return false;
|
|
78
|
+
const parts = pathname.slice(NUDGE_UI_ARTIFACTS_PATH.length).split("/").filter(Boolean);
|
|
79
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
80
|
+
const origin = request.headers.origin;
|
|
81
|
+
const host = request.headers.host;
|
|
82
|
+
if (origin && host) {
|
|
83
|
+
try {
|
|
84
|
+
if (new URL(origin).host !== host) {
|
|
85
|
+
respond(response, 403, "Cross-origin artifact writes are not allowed");
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
respond(response, 403, "Invalid request origin");
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const id = parts[0];
|
|
96
|
+
if (parts.length > 2 || (id && !ID.test(id)) || (parts.length === 2 && !["document", "baseline", "preview", "commit", "revision"].includes(parts[1]))) {
|
|
97
|
+
respond(response, 404, "Not Found");
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
if (request.method === "POST" && parts.length === 0) {
|
|
102
|
+
const input = await body(request);
|
|
103
|
+
if (!validArtifact(input)) {
|
|
104
|
+
respond(response, 400, "Invalid artifact");
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
const target = await directory(root, input.id);
|
|
108
|
+
const temporary = `${target}.create-${crypto.randomUUID()}`;
|
|
109
|
+
const metadata = {
|
|
110
|
+
id: input.id,
|
|
111
|
+
sourceUrl: input.sourceUrl,
|
|
112
|
+
title: input.title,
|
|
113
|
+
capturedAt: Date.now(),
|
|
114
|
+
viewport: input.viewport,
|
|
115
|
+
};
|
|
116
|
+
await mkdir(temporary);
|
|
117
|
+
try {
|
|
118
|
+
await writeFile(join(temporary, "baseline.html"), input.html);
|
|
119
|
+
await writeFile(join(temporary, "preview.html"), input.html);
|
|
120
|
+
await writeFile(join(temporary, "document.html"), input.html);
|
|
121
|
+
await writeFile(join(temporary, "metadata.json"), JSON.stringify(metadata));
|
|
122
|
+
await rename(temporary, target);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
await rm(temporary, { recursive: true, force: true });
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
respond(response, 201, JSON.stringify(metadata), "application/json; charset=utf-8");
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
if (!id) {
|
|
132
|
+
respond(response, 405, "Method Not Allowed");
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
const target = await directory(root, id);
|
|
136
|
+
return await withArtifactLock(target, async () => {
|
|
137
|
+
if (request.method !== "DELETE") {
|
|
138
|
+
const actual = await realpath(target);
|
|
139
|
+
if (actual !== target)
|
|
140
|
+
throw new Error("Artifact directory must not be a symbolic link");
|
|
141
|
+
}
|
|
142
|
+
if (request.method === "GET" && parts[1] === "revision") {
|
|
143
|
+
respond(response, 200, JSON.stringify(await readRevision(target)), "application/json; charset=utf-8");
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
if (request.method === "GET" && parts.length === 2 && parts[1] !== "commit") {
|
|
147
|
+
const file = parts[1] === "baseline" ? "baseline.html" : parts[1] === "preview" ? "preview.html" : "document.html";
|
|
148
|
+
const html = await readFile(join(target, file), "utf8");
|
|
149
|
+
const stamped = html.replace(/<html\b/i, `<html ${HTML_ARTIFACT_REVISION_ATTRIBUTE}="${revision(html)}"`);
|
|
150
|
+
const bootstrapped = injectStandaloneBootstrap(stamped).html;
|
|
151
|
+
const guarded = bootstrapped.replace(/<\/body>/i, `${INERT_INTERACTIONS}</body>`);
|
|
152
|
+
respond(response, 200, transformHtml ? await transformHtml(guarded) : guarded, "text/html; charset=utf-8");
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
if (request.method === "GET" && parts.length === 1) {
|
|
156
|
+
respond(response, 200, await readFile(join(target, "metadata.json"), "utf8"), "application/json; charset=utf-8");
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
if ((request.method === "PUT" && parts[1] === "document")
|
|
160
|
+
|| (request.method === "POST" && parts[1] === "commit")) {
|
|
161
|
+
const input = await body(request);
|
|
162
|
+
if (!input || typeof input !== "object") {
|
|
163
|
+
respond(response, 400, "Invalid document");
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
const item = input;
|
|
167
|
+
if (!isHtmlArtifactRevision(item.expected)) {
|
|
168
|
+
respond(response, 428, "An expected iteration revision is required");
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
if ((item.html !== undefined && typeof item.html !== "string")
|
|
172
|
+
|| (request.method === "PUT" && typeof item.html !== "string")) {
|
|
173
|
+
respond(response, 400, "Invalid document");
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
const expected = item.expected;
|
|
177
|
+
if (!sameRevision(await readRevision(target), expected)) {
|
|
178
|
+
respond(response, 409, HTML_ITERATION_CONFLICT);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
const html = typeof item.html === "string" ? item.html : await readFile(join(target, "document.html"), "utf8");
|
|
182
|
+
const writes = request.method === "POST" ? ["preview.html"] : [];
|
|
183
|
+
if (typeof item.html === "string")
|
|
184
|
+
writes.push("document.html");
|
|
185
|
+
const previewBefore = await readFile(join(target, "preview.html"));
|
|
186
|
+
const temporaryFiles = [];
|
|
187
|
+
try {
|
|
188
|
+
for (const file of writes) {
|
|
189
|
+
const temporary = join(target, `${file}-${crypto.randomUUID()}.tmp`);
|
|
190
|
+
temporaryFiles.push({ temporary, destination: join(target, file) });
|
|
191
|
+
await writeFile(temporary, html);
|
|
192
|
+
}
|
|
193
|
+
// Keep the final check and replacements in one controller critical section.
|
|
194
|
+
// External tools must finish their file writes before handoff; they do
|
|
195
|
+
// not participate in this process's request queue.
|
|
196
|
+
const latest = {
|
|
197
|
+
document: revision(readFileSync(join(target, "document.html"))),
|
|
198
|
+
preview: revision(readFileSync(join(target, "preview.html"))),
|
|
199
|
+
};
|
|
200
|
+
if (!sameRevision(latest, expected)) {
|
|
201
|
+
respond(response, 409, HTML_ITERATION_CONFLICT);
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
let replacedPreview = false;
|
|
205
|
+
try {
|
|
206
|
+
for (const file of temporaryFiles) {
|
|
207
|
+
renameSync(file.temporary, file.destination);
|
|
208
|
+
replacedPreview ||= file.destination === join(target, "preview.html");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
// A failed document replacement must not advance the editing base.
|
|
213
|
+
if (replacedPreview) {
|
|
214
|
+
const rollback = join(target, `preview-rollback-${crypto.randomUUID()}.tmp`);
|
|
215
|
+
writeFileSync(rollback, previewBefore);
|
|
216
|
+
renameSync(rollback, join(target, "preview.html"));
|
|
217
|
+
}
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
await Promise.all(temporaryFiles.map((file) => rm(file.temporary, { force: true }).catch(() => undefined)));
|
|
223
|
+
}
|
|
224
|
+
const saved = revision(html);
|
|
225
|
+
respond(response, 200, JSON.stringify({ document: saved, preview: request.method === "POST" ? saved : expected.preview }), "application/json; charset=utf-8");
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
if (request.method === "DELETE" && parts.length === 1) {
|
|
229
|
+
await rm(target, { recursive: true, force: true });
|
|
230
|
+
respond(response, 204, "");
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
respond(response, 405, "Method Not Allowed");
|
|
234
|
+
return true;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
const code = error.code;
|
|
239
|
+
respond(response, code === "ENOENT" ? 404 : code === "EEXIST" || code === "ENOTEMPTY" ? 409 : 500, code === "ENOENT" ? "Artifact not found" : error instanceof Error ? error.message : "Artifact storage failed");
|
|
240
|
+
}
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
function validArtifact(value) {
|
|
244
|
+
if (!value || typeof value !== "object")
|
|
245
|
+
return false;
|
|
246
|
+
const item = value;
|
|
247
|
+
const viewport = item.viewport;
|
|
248
|
+
return typeof item.id === "string" && ID.test(item.id)
|
|
249
|
+
&& typeof item.sourceUrl === "string" && item.sourceUrl.length < 4096
|
|
250
|
+
&& typeof item.title === "string" && item.title.length < 512
|
|
251
|
+
&& typeof item.html === "string" && item.html.length > 0
|
|
252
|
+
&& !!viewport && typeof viewport.width === "number" && typeof viewport.height === "number";
|
|
253
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Revisions cover both the agent-editable file and the browser editing base. */
|
|
2
|
+
export interface HtmlArtifactRevision {
|
|
3
|
+
readonly document: string;
|
|
4
|
+
readonly preview: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const HTML_ARTIFACT_REVISION_ATTRIBUTE = "data-nudge-artifact-revision";
|
|
7
|
+
export declare const HTML_ITERATION_CONFLICT = "This iteration changed outside Nudge. Your visual edits are still pending. Create another iteration to keep your design, then clear this iteration\u2019s changes to load the external update.";
|
|
8
|
+
export declare function isHtmlArtifactRevision(value: unknown): value is HtmlArtifactRevision;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const HTML_ARTIFACT_REVISION_ATTRIBUTE = "data-nudge-artifact-revision";
|
|
2
|
+
export const HTML_ITERATION_CONFLICT = "This iteration changed outside Nudge. Your visual edits are still pending. Create another iteration to keep your design, then clear this iteration’s changes to load the external update.";
|
|
3
|
+
export function isHtmlArtifactRevision(value) {
|
|
4
|
+
if (!value || typeof value !== "object")
|
|
5
|
+
return false;
|
|
6
|
+
const item = value;
|
|
7
|
+
return typeof item.document === "string" && /^[a-f0-9]{64}$/.test(item.document)
|
|
8
|
+
&& typeof item.preview === "string" && /^[a-f0-9]{64}$/.test(item.preview);
|
|
9
|
+
}
|
|
@@ -7,12 +7,18 @@ type RequestHeaderValue = string | readonly string[] | undefined;
|
|
|
7
7
|
export declare function isNudgeUiEditorDocumentRequest(requestHref: string, method: string | undefined, headers: Record<string, RequestHeaderValue> | undefined): boolean;
|
|
8
8
|
/** Adds the persistent marker that bypasses editor entry for a plain app view. */
|
|
9
9
|
export declare function createNudgeUiDirectUrl(applicationHref: string): string;
|
|
10
|
+
/** Reads the public `nudge-ui=off` / `nudge-ui=on` switch from an application URL. */
|
|
11
|
+
export declare function readNudgeUiQuerySwitch(href: string): "off" | "on" | null;
|
|
10
12
|
/** Returns whether this application location explicitly bypasses editor entry. */
|
|
11
13
|
export declare function isNudgeUiDirectUrl(href: string): boolean;
|
|
12
14
|
/** Remembers that this browser tab was explicitly opened outside the editor. */
|
|
13
15
|
export declare function rememberNudgeUiDirectTabIntent(): void;
|
|
16
|
+
/** Remembers an explicit URL switch so later navigations in this tab keep it. */
|
|
17
|
+
export declare function rememberNudgeUiTabSwitch(value: "off" | "on"): void;
|
|
14
18
|
/** Returns whether this browser tab should remain outside the editor. */
|
|
15
19
|
export declare function hasNudgeUiDirectTabIntent(): boolean;
|
|
20
|
+
/** Returns whether this browser tab explicitly asked for the editor with `nudge-ui=on`. */
|
|
21
|
+
export declare function hasNudgeUiForcedTabIntent(): boolean;
|
|
16
22
|
export type NudgeUiClientEntry = {
|
|
17
23
|
readonly kind: "bootstrap";
|
|
18
24
|
} | {
|
|
@@ -20,9 +26,22 @@ export type NudgeUiClientEntry = {
|
|
|
20
26
|
} | {
|
|
21
27
|
readonly kind: "redirect";
|
|
22
28
|
readonly href: string;
|
|
29
|
+
}
|
|
30
|
+
/** An automated browser that enters the editor only when the host opts in. */
|
|
31
|
+
| {
|
|
32
|
+
readonly kind: "automated";
|
|
33
|
+
readonly href: string;
|
|
23
34
|
};
|
|
35
|
+
export interface NudgeUiClientEntryContext {
|
|
36
|
+
readonly editorDocument: boolean;
|
|
37
|
+
readonly canvasRenderer: boolean;
|
|
38
|
+
readonly directTab?: boolean;
|
|
39
|
+
readonly forcedTab?: boolean;
|
|
40
|
+
/** `navigator.webdriver`: Playwright, Puppeteer, Selenium, and headless Chrome. */
|
|
41
|
+
readonly automated?: boolean;
|
|
42
|
+
}
|
|
24
43
|
/** Chooses the browser-client role before any runtime manifest is fetched. */
|
|
25
|
-
export declare function resolveNudgeUiClientEntry(href: string,
|
|
44
|
+
export declare function resolveNudgeUiClientEntry(href: string, context: NudgeUiClientEntryContext): NudgeUiClientEntry;
|
|
26
45
|
/** Renders the host-neutral editor document served by every development Adapter. */
|
|
27
46
|
export declare function createNudgeUiEditorDocument(): string;
|
|
28
47
|
export {};
|
package/dist/transport/editor.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import { NUDGE_UI_CLIENT_PATH, NUDGE_UI_DIRECT_QUERY_PARAM, NUDGE_UI_EDITOR_QUERY_PARAM, NUDGE_UI_EDITOR_QUERY_VALUE, NUDGE_UI_EDITOR_PATH, NUDGE_UI_MANIFEST_PATH, NUDGE_UI_MOUNT_ID, isReservedNudgeUiRoute, } from "./routes.js";
|
|
1
|
+
import { NUDGE_UI_CLIENT_PATH, NUDGE_UI_ARTIFACTS_PATH, NUDGE_UI_DIRECT_QUERY_PARAM, NUDGE_UI_EDITOR_QUERY_PARAM, NUDGE_UI_EDITOR_QUERY_VALUE, NUDGE_UI_EDITOR_PATH, NUDGE_UI_MANIFEST_PATH, NUDGE_UI_MOUNT_ID, NUDGE_UI_OFF_QUERY_VALUE, NUDGE_UI_ON_QUERY_VALUE, isReservedNudgeUiRoute, } from "./routes.js";
|
|
2
2
|
const TARGET_QUERY_PARAM = "url";
|
|
3
3
|
const DIRECT_TAB_SESSION_KEY = "nudge-ui:direct-tab";
|
|
4
|
+
const FORCED_TAB_SESSION_KEY = "nudge-ui:forced-tab";
|
|
4
5
|
/** Returns the editor URL for one same-origin application location. */
|
|
5
6
|
export function createNudgeUiEditorUrl(applicationHref) {
|
|
6
7
|
const applicationUrl = new URL(applicationHref);
|
|
7
8
|
assertApplicationUrl(applicationUrl);
|
|
8
9
|
const retainedSegments = applicationUrl.search.slice(1).split("&").filter((segment) => {
|
|
9
10
|
const entry = new URLSearchParams(segment);
|
|
10
|
-
|
|
11
|
+
const value = entry.get(NUDGE_UI_EDITOR_QUERY_PARAM);
|
|
12
|
+
return entry.get(NUDGE_UI_DIRECT_QUERY_PARAM) !== "1"
|
|
13
|
+
&& value !== NUDGE_UI_OFF_QUERY_VALUE
|
|
14
|
+
&& value !== NUDGE_UI_ON_QUERY_VALUE;
|
|
11
15
|
});
|
|
12
16
|
applicationUrl.search = retainedSegments.join("&");
|
|
13
17
|
if (applicationUrl.searchParams.getAll(NUDGE_UI_EDITOR_QUERY_PARAM).includes(NUDGE_UI_EDITOR_QUERY_VALUE)) {
|
|
@@ -72,36 +76,65 @@ export function createNudgeUiDirectUrl(applicationHref) {
|
|
|
72
76
|
url.searchParams.set(NUDGE_UI_DIRECT_QUERY_PARAM, "1");
|
|
73
77
|
return url.href;
|
|
74
78
|
}
|
|
79
|
+
/** Reads the public `nudge-ui=off` / `nudge-ui=on` switch from an application URL. */
|
|
80
|
+
export function readNudgeUiQuerySwitch(href) {
|
|
81
|
+
const url = new URL(href);
|
|
82
|
+
const values = url.searchParams.getAll(NUDGE_UI_EDITOR_QUERY_PARAM);
|
|
83
|
+
if (values.includes(NUDGE_UI_OFF_QUERY_VALUE) || url.searchParams.get(NUDGE_UI_DIRECT_QUERY_PARAM) === "1") {
|
|
84
|
+
return "off";
|
|
85
|
+
}
|
|
86
|
+
return values.includes(NUDGE_UI_ON_QUERY_VALUE) ? "on" : null;
|
|
87
|
+
}
|
|
75
88
|
/** Returns whether this application location explicitly bypasses editor entry. */
|
|
76
89
|
export function isNudgeUiDirectUrl(href) {
|
|
77
|
-
|
|
78
|
-
return url.searchParams.get(NUDGE_UI_DIRECT_QUERY_PARAM) === "1";
|
|
90
|
+
return readNudgeUiQuerySwitch(href) === "off";
|
|
79
91
|
}
|
|
80
92
|
/** Remembers that this browser tab was explicitly opened outside the editor. */
|
|
81
93
|
export function rememberNudgeUiDirectTabIntent() {
|
|
94
|
+
rememberNudgeUiTabSwitch("off");
|
|
95
|
+
}
|
|
96
|
+
/** Remembers an explicit URL switch so later navigations in this tab keep it. */
|
|
97
|
+
export function rememberNudgeUiTabSwitch(value) {
|
|
82
98
|
try {
|
|
83
|
-
sessionStorage.setItem(DIRECT_TAB_SESSION_KEY, "1");
|
|
99
|
+
sessionStorage.setItem(value === "off" ? DIRECT_TAB_SESSION_KEY : FORCED_TAB_SESSION_KEY, "1");
|
|
100
|
+
sessionStorage.removeItem(value === "off" ? FORCED_TAB_SESSION_KEY : DIRECT_TAB_SESSION_KEY);
|
|
84
101
|
}
|
|
85
102
|
catch {
|
|
86
|
-
// Storage denial must not prevent
|
|
103
|
+
// Storage denial must not prevent the current page from honoring the switch.
|
|
87
104
|
}
|
|
88
105
|
}
|
|
89
106
|
/** Returns whether this browser tab should remain outside the editor. */
|
|
90
107
|
export function hasNudgeUiDirectTabIntent() {
|
|
108
|
+
return readTabFlag(DIRECT_TAB_SESSION_KEY);
|
|
109
|
+
}
|
|
110
|
+
/** Returns whether this browser tab explicitly asked for the editor with `nudge-ui=on`. */
|
|
111
|
+
export function hasNudgeUiForcedTabIntent() {
|
|
112
|
+
return readTabFlag(FORCED_TAB_SESSION_KEY);
|
|
113
|
+
}
|
|
114
|
+
function readTabFlag(key) {
|
|
91
115
|
try {
|
|
92
|
-
return sessionStorage.getItem(
|
|
116
|
+
return sessionStorage.getItem(key) === "1";
|
|
93
117
|
}
|
|
94
118
|
catch {
|
|
95
119
|
return false;
|
|
96
120
|
}
|
|
97
121
|
}
|
|
98
122
|
/** Chooses the browser-client role before any runtime manifest is fetched. */
|
|
99
|
-
export function resolveNudgeUiClientEntry(href,
|
|
100
|
-
if (editorDocument || canvasRenderer)
|
|
123
|
+
export function resolveNudgeUiClientEntry(href, context) {
|
|
124
|
+
if (context.editorDocument || context.canvasRenderer)
|
|
101
125
|
return { kind: "bootstrap" };
|
|
102
|
-
if (
|
|
126
|
+
if (new URL(href).pathname.startsWith(`${NUDGE_UI_ARTIFACTS_PATH}/`))
|
|
127
|
+
return { kind: "direct" };
|
|
128
|
+
const urlSwitch = readNudgeUiQuerySwitch(href);
|
|
129
|
+
if (urlSwitch === "off")
|
|
130
|
+
return { kind: "direct" };
|
|
131
|
+
const forced = urlSwitch === "on" || context.forcedTab === true;
|
|
132
|
+
if (!forced && context.directTab === true)
|
|
103
133
|
return { kind: "direct" };
|
|
104
|
-
|
|
134
|
+
const editorHref = createNudgeUiEditorUrl(href);
|
|
135
|
+
if (context.automated === true && !forced)
|
|
136
|
+
return { kind: "automated", href: editorHref };
|
|
137
|
+
return { kind: "redirect", href: editorHref };
|
|
105
138
|
}
|
|
106
139
|
function isApplicationUrl(url) {
|
|
107
140
|
return (url.protocol === "http:" || url.protocol === "https:")
|
|
@@ -11,10 +11,15 @@ export declare const NUDGE_UI_CLIENT_PATH = "/__nudge_ui__/client.mjs";
|
|
|
11
11
|
export declare const NUDGE_UI_EDITOR_PATH = "/__nudge_ui__/editor";
|
|
12
12
|
/** Server-sent events announcing settled project changes. */
|
|
13
13
|
export declare const NUDGE_UI_RELOAD_PATH = "/__nudge_ui__/reload";
|
|
14
|
+
/** Project-local HTML iterations created by unlinking a Canvas frame. */
|
|
15
|
+
export declare const NUDGE_UI_ARTIFACTS_PATH = "/__nudge_ui__/artifacts";
|
|
14
16
|
export declare const NUDGE_UI_MOUNT_ID = "nudge-ui-root";
|
|
15
17
|
/** Query marker that presents an application route through the editor shell. */
|
|
16
18
|
export declare const NUDGE_UI_EDITOR_QUERY_PARAM = "nudge-ui";
|
|
17
19
|
export declare const NUDGE_UI_EDITOR_QUERY_VALUE = "editor";
|
|
18
|
-
/**
|
|
20
|
+
/** Public `nudge-ui` values that turn the inspector off or on for one browser tab. */
|
|
21
|
+
export declare const NUDGE_UI_OFF_QUERY_VALUE = "off";
|
|
22
|
+
export declare const NUDGE_UI_ON_QUERY_VALUE = "on";
|
|
23
|
+
/** Internal alias of `nudge-ui=off` kept for existing direct-view links. */
|
|
19
24
|
export declare const NUDGE_UI_DIRECT_QUERY_PARAM = "__nudge_ui_direct";
|
|
20
25
|
export declare function isReservedNudgeUiRoute(pathname: string): boolean;
|
package/dist/transport/routes.js
CHANGED
|
@@ -11,11 +11,16 @@ export const NUDGE_UI_CLIENT_PATH = `${NUDGE_UI_ROUTE_PREFIX}client.mjs`;
|
|
|
11
11
|
export const NUDGE_UI_EDITOR_PATH = `${NUDGE_UI_ROUTE_PREFIX}editor`;
|
|
12
12
|
/** Server-sent events announcing settled project changes. */
|
|
13
13
|
export const NUDGE_UI_RELOAD_PATH = `${NUDGE_UI_ROUTE_PREFIX}reload`;
|
|
14
|
+
/** Project-local HTML iterations created by unlinking a Canvas frame. */
|
|
15
|
+
export const NUDGE_UI_ARTIFACTS_PATH = `${NUDGE_UI_ROUTE_PREFIX}artifacts`;
|
|
14
16
|
export const NUDGE_UI_MOUNT_ID = "nudge-ui-root";
|
|
15
17
|
/** Query marker that presents an application route through the editor shell. */
|
|
16
18
|
export const NUDGE_UI_EDITOR_QUERY_PARAM = "nudge-ui";
|
|
17
19
|
export const NUDGE_UI_EDITOR_QUERY_VALUE = "editor";
|
|
18
|
-
/**
|
|
20
|
+
/** Public `nudge-ui` values that turn the inspector off or on for one browser tab. */
|
|
21
|
+
export const NUDGE_UI_OFF_QUERY_VALUE = "off";
|
|
22
|
+
export const NUDGE_UI_ON_QUERY_VALUE = "on";
|
|
23
|
+
/** Internal alias of `nudge-ui=off` kept for existing direct-view links. */
|
|
19
24
|
export const NUDGE_UI_DIRECT_QUERY_PARAM = "__nudge_ui_direct";
|
|
20
25
|
export function isReservedNudgeUiRoute(pathname) {
|
|
21
26
|
return pathname === NUDGE_UI_ROUTE_PREFIX.slice(0, -1)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nudge-ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Development-only visual UI inspector. One package, one subpath per host.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -76,13 +76,14 @@
|
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"@babel/parser": "^7.29.8",
|
|
79
|
-
"@base-ui/react": "^1.
|
|
79
|
+
"@base-ui/react": "^1.8.0",
|
|
80
80
|
"@tabler/icons-react": "^3.46.0",
|
|
81
81
|
"magic-string": "^0.30.21",
|
|
82
82
|
"modern-screenshot": "^4.7.0",
|
|
83
83
|
"parse5": "^7.3.0",
|
|
84
84
|
"perfect-freehand": "^1.2.3",
|
|
85
85
|
"postcss": "^8.5.26",
|
|
86
|
+
"thinking-orbs": "^0.3.2",
|
|
86
87
|
"typescript": "^5.9.3"
|
|
87
88
|
},
|
|
88
89
|
"peerDependencies": {
|
|
@@ -110,6 +111,8 @@
|
|
|
110
111
|
}
|
|
111
112
|
},
|
|
112
113
|
"devDependencies": {
|
|
114
|
+
"@mdx-js/mdx": "^3.1.1",
|
|
115
|
+
"@types/hast": "^3.0.5",
|
|
113
116
|
"@types/node": "^22.20.1",
|
|
114
117
|
"@types/react": "19.2.18",
|
|
115
118
|
"@types/react-dom": "19.2.5",
|
|
@@ -118,6 +121,7 @@
|
|
|
118
121
|
"jsdom": "^25.0.1",
|
|
119
122
|
"react": "^19.2.8",
|
|
120
123
|
"react-dom": "^19.2.8",
|
|
124
|
+
"satteri": "^0.10.5",
|
|
121
125
|
"vite": "^8.2.2",
|
|
122
126
|
"vitest": "^2.1.9",
|
|
123
127
|
"@nudge-ui/agent-protocol": "0.2.0"
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { ChangeRecord } from "./types.js";
|
|
2
|
-
import type { StructuralChange } from "./structuralTypes.js";
|
|
3
|
-
export interface WorkspaceContents {
|
|
4
|
-
readonly changes: readonly ChangeRecord[];
|
|
5
|
-
readonly structuralChanges: readonly StructuralChange[];
|
|
6
|
-
}
|
|
7
|
-
export interface WorkspaceChangesSnapshot extends WorkspaceContents {
|
|
8
|
-
readonly revision: number;
|
|
9
|
-
readonly canUndo: boolean;
|
|
10
|
-
readonly canRedo: boolean;
|
|
11
|
-
}
|
|
12
|
-
export type WorkspaceProjector = (snapshot: WorkspaceChangesSnapshot) => void;
|
|
13
|
-
export type CommitResult = "applied" | "unchanged" | "blocked";
|
|
14
|
-
export interface WorkspaceChangeStore {
|
|
15
|
-
getSnapshot(): WorkspaceChangesSnapshot;
|
|
16
|
-
subscribe(listener: () => void): () => void;
|
|
17
|
-
commitChangeRecords(incoming: readonly ChangeRecord[]): CommitResult;
|
|
18
|
-
revertChangeRecord(change: ChangeRecord): boolean;
|
|
19
|
-
discardChangeRecords(target: {
|
|
20
|
-
kind: "selector";
|
|
21
|
-
selector: string;
|
|
22
|
-
} | {
|
|
23
|
-
kind: "instance-override";
|
|
24
|
-
id: string;
|
|
25
|
-
}): boolean;
|
|
26
|
-
commitStructuralChange(change: StructuralChange): boolean;
|
|
27
|
-
revertStructuralChangeRecord(changeId: string): boolean;
|
|
28
|
-
reconcileWorkspaceChanges(verifiedKeys: ReadonlySet<string>, verifiedStructuralIds: ReadonlySet<string>): number;
|
|
29
|
-
undoWorkspaceChange(): boolean;
|
|
30
|
-
redoWorkspaceChange(): boolean;
|
|
31
|
-
restoreWorkspaceChanges(next: WorkspaceContents): void;
|
|
32
|
-
clearWorkspaceChanges(): void;
|
|
33
|
-
}
|
|
34
|
-
export declare const workspaceChangeStore: WorkspaceChangeStore;
|
|
35
|
-
/** Compatibility accessors retained for callers that have not adopted the store seam. */
|
|
36
|
-
export declare function getWorkspaceChanges(): WorkspaceChangesSnapshot;
|
|
37
|
-
export declare function subscribeWorkspaceChanges(listener: () => void): () => void;
|
|
38
|
-
export declare function commitChangeRecords(incoming: readonly ChangeRecord[], project: WorkspaceProjector): CommitResult;
|
|
39
|
-
export declare function commitStructuralChange(change: StructuralChange, project: WorkspaceProjector): boolean;
|
|
40
|
-
export declare function undoWorkspaceChange(project: WorkspaceProjector): boolean;
|
|
41
|
-
export declare function redoWorkspaceChange(project: WorkspaceProjector): boolean;
|
|
42
|
-
export declare function restoreWorkspaceChanges(next: WorkspaceContents, project: WorkspaceProjector): void;
|
|
43
|
-
export declare function resetWorkspaceChanges(): void;
|