nudge-ui 0.2.2 → 0.2.4
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 +260 -61
- 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 +38 -32
- package/dist/hosts/astro/integration.js +5 -1
- package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
- package/dist/hosts/astro/markdownIdentity.js +81 -0
- package/dist/hosts/next/manifest.js +1 -1
- package/dist/hosts/next/sidecar.js +5 -1
- package/dist/hosts/static/manifest.js +1 -1
- package/dist/hosts/static/server.js +3 -0
- package/dist/hosts/vite/vite.d.ts +3 -0
- package/dist/hosts/vite/vite.js +24 -4
- package/dist/inspector/agent/McpConnectionDialog.js +13 -19
- package/dist/inspector/agent/activity.d.ts +18 -0
- package/dist/inspector/agent/activity.js +70 -0
- package/dist/inspector/agent/client.d.ts +2 -0
- package/dist/inspector/agent/client.js +29 -1
- package/dist/inspector/agent/connectionStatus.js +1 -1
- package/dist/inspector/agent/httpTransport.js +9 -2
- package/dist/inspector/agent/protocol.d.ts +6 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +15 -7
- package/dist/inspector/agent/verification.js +113 -41
- 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 +135 -38
- package/dist/inspector/canvas/CanvasElementOverlay.js +166 -16
- 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 +270 -122
- 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 +21 -4
- package/dist/inspector/canvas/frameProtocol.js +3 -3
- package/dist/inspector/canvas/frameScrollbarStyles.d.ts +3 -0
- package/dist/inspector/canvas/frameScrollbarStyles.js +21 -0
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -1
- package/dist/inspector/canvas/keyboardShortcuts.js +4 -4
- package/dist/inspector/canvas/layout.d.ts +52 -0
- package/dist/inspector/canvas/layout.js +106 -0
- package/dist/inspector/canvas/projection.d.ts +15 -5
- package/dist/inspector/canvas/projection.js +69 -13
- package/dist/inspector/canvas/rendererBootstrap.js +32 -4
- package/dist/inspector/canvas/rendererElementSelector.js +47 -6
- 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 +9 -6
- package/dist/inspector/changes/changesLog.js +48 -32
- package/dist/inspector/changes/codecs.d.ts +1 -0
- package/dist/inspector/changes/codecs.js +10 -1
- package/dist/inspector/changes/draftChanges.d.ts +58 -0
- package/dist/inspector/changes/draftChanges.js +304 -0
- package/dist/inspector/changes/editModel.d.ts +10 -0
- package/dist/inspector/changes/editModel.js +9 -2
- package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
- package/dist/inspector/changes/previewDiagnostics.js +11 -25
- package/dist/inspector/changes/types.d.ts +7 -0
- package/dist/inspector/clientManifest.d.ts +1 -1
- 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 +54 -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/componentSemantics/adapterRegistry.d.ts +6 -1
- package/dist/inspector/componentSemantics/adapterRegistry.js +23 -1
- package/dist/inspector/componentSemantics/reactRuntime.d.ts +2 -1
- package/dist/inspector/componentSemantics/reactRuntime.js +38 -0
- package/dist/inspector/componentSemantics/runtimeBridge.d.ts +4 -2
- package/dist/inspector/componentSemantics/runtimeBridge.js +17 -0
- package/dist/inspector/componentSemantics/types.d.ts +11 -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 +20 -27
- 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 +174 -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.js +21 -15
- package/dist/inspector/overlay/interactionStyles.js +3 -1
- package/dist/inspector/overlay/spacingGestures.d.ts +13 -1
- package/dist/inspector/overlay/spacingGestures.js +82 -5
- 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 +17 -3
- package/dist/inspector/projection/renderedInstance.js +62 -11
- package/dist/inspector/projection/structuralProjection.d.ts +2 -1
- package/dist/inspector/projection/structuralProjection.js +21 -17
- package/dist/inspector/projection/textProjection.d.ts +2 -1
- package/dist/inspector/projection/textProjection.js +64 -15
- package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
- package/dist/inspector/prompt/clipboardHandoff.d.ts +15 -18
- package/dist/inspector/prompt/clipboardHandoff.js +97 -143
- package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
- package/dist/inspector/prompt/generatePrompt.js +22 -17
- package/dist/inspector/runtime/runtimeConfig.d.ts +9 -0
- package/dist/inspector/runtime/runtimeConfig.js +24 -0
- package/dist/inspector/selection/sourceSite.d.ts +12 -0
- package/dist/inspector/selection/sourceSite.js +28 -0
- package/dist/inspector/settings/SettingsDialog.js +21 -8
- 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 +46 -26
- package/dist/inspector/shell/CopyPromptButton.js +184 -93
- package/dist/inspector/shell/EmptyState.js +9 -8
- package/dist/inspector/shell/InspectorShell.js +13 -21
- 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/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 +7 -5
- 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 +2 -2
- package/dist/inspector/styleEditors/nudgeValue.js +36 -8
- package/dist/inspector/tokens/TokenField.d.ts +7 -0
- package/dist/inspector/tokens/TokenField.js +35 -25
- package/dist/inspector/tokens/TokenLabel.js +5 -14
- package/dist/inspector/tokens/editActions.d.ts +1 -1
- package/dist/inspector/tokens/editActions.js +7 -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/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 +0 -2
- package/dist/inspector/ui/dragCursor.js +2 -10
- package/dist/inspector/ui/portalContainer.d.ts +2 -0
- package/dist/inspector/ui/portalContainer.js +6 -0
- package/dist/inspector/ui/styles.js +29 -21
- 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/sentItems.d.ts +19 -0
- package/dist/inspector/workspace/sentItems.js +51 -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 +558 -311
- 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.js +3 -1
- package/dist/transport/routes.d.ts +2 -0
- package/dist/transport/routes.js +2 -0
- package/package.json +7 -3
- package/dist/inspector/changes/workspaceChanges.d.ts +0 -47
- package/dist/inspector/changes/workspaceChanges.js +0 -265
|
@@ -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
|
+
}
|
package/dist/transport/editor.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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, NUDGE_UI_OFF_QUERY_VALUE, NUDGE_UI_ON_QUERY_VALUE, 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
4
|
const FORCED_TAB_SESSION_KEY = "nudge-ui:forced-tab";
|
|
@@ -123,6 +123,8 @@ function readTabFlag(key) {
|
|
|
123
123
|
export function resolveNudgeUiClientEntry(href, context) {
|
|
124
124
|
if (context.editorDocument || context.canvasRenderer)
|
|
125
125
|
return { kind: "bootstrap" };
|
|
126
|
+
if (new URL(href).pathname.startsWith(`${NUDGE_UI_ARTIFACTS_PATH}/`))
|
|
127
|
+
return { kind: "direct" };
|
|
126
128
|
const urlSwitch = readNudgeUiQuerySwitch(href);
|
|
127
129
|
if (urlSwitch === "off")
|
|
128
130
|
return { kind: "direct" };
|
|
@@ -11,6 +11,8 @@ 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";
|
package/dist/transport/routes.js
CHANGED
|
@@ -11,6 +11,8 @@ 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";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nudge-ui",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.4",
|
|
4
|
+
"description": "A design panel for your codebase. Development only.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -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,47 +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
|
-
/** Groups consecutive synchronous updates from one gesture into one undo step.
|
|
35
|
-
* Updates outside the callback keep their own history entries.
|
|
36
|
-
*/
|
|
37
|
-
export declare function createChangeHistoryGroup(): (update: () => void) => void;
|
|
38
|
-
export declare const workspaceChangeStore: WorkspaceChangeStore;
|
|
39
|
-
/** Compatibility accessors retained for callers that have not adopted the store seam. */
|
|
40
|
-
export declare function getWorkspaceChanges(): WorkspaceChangesSnapshot;
|
|
41
|
-
export declare function subscribeWorkspaceChanges(listener: () => void): () => void;
|
|
42
|
-
export declare function commitChangeRecords(incoming: readonly ChangeRecord[], project: WorkspaceProjector): CommitResult;
|
|
43
|
-
export declare function commitStructuralChange(change: StructuralChange, project: WorkspaceProjector): boolean;
|
|
44
|
-
export declare function undoWorkspaceChange(project: WorkspaceProjector): boolean;
|
|
45
|
-
export declare function redoWorkspaceChange(project: WorkspaceProjector): boolean;
|
|
46
|
-
export declare function restoreWorkspaceChanges(next: WorkspaceContents, project: WorkspaceProjector): void;
|
|
47
|
-
export declare function resetWorkspaceChanges(): void;
|