nudge-ui 0.2.2 → 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 -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 +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 +152 -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 +225 -57
- 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/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 +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 +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 +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 +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 +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.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 +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 +9 -0
- package/dist/inspector/runtime/runtimeConfig.js +24 -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 +138 -89
- 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 +29 -21
- package/dist/inspector/tokens/TokenLabel.js +5 -14
- 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/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/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 +6 -2
- package/dist/inspector/changes/workspaceChanges.d.ts +0 -47
- package/dist/inspector/changes/workspaceChanges.js +0 -265
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { HTML_ARTIFACT_REVISION_ATTRIBUTE } from "../../transport/artifacts.js";
|
|
2
|
+
import { getDraftForCard, getDraftContentsForCard, clearSavedIterationDraft } from "../drafts/store.js";
|
|
3
|
+
import { lockDraft } from "../changes/draftChanges.js";
|
|
4
|
+
import { canWriteWorkspace } from "../canvas/workspaceLease.js";
|
|
5
|
+
import { projectDraftToDocument, isCanvasProjectionRevisionCurrent, projectToAllReadyCards } from "../canvas/projection.js";
|
|
6
|
+
import { isInlineTextEditingActive } from "../inline-text/inlineTextEditor.js";
|
|
7
|
+
import { captureHtmlIteration } from "./capture.js";
|
|
8
|
+
import { artifactDocumentUrl, commitHtmlArtifact, readHtmlArtifactRevision } from "./client.js";
|
|
9
|
+
/** Owns save ordering, draft settlement, and external document promotion. */
|
|
10
|
+
export function createHtmlIterationCoordinator(storage) {
|
|
11
|
+
const operations = new Map();
|
|
12
|
+
async function serialize(id, operation) {
|
|
13
|
+
const previous = operations.get(id) ?? Promise.resolve();
|
|
14
|
+
const pending = previous.catch(() => undefined).then(operation);
|
|
15
|
+
operations.set(id, pending);
|
|
16
|
+
try {
|
|
17
|
+
return await pending;
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
if (operations.get(id) === pending)
|
|
21
|
+
operations.delete(id);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
prepare(cardId, artifactId, baseRevision, capture) {
|
|
26
|
+
return serialize(artifactId, async () => {
|
|
27
|
+
if (!canWriteWorkspace())
|
|
28
|
+
throw new Error("This workspace is open in another tab.");
|
|
29
|
+
const draft = getDraftForCard(cardId);
|
|
30
|
+
const workspace = getDraftContentsForCard(cardId);
|
|
31
|
+
if (!draft || draft.target.kind !== "html" || draft.target.artifactId !== artifactId || !workspace)
|
|
32
|
+
throw new Error("The iteration draft is no longer available.");
|
|
33
|
+
const html = await capture();
|
|
34
|
+
const current = getDraftForCard(cardId);
|
|
35
|
+
if (current?.id !== draft.id || current.revision !== draft.revision) {
|
|
36
|
+
throw new Error("The iteration changed while saving. Your edits are still pending. Try again.");
|
|
37
|
+
}
|
|
38
|
+
const release = lockDraft(draft.id);
|
|
39
|
+
try {
|
|
40
|
+
if (!canWriteWorkspace())
|
|
41
|
+
throw new Error("This workspace is open in another tab.");
|
|
42
|
+
await storage.commit(artifactId, { document: baseRevision, preview: baseRevision }, html);
|
|
43
|
+
if (!canWriteWorkspace())
|
|
44
|
+
throw new Error("The iteration was saved. Reopen the active workspace to continue.");
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
release();
|
|
48
|
+
}
|
|
49
|
+
// No await between releasing the lock and settling this exact revision.
|
|
50
|
+
if (!clearSavedIterationDraft(cardId, draft.revision)) {
|
|
51
|
+
throw new Error("The iteration was saved, but its newer edits remain pending.");
|
|
52
|
+
}
|
|
53
|
+
return workspace;
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
refresh(cardId, artifactId, baseRevision, available) {
|
|
57
|
+
return serialize(artifactId, async () => {
|
|
58
|
+
const clean = () => {
|
|
59
|
+
const workspace = getDraftContentsForCard(cardId);
|
|
60
|
+
return canWriteWorkspace() && available() && workspace !== null
|
|
61
|
+
&& workspace.changes.length === 0 && workspace.structuralChanges.length === 0;
|
|
62
|
+
};
|
|
63
|
+
if (!clean())
|
|
64
|
+
return false;
|
|
65
|
+
const revision = await storage.readRevision(artifactId);
|
|
66
|
+
if (!clean())
|
|
67
|
+
return false;
|
|
68
|
+
// A previous promotion may have completed while an inline edit deferred reload.
|
|
69
|
+
if (revision.document === revision.preview)
|
|
70
|
+
return revision.preview !== baseRevision;
|
|
71
|
+
const draft = getDraftForCard(cardId);
|
|
72
|
+
if (!draft)
|
|
73
|
+
return false;
|
|
74
|
+
const release = lockDraft(draft.id);
|
|
75
|
+
try {
|
|
76
|
+
await storage.commit(artifactId, revision);
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
release();
|
|
80
|
+
}
|
|
81
|
+
return clean();
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const coordinator = createHtmlIterationCoordinator({ readRevision: readHtmlArtifactRevision, commit: commitHtmlArtifact });
|
|
87
|
+
function loadedRevision(frame) {
|
|
88
|
+
const revision = frame.contentDocument?.documentElement.getAttribute(HTML_ARTIFACT_REVISION_ATTRIBUTE);
|
|
89
|
+
if (!revision || !/^[a-f0-9]{64}$/.test(revision))
|
|
90
|
+
throw new Error("The HTML iteration is still loading. Try again when the frame is ready.");
|
|
91
|
+
return revision;
|
|
92
|
+
}
|
|
93
|
+
/** Materializes pending intent before the agent can start editing document.html. */
|
|
94
|
+
export async function prepareHtmlIterationHandoff(cardId, artifactId, frame) {
|
|
95
|
+
if (isInlineTextEditingActive())
|
|
96
|
+
throw new Error("Finish editing the text before saving this iteration.");
|
|
97
|
+
const contents = getDraftContentsForCard(cardId);
|
|
98
|
+
if (!contents)
|
|
99
|
+
throw new Error("The iteration draft is no longer available.");
|
|
100
|
+
if (contents.changes.length + contents.structuralChanges.length === 0)
|
|
101
|
+
return contents;
|
|
102
|
+
const document = frame.contentDocument;
|
|
103
|
+
const baseRevision = loadedRevision(frame);
|
|
104
|
+
try {
|
|
105
|
+
const workspace = await coordinator.prepare(cardId, artifactId, baseRevision, async () => {
|
|
106
|
+
if (!document || isInlineTextEditingActive())
|
|
107
|
+
throw new Error("Finish editing the text before saving this iteration.");
|
|
108
|
+
const workspace = getDraftContentsForCard(cardId);
|
|
109
|
+
if (!workspace)
|
|
110
|
+
throw new Error("The iteration draft is no longer available.");
|
|
111
|
+
const revision = await projectDraftToDocument(document, workspace);
|
|
112
|
+
if (revision === null)
|
|
113
|
+
throw new Error("The iteration preview is still updating. Try again.");
|
|
114
|
+
const html = await captureHtmlIteration(frame);
|
|
115
|
+
if (frame.contentDocument !== document || !isCanvasProjectionRevisionCurrent(document, revision)) {
|
|
116
|
+
throw new Error("The iteration changed while saving. Your edits are still pending. Try again.");
|
|
117
|
+
}
|
|
118
|
+
return html;
|
|
119
|
+
});
|
|
120
|
+
frame.src = `${artifactDocumentUrl(artifactId)}?revision=${Date.now()}`;
|
|
121
|
+
return workspace;
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
projectToAllReadyCards();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
export async function refreshHtmlIteration(cardId, artifactId, frame) {
|
|
128
|
+
const document = frame.contentDocument;
|
|
129
|
+
return coordinator.refresh(cardId, artifactId, loadedRevision(frame), () => frame.contentDocument === document && !isInlineTextEditingActive());
|
|
130
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Defines the edit destination for every HTML-iteration handoff, including fallback copies. */
|
|
2
|
+
export declare function htmlIterationPrompt(id: string, changes: string, noInspectorChanges: boolean, hasSketches: boolean, customInstructions?: string): string;
|
|
3
|
+
export declare function htmlIterationImplementationPrompt(id: string, label: string, page: string, changes: string): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Defines the edit destination for every HTML-iteration handoff, including fallback copies. */
|
|
2
|
+
export function htmlIterationPrompt(id, changes, noInspectorChanges, hasSketches, customInstructions = "") {
|
|
3
|
+
const directory = `.nudge/artifacts/${id}`;
|
|
4
|
+
return [
|
|
5
|
+
"# Edit this HTML iteration",
|
|
6
|
+
"",
|
|
7
|
+
`Edit only \`${directory}/document.html\`. This is the standalone HTML and CSS shown in the selected Nudge iteration frame.`,
|
|
8
|
+
"Preserve its responsive CSS and existing design edits. Keep application source files unchanged. Any original source locations below identify captured elements; they are not files to edit.",
|
|
9
|
+
`Keep \`${directory}/baseline.html\`, \`${directory}/preview.html\`, and metadata unchanged. Nudge will reload the iteration from your updated document.`,
|
|
10
|
+
...(hasSketches ? ["Implement the attached sketch notes in this iteration's HTML and CSS. Match numbered notes to their marks in the image."] : []),
|
|
11
|
+
"",
|
|
12
|
+
...(noInspectorChanges ? ["Use the current iteration as the editing base. Existing visual edits are already saved in its markup and styles.", ...(customInstructions.trim() ? ["", "## Custom instructions", "", customInstructions.trim()] : [])] : [changes]),
|
|
13
|
+
].join("\n");
|
|
14
|
+
}
|
|
15
|
+
export function htmlIterationImplementationPrompt(id, label, page, changes) {
|
|
16
|
+
const directory = `.nudge/artifacts/${id}`;
|
|
17
|
+
return [
|
|
18
|
+
`# Implement ${label} in the live app`,
|
|
19
|
+
"",
|
|
20
|
+
`Page: ${page}`,
|
|
21
|
+
`Use \`${directory}/document.html\` as the design reference, including all saved visual edits.`,
|
|
22
|
+
"Update application source for this page to match it. Reuse existing components and styles, and preserve app behavior and data.",
|
|
23
|
+
"Keep iteration files unchanged; captured source locations below are hints for finding app code.",
|
|
24
|
+
...(changes.trim() ? ["", changes] : []),
|
|
25
|
+
].join("\n");
|
|
26
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from "react";
|
|
3
|
+
import { IconMenu2 } from "@tabler/icons-react";
|
|
4
|
+
import { Popover as BasePopover } from "@base-ui/react/popover";
|
|
3
5
|
import { setSelectedElement } from "../selection/selectionStore.js";
|
|
4
6
|
import { computeNavigationNodes } from "../selection/hierarchy.js";
|
|
5
7
|
import { resolveSelectionFromElement } from "../selection/resolveSelection.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
8
|
+
import { portalContainer } from "../ui/portalContainer.js";
|
|
9
|
+
import { cx } from "../ui/classNames.js";
|
|
9
10
|
function nodeDescription(node) {
|
|
10
11
|
const id = node.id ? `#${node.id}` : "";
|
|
11
12
|
const className = typeof node.className === "string"
|
|
12
13
|
? node.className.trim().split(/\s+/).filter(Boolean).slice(0, 2).map((name) => `.${name}`).join("")
|
|
13
14
|
: "";
|
|
14
|
-
|
|
15
|
-
return `${node.localName}${id}${className}${source ? ` — ${source}` : ""}`;
|
|
15
|
+
return `${node.localName}${id}${className}`;
|
|
16
16
|
}
|
|
17
17
|
function selectNode(node) {
|
|
18
18
|
const resolved = resolveSelectionFromElement(node);
|
|
@@ -21,20 +21,26 @@ function selectNode(node) {
|
|
|
21
21
|
}
|
|
22
22
|
export function DomNavigator({ selected, hierarchy, anchor, project }) {
|
|
23
23
|
const nodes = computeNavigationNodes(selected.domElement, hierarchy);
|
|
24
|
+
const parents = nodes.filter((node) => node.direction === "up");
|
|
25
|
+
const children = nodes.filter((node) => node.direction === "down");
|
|
24
26
|
const [expanded, setExpanded] = useState(false);
|
|
25
27
|
const [hovered, setHovered] = useState(null);
|
|
26
28
|
if (nodes.length === 0)
|
|
27
29
|
return null;
|
|
28
30
|
const preview = hovered ? project(hovered) : null;
|
|
29
|
-
const
|
|
30
|
-
setExpanded(
|
|
31
|
-
|
|
31
|
+
const setOpen = (next) => {
|
|
32
|
+
setExpanded(next);
|
|
33
|
+
if (!next)
|
|
34
|
+
setHovered(null);
|
|
35
|
+
};
|
|
36
|
+
const renderItem = (node, depth) => {
|
|
37
|
+
const current = node === null;
|
|
38
|
+
const element = current ? selected.domElement : node.element;
|
|
39
|
+
return (_jsx("button", { type: "button", className: cx("dom-navigator__item", current && "dom-navigator__item--selected"), "data-test": "dom-navigator-item", "data-current": current ? "true" : undefined, "data-direction": node?.direction, "data-depth": depth, "data-cid": element.getAttribute("data-cid") ?? undefined, style: { paddingLeft: `calc(var(--space-8) + ${depth * 16}px)` }, role: "menuitem", "aria-current": current ? "true" : undefined, onMouseEnter: () => setHovered(current ? null : element), onClick: () => {
|
|
40
|
+
if (!current)
|
|
41
|
+
selectNode(element);
|
|
42
|
+
setOpen(false);
|
|
43
|
+
}, children: _jsx("span", { children: nodeDescription(element) }) }, current ? "current" : `${node.direction}-${node.depth}-${element.getAttribute("data-cid") ?? element.localName}`));
|
|
32
44
|
};
|
|
33
|
-
return (_jsxs(_Fragment, { children: [preview ? _jsx("div", { className: "dom-navigator-preview", "data-test": "dom-navigator-preview", style: preview, "aria-hidden": "true" }) : null,
|
|
34
|
-
if (!(event.relatedTarget instanceof Node) || !event.currentTarget.contains(event.relatedTarget))
|
|
35
|
-
close();
|
|
36
|
-
}, children: [_jsxs("button", { type: "button", className: "dom-navigator__trigger", "data-test": "dom-navigator-trigger", "aria-expanded": expanded, "aria-haspopup": "menu", title: nodeDescription(selected.domElement), onMouseEnter: () => setHovered(null), children: [_jsx("span", { children: nodeLabel(selected.domElement) }), _jsx("span", { "aria-hidden": "true", children: "\u2304" })] }), expanded ? (_jsx("div", { className: "dom-navigator__menu", role: "menu", children: nodes.map((node) => (_jsxs("button", { type: "button", className: "dom-navigator__item", "data-test": "dom-navigator-item", "data-direction": node.direction, "data-depth": node.depth, "data-cid": node.element.getAttribute("data-cid") ?? undefined, title: nodeDescription(node.element), role: "menuitem", onMouseEnter: () => setHovered(node.element), onClick: () => {
|
|
37
|
-
selectNode(node.element);
|
|
38
|
-
close();
|
|
39
|
-
}, children: [_jsx("span", { "aria-hidden": "true", children: node.direction === "up" ? "↑" : "↓" }), _jsx("span", { children: nodeLabel(node.element) })] }, `${node.direction}-${node.depth}-${node.element.getAttribute("data-cid") ?? node.element.localName}`))) })) : null] })] }));
|
|
45
|
+
return (_jsxs(_Fragment, { children: [preview ? _jsx("div", { className: "dom-navigator-preview", "data-test": "dom-navigator-preview", style: preview, "aria-hidden": "true" }) : null, _jsx("div", { className: "dom-navigator", "data-test": "dom-navigator", "aria-label": "DOM navigator", style: { left: Math.max(4, anchor.left), top: Math.max(4, anchor.top - 28) }, children: _jsxs(BasePopover.Root, { open: expanded, onOpenChange: setOpen, children: [_jsx(BasePopover.Trigger, { render: _jsx("button", { type: "button", className: "dom-navigator__trigger", "data-test": "dom-navigator-trigger", "aria-label": "Open DOM navigator", "aria-haspopup": "menu", "aria-expanded": expanded, onMouseEnter: () => setHovered(null) }), children: _jsx(IconMenu2, { size: 14, "aria-hidden": "true" }) }), _jsx("span", { className: "dom-navigator__label", children: nodeDescription(selected.domElement) }), _jsx(BasePopover.Portal, { container: portalContainer(), children: _jsx(BasePopover.Positioner, { className: "dom-navigator__positioner", side: "top", align: "start", sideOffset: 4, children: _jsxs(BasePopover.Popup, { className: "dom-navigator__menu", role: "menu", "aria-label": "DOM navigator", onMouseLeave: () => setHovered(null), children: [parents.slice().reverse().map((node) => renderItem(node, parents.length - node.depth)), renderItem(null, parents.length), children.map((node) => renderItem(node, parents.length + node.depth))] }) }) })] }) })] }));
|
|
40
46
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { DESIGN_SELECT_CURSOR, INTERACTION_CURSOR_PROPERTY } from "../ui/customCursors.js";
|
|
1
2
|
const INTERACTION_STYLE_ID = "nudge-ui-interaction-styles";
|
|
2
3
|
const interactionStyleDocuments = new Set();
|
|
3
4
|
const INTERACTION_CSS = `
|
|
5
|
+
html, body,
|
|
4
6
|
[data-cid], [data-cid] * {
|
|
5
|
-
cursor:
|
|
7
|
+
cursor: var(${INTERACTION_CURSOR_PROPERTY}, ${DESIGN_SELECT_CURSOR}) !important;
|
|
6
8
|
-webkit-user-select: none !important;
|
|
7
9
|
user-select: none !important;
|
|
8
10
|
}
|
|
@@ -30,9 +30,21 @@ export interface SpacingDescriptor {
|
|
|
30
30
|
export declare const SPACING_GUIDE_HANDLE_LENGTH = 40;
|
|
31
31
|
/** Finds the spacing target under a document-local pointer location. */
|
|
32
32
|
export declare function getSpacingAffordanceAtPoint(doc: Document, x: number, y: number): SpacingAffordance | null;
|
|
33
|
+
/** Finds the spacing target whose visible padding or gap area contains the
|
|
34
|
+
* pointer, even when the pointer is outside the drag handle. Hover previews use
|
|
35
|
+
* this; drags and the resize cursor still require the handle hit from
|
|
36
|
+
* getSpacingAffordanceAtPoint. */
|
|
37
|
+
export declare function getSpacingHoverAtPoint(doc: Document, x: number, y: number): SpacingAffordance | null;
|
|
33
38
|
/** Re-resolves a renderer-reported spacing descriptor in its owner document. */
|
|
34
39
|
export declare function getSpacingAffordanceForDescriptor(element: HTMLElement, descriptor: SpacingDescriptor): SpacingAffordance | null;
|
|
35
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Calculates a direct-manipulation value, optionally stepping changes by 8px.
|
|
42
|
+
* While Shift is held the value snaps to a step anchor: the first displacement
|
|
43
|
+
* picks the nearest multiple of 8 from the authored value in the drag
|
|
44
|
+
* direction (ties resolve upward), and further movement adds whole 8px steps
|
|
45
|
+
* from that anchor, so the result is always a multiple of 8 once the drag
|
|
46
|
+
* moves. At the exact start point the authored value is preserved.
|
|
47
|
+
*/
|
|
36
48
|
export declare function spacingValueForDrag(affordance: Pick<SpacingAffordance, "dragAxis" | "direction" | "value">, start: {
|
|
37
49
|
x: number;
|
|
38
50
|
y: number;
|
|
@@ -263,6 +263,52 @@ function gapDragHit(segment) {
|
|
|
263
263
|
const orientation = segment.axis === "horizontal" ? "vertical" : "horizontal";
|
|
264
264
|
return guideHit(gapGuide(segment), orientation);
|
|
265
265
|
}
|
|
266
|
+
/** Matches the padding bar whose visible area contains the pointer, whether or
|
|
267
|
+
* not the point is inside the drag handle. Returns the first side by the same
|
|
268
|
+
* priority order as the drag affordances so both paths agree on overlaps. */
|
|
269
|
+
function paddingAffordanceInAreaAtPoint(element, x, y) {
|
|
270
|
+
const model = boxModel(element);
|
|
271
|
+
if (!containsPoint(model.outer, x, y))
|
|
272
|
+
return null;
|
|
273
|
+
const sides = ["top", "right", "bottom", "left"];
|
|
274
|
+
for (const side of sides) {
|
|
275
|
+
const affordance = paddingAffordanceForSide(element, side, model);
|
|
276
|
+
if (affordance
|
|
277
|
+
&& affordance.affectedAreas.some((area) => containsRectPoint(area, x, y))) {
|
|
278
|
+
return affordance;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
/** Matches a layout gap whose visible area contains the pointer, whether or not
|
|
284
|
+
* the point is inside the drag handle. Shares segment priority and geometry
|
|
285
|
+
* with gapAffordanceAtPoint so hover and drag agree on crossed gap properties. */
|
|
286
|
+
function gapAffordanceInAreaAtPoint(element, x, y) {
|
|
287
|
+
const segments = gapSegments(element);
|
|
288
|
+
if (segments.length === 0)
|
|
289
|
+
return null;
|
|
290
|
+
const candidate = segments.find((segment) => containsRectPoint(gapArea(segment), x, y));
|
|
291
|
+
if (!candidate)
|
|
292
|
+
return null;
|
|
293
|
+
const matchingSegments = segments.filter((segment) => segment.property === candidate.property);
|
|
294
|
+
const first = matchingSegments[0];
|
|
295
|
+
const guide = gapGuide(first);
|
|
296
|
+
return {
|
|
297
|
+
kind: "gap",
|
|
298
|
+
property: first.property,
|
|
299
|
+
side: null,
|
|
300
|
+
axis: first.axis,
|
|
301
|
+
dragAxis: first.axis === "horizontal" ? "x" : "y",
|
|
302
|
+
value: gapValue(getElementComputedStyle(element), first.property),
|
|
303
|
+
cursor: first.axis === "horizontal" ? "ew-resize" : "ns-resize",
|
|
304
|
+
guide,
|
|
305
|
+
affectedGuides: matchingSegments.map(gapGuide),
|
|
306
|
+
affectedAreas: matchingSegments.map(gapArea),
|
|
307
|
+
hit: gapDragHit(first),
|
|
308
|
+
direction: 1,
|
|
309
|
+
element,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
266
312
|
function gapValue(style, property) {
|
|
267
313
|
const direct = style.getPropertyValue(property)
|
|
268
314
|
|| (property === "row-gap" ? style.rowGap : style.columnGap);
|
|
@@ -313,6 +359,24 @@ export function getSpacingAffordanceAtPoint(doc, x, y) {
|
|
|
313
359
|
}
|
|
314
360
|
return null;
|
|
315
361
|
}
|
|
362
|
+
/** Finds the spacing target whose visible padding or gap area contains the
|
|
363
|
+
* pointer, even when the pointer is outside the drag handle. Hover previews use
|
|
364
|
+
* this; drags and the resize cursor still require the handle hit from
|
|
365
|
+
* getSpacingAffordanceAtPoint. */
|
|
366
|
+
export function getSpacingHoverAtPoint(doc, x, y) {
|
|
367
|
+
const candidates = trackedElementsAtPoint(doc, x, y);
|
|
368
|
+
for (const element of candidates) {
|
|
369
|
+
const padding = paddingAffordanceInAreaAtPoint(element, x, y);
|
|
370
|
+
if (padding)
|
|
371
|
+
return padding;
|
|
372
|
+
}
|
|
373
|
+
for (const element of candidates) {
|
|
374
|
+
const gap = gapAffordanceInAreaAtPoint(element, x, y);
|
|
375
|
+
if (gap)
|
|
376
|
+
return gap;
|
|
377
|
+
}
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
316
380
|
/** Re-resolves a renderer-reported spacing descriptor in its owner document. */
|
|
317
381
|
export function getSpacingAffordanceForDescriptor(element, descriptor) {
|
|
318
382
|
if (descriptor.kind === "padding") {
|
|
@@ -329,14 +393,27 @@ export function getSpacingAffordanceForDescriptor(element, descriptor) {
|
|
|
329
393
|
const guide = gapGuide(segment);
|
|
330
394
|
return gapAffordanceAtPoint(element, guide.left + guide.width / 2, guide.top + guide.height / 2, descriptor.property);
|
|
331
395
|
}
|
|
332
|
-
/**
|
|
396
|
+
/**
|
|
397
|
+
* Calculates a direct-manipulation value, optionally stepping changes by 8px.
|
|
398
|
+
* While Shift is held the value snaps to a step anchor: the first displacement
|
|
399
|
+
* picks the nearest multiple of 8 from the authored value in the drag
|
|
400
|
+
* direction (ties resolve upward), and further movement adds whole 8px steps
|
|
401
|
+
* from that anchor, so the result is always a multiple of 8 once the drag
|
|
402
|
+
* moves. At the exact start point the authored value is preserved.
|
|
403
|
+
*/
|
|
333
404
|
export function spacingValueForDrag(affordance, start, point, shiftKey = false) {
|
|
334
405
|
const delta = affordance.dragAxis === "x" ? point.x - start.x : point.y - start.y;
|
|
335
406
|
const signedDelta = delta * affordance.direction;
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
407
|
+
if (shiftKey) {
|
|
408
|
+
const step = SPACING_SHIFT_STEP;
|
|
409
|
+
if (signedDelta !== 0) {
|
|
410
|
+
const anchor = signedDelta > 0
|
|
411
|
+
? Math.ceil(affordance.value / step) * step
|
|
412
|
+
: Math.floor(affordance.value / step) * step;
|
|
413
|
+
return Math.max(0, anchor + Math.trunc(signedDelta / step) * step);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return Math.max(0, Math.round(affordance.value + signedDelta));
|
|
340
417
|
}
|
|
341
418
|
export function spacingValueCss(value) {
|
|
342
419
|
return `${Math.max(0, Math.round(value))}px`;
|
|
@@ -3,8 +3,8 @@ import type { RenderedInstanceOverride } from "../changes/editModel.js";
|
|
|
3
3
|
import { type StructuralChange } from "./structuralProjection.js";
|
|
4
4
|
import type { ComponentOverride } from "../componentSemantics/types.js";
|
|
5
5
|
import { type TextContentChangeRecord } from "../changes/types.js";
|
|
6
|
-
import type {
|
|
7
|
-
export interface
|
|
6
|
+
import type { DraftContents } from "../changes/draftChanges.js";
|
|
7
|
+
export interface DraftProjectionSource extends DraftContents {
|
|
8
8
|
readonly revision: number;
|
|
9
9
|
}
|
|
10
10
|
export interface CompiledManagedStyles {
|
|
@@ -15,7 +15,7 @@ export interface SerializedManagedStyles {
|
|
|
15
15
|
readonly css: string;
|
|
16
16
|
}
|
|
17
17
|
/** A complete, read-only projection for one document-local runtime. */
|
|
18
|
-
export interface
|
|
18
|
+
export interface DraftProjectionPlan<ManagedStyles> {
|
|
19
19
|
readonly sourceRevision: number;
|
|
20
20
|
readonly managedStyles: ManagedStyles;
|
|
21
21
|
readonly instanceOverrides: readonly RenderedInstanceOverride[];
|
|
@@ -31,7 +31,7 @@ export interface DocumentProjectionAdapter<ManagedStyles> {
|
|
|
31
31
|
applyComponents(overrides: readonly ComponentOverride[]): void;
|
|
32
32
|
applyManagedStyles(styles: ManagedStyles): void;
|
|
33
33
|
}
|
|
34
|
-
export declare function
|
|
34
|
+
export declare function compileDraftProjection(snapshot: DraftProjectionSource): DraftProjectionPlan<CompiledManagedStyles>;
|
|
35
35
|
/**
|
|
36
36
|
* Replaces one document projection in the required order.
|
|
37
37
|
*
|
|
@@ -39,5 +39,5 @@ export declare function compileWorkspaceProjection(snapshot: WorkspaceProjection
|
|
|
39
39
|
* can depend on the restored element order. Managed CSS is applied last so
|
|
40
40
|
* selectors can use the document-local instance markers installed above.
|
|
41
41
|
*/
|
|
42
|
-
export declare function
|
|
43
|
-
export declare function
|
|
42
|
+
export declare function applyDraftProjection<ManagedStyles>(adapter: DocumentProjectionAdapter<ManagedStyles>, plan: DraftProjectionPlan<ManagedStyles>): void;
|
|
43
|
+
export declare function applyHostDraftProjection(plan: DraftProjectionPlan<CompiledManagedStyles>): void;
|
|
@@ -6,7 +6,7 @@ import { replaceComponentOverrideProjection } from "../componentSemantics/index.
|
|
|
6
6
|
import { componentChangeToOverride } from "../componentSemantics/changeModel.js";
|
|
7
7
|
import { isComponentChange } from "../changes/types.js";
|
|
8
8
|
import { buildManagedStyleRules } from "../changes/managedStyleProjection.js";
|
|
9
|
-
export function
|
|
9
|
+
export function compileDraftProjection(snapshot) {
|
|
10
10
|
const changes = [...snapshot.changes];
|
|
11
11
|
const rules = buildManagedStyleRules(changes);
|
|
12
12
|
return {
|
|
@@ -31,7 +31,7 @@ export function compileWorkspaceProjection(snapshot) {
|
|
|
31
31
|
* can depend on the restored element order. Managed CSS is applied last so
|
|
32
32
|
* selectors can use the document-local instance markers installed above.
|
|
33
33
|
*/
|
|
34
|
-
export function
|
|
34
|
+
export function applyDraftProjection(adapter, plan) {
|
|
35
35
|
adapter.applyStructural(plan.structuralChanges);
|
|
36
36
|
adapter.applyRenderedInstances(plan.instanceOverrides);
|
|
37
37
|
adapter.applyText(plan.textContentChanges);
|
|
@@ -53,6 +53,6 @@ const hostDocumentProjectionAdapter = {
|
|
|
53
53
|
applyRules([...styles.rules]);
|
|
54
54
|
},
|
|
55
55
|
};
|
|
56
|
-
export function
|
|
57
|
-
|
|
56
|
+
export function applyHostDraftProjection(plan) {
|
|
57
|
+
applyDraftProjection(hostDocumentProjectionAdapter, plan);
|
|
58
58
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FrameProjection } from "../canvas/projection.js";
|
|
1
2
|
import type { RenderedInstanceOverride, RenderedInstanceRef } from "../changes/editModel.js";
|
|
2
3
|
export { isRenderedInstanceOverride, isRenderedInstanceRef } from "../changes/editModel.js";
|
|
3
4
|
export type { RenderedInstanceOverride, RenderedInstanceRef, SourceSiteRef } from "../changes/editModel.js";
|
|
@@ -50,7 +51,7 @@ export declare function getRenderedInstanceProjectionReports(doc: Document): rea
|
|
|
50
51
|
/** Diagnostics arrive independently when a renderer reports or React reconciles. */
|
|
51
52
|
export declare function subscribeRenderedInstanceDiagnostics(listener: () => void): () => void;
|
|
52
53
|
export declare function getRenderedInstanceDiagnosticRevision(): number;
|
|
53
|
-
export declare function recordCanvasRenderedInstanceProjectionReports(cardId: string, revision: number, reports: readonly DocumentProjectionReport[]): void;
|
|
54
|
+
export declare function recordCanvasRenderedInstanceProjectionReports(cardId: string, revision: number, reports: readonly DocumentProjectionReport[], projection?: FrameProjection): void;
|
|
54
55
|
export declare function clearCanvasRenderedInstanceProjectionReports(cardId: string): void;
|
|
55
56
|
export declare function getRenderedInstanceChangeDiagnostics(overrideId: string): RenderedInstanceChangeDiagnostic[];
|
|
56
57
|
export declare function isDocumentProjectionReport(value: unknown): value is DocumentProjectionReport;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getActiveDraftChanges, getDraftChanges } from "../changes/draftChanges.js";
|
|
1
2
|
import { escapeAttrValue } from "./cssEscapes.js";
|
|
2
3
|
import { isNudgeUiDev } from "../runtime/devFlag.js";
|
|
3
4
|
import { sourceSiteSelector } from "../selection/sourceSite.js";
|
|
@@ -74,8 +75,8 @@ export function resolveRenderedInstance(doc, ref) {
|
|
|
74
75
|
/** Checks stable source/evidence fields without treating a post-move ordinal as identity. */
|
|
75
76
|
export function matchesRenderedInstanceEvidence(el, ref) {
|
|
76
77
|
const { props, text, ariaLabel = null } = ref.locator;
|
|
77
|
-
return el.getAttribute("data-cid") === ref.sourceSite.cid
|
|
78
|
-
&& el.getAttribute("data-src") === ref.sourceSite.src
|
|
78
|
+
return (el.getAttribute("data-cid") ?? "") === ref.sourceSite.cid
|
|
79
|
+
&& (el.getAttribute("data-src") ?? "") === ref.sourceSite.src
|
|
79
80
|
&& el.getAttribute("data-cprops") === props
|
|
80
81
|
&& normalizedText(el) === text
|
|
81
82
|
&& el.getAttribute("aria-label") === ariaLabel;
|
|
@@ -254,10 +255,10 @@ export function subscribeRenderedInstanceDiagnostics(listener) {
|
|
|
254
255
|
export function getRenderedInstanceDiagnosticRevision() {
|
|
255
256
|
return diagnosticRevision;
|
|
256
257
|
}
|
|
257
|
-
export function recordCanvasRenderedInstanceProjectionReports(cardId, revision, reports) {
|
|
258
|
+
export function recordCanvasRenderedInstanceProjectionReports(cardId, revision, reports, projection) {
|
|
258
259
|
if (!Number.isSafeInteger(revision) || revision < 0 || !reports.every(isDocumentProjectionReport))
|
|
259
260
|
return;
|
|
260
|
-
const expected = new Set(canonicalOverrides.keys());
|
|
261
|
+
const expected = projection ? new Set(projection.instanceOverrides.map((record) => record.id)) : new Set(canonicalOverrides.keys());
|
|
261
262
|
if (reports.length !== expected.size || new Set(reports.map((report) => report.overrideId)).size !== reports.length
|
|
262
263
|
|| reports.some((report) => !expected.has(report.overrideId)))
|
|
263
264
|
return;
|
|
@@ -267,7 +268,8 @@ export function recordCanvasRenderedInstanceProjectionReports(cardId, revision,
|
|
|
267
268
|
const next = reports.map((report) => ({ ...report }));
|
|
268
269
|
if (existing && existing.revision === revision && sameReports(existing.reports, next))
|
|
269
270
|
return;
|
|
270
|
-
|
|
271
|
+
const current = getActiveDraftChanges();
|
|
272
|
+
reportsByCanvasCard.set(cardId, { revision, reports: next, draftId: projection?.draftId ?? current.draftId, draftRevision: projection?.draftRevision ?? current.revision });
|
|
271
273
|
notifyDiagnostics();
|
|
272
274
|
}
|
|
273
275
|
export function clearCanvasRenderedInstanceProjectionReports(cardId) {
|
|
@@ -281,6 +283,8 @@ export function getRenderedInstanceChangeDiagnostics(overrideId) {
|
|
|
281
283
|
if (host)
|
|
282
284
|
diagnostics.push({ ...host, document: "Inspect" });
|
|
283
285
|
for (const [cardId, entry] of reportsByCanvasCard) {
|
|
286
|
+
if (entry.draftId !== getActiveDraftChanges().draftId || entry.draftRevision !== getDraftChanges(entry.draftId).revision)
|
|
287
|
+
continue;
|
|
284
288
|
const report = entry.reports.find((candidate) => candidate.overrideId === overrideId);
|
|
285
289
|
if (report)
|
|
286
290
|
diagnostics.push({ ...report, document: `Canvas ${cardId}` });
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FrameProjection } from "../canvas/projection.js";
|
|
1
2
|
import type { StructuralChange, StructuralDelete, StructuralMove, StructuralProjectionReason, StructuralProjectionReport } from "../changes/structuralTypes.js";
|
|
2
3
|
export { isStructuralChange, isStructuralDelete, isStructuralMove, isStructuralProjectionReport, } from "./structuralProjectionBoundary.js";
|
|
3
4
|
/**
|
|
@@ -76,7 +77,7 @@ export declare function getStructuralProjectionReports(doc: Document): readonly
|
|
|
76
77
|
* Frames may report only their document-local outcome. The controller owns
|
|
77
78
|
* these reports and filters them against its current canonical change set.
|
|
78
79
|
*/
|
|
79
|
-
export declare function recordCanvasStructuralProjectionReports(cardId: string, revision: number, reports: readonly StructuralProjectionReport[]): void;
|
|
80
|
+
export declare function recordCanvasStructuralProjectionReports(cardId: string, revision: number, reports: readonly StructuralProjectionReport[], projection?: FrameProjection): void;
|
|
80
81
|
export declare function clearCanvasStructuralProjectionReports(cardId: string): void;
|
|
81
82
|
/** Removes document-local reports for canonical intents that no longer exist. */
|
|
82
83
|
export declare function pruneStructuralProjectionReports(changeIds: ReadonlySet<string>): void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { getActiveDraftChanges, getDraftChanges } from "../changes/draftChanges.js";
|
|
1
2
|
import { captureRenderedInstance, matchesRenderedInstanceEvidence, resolveRenderedInstance, } from "./renderedInstance.js";
|
|
2
3
|
import { isStructuralProjectionReport } from "./structuralProjectionBoundary.js";
|
|
3
|
-
import {
|
|
4
|
+
import { resetDraftChanges, draftChangeStore, } from "../changes/draftChanges.js";
|
|
4
5
|
export { isStructuralChange, isStructuralDelete, isStructuralMove, isStructuralProjectionReport, } from "./structuralProjectionBoundary.js";
|
|
5
6
|
const VOID_PARENT_TAGS = new Set([
|
|
6
7
|
"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr",
|
|
@@ -193,9 +194,9 @@ export function createStructuralDelete(element, id = structuralId()) {
|
|
|
193
194
|
...(route ? { route } : {}),
|
|
194
195
|
...(state ? { state } : {}),
|
|
195
196
|
};
|
|
196
|
-
if (!
|
|
197
|
+
if (!draftChangeStore.commitStructuralChange(change))
|
|
197
198
|
return null;
|
|
198
|
-
projectStructuralChanges(
|
|
199
|
+
projectStructuralChanges(draftChangeStore.getSnapshot());
|
|
199
200
|
return change;
|
|
200
201
|
}
|
|
201
202
|
/**
|
|
@@ -236,22 +237,22 @@ export function createStructuralMove(element, destination, id = structuralId())
|
|
|
236
237
|
: destinationChildren.length - (sameParent ? 1 : 0),
|
|
237
238
|
},
|
|
238
239
|
};
|
|
239
|
-
if (!
|
|
240
|
+
if (!draftChangeStore.commitStructuralChange(change))
|
|
240
241
|
return null;
|
|
241
|
-
projectStructuralChanges(
|
|
242
|
+
projectStructuralChanges(draftChangeStore.getSnapshot());
|
|
242
243
|
return change;
|
|
243
244
|
}
|
|
244
245
|
export function getStructuralChanges() {
|
|
245
|
-
return
|
|
246
|
+
return draftChangeStore.getSnapshot().structuralChanges;
|
|
246
247
|
}
|
|
247
248
|
export function getStructuralDeletes() {
|
|
248
249
|
return getStructuralChanges().filter((change) => change.kind === "delete");
|
|
249
250
|
}
|
|
250
251
|
/** Revert removes one canonical intent and leaves every other intent intact. */
|
|
251
252
|
export function revertStructuralChange(changeId) {
|
|
252
|
-
const changed =
|
|
253
|
+
const changed = draftChangeStore.revertStructuralChangeRecord(changeId);
|
|
253
254
|
if (changed)
|
|
254
|
-
projectStructuralChanges(
|
|
255
|
+
projectStructuralChanges(draftChangeStore.getSnapshot());
|
|
255
256
|
return changed;
|
|
256
257
|
}
|
|
257
258
|
/**
|
|
@@ -265,10 +266,10 @@ export function revertStructuralChange(changeId) {
|
|
|
265
266
|
export function reconcileVerifiedStructuralChanges(verifiedIds) {
|
|
266
267
|
if (verifiedIds.size === 0)
|
|
267
268
|
return 0;
|
|
268
|
-
const removed =
|
|
269
|
+
const removed = draftChangeStore.reconcileActiveDraftChanges(new Set(), verifiedIds);
|
|
269
270
|
if (removed === 0)
|
|
270
271
|
return 0;
|
|
271
|
-
projectStructuralChanges(
|
|
272
|
+
projectStructuralChanges(draftChangeStore.getSnapshot());
|
|
272
273
|
for (const [cardId, canvasReports] of reportsByCanvasCard) {
|
|
273
274
|
reportsByCanvasCard.set(cardId, {
|
|
274
275
|
...canvasReports,
|
|
@@ -280,7 +281,7 @@ export function reconcileVerifiedStructuralChanges(verifiedIds) {
|
|
|
280
281
|
}
|
|
281
282
|
/** State changes drive controller-to-renderer projection. Diagnostics do not. */
|
|
282
283
|
export function subscribeStructuralChanges(listener) {
|
|
283
|
-
return
|
|
284
|
+
return draftChangeStore.subscribe(listener);
|
|
284
285
|
}
|
|
285
286
|
/** Compatibility name retained while the controller moved to a full union. */
|
|
286
287
|
export const subscribeStructuralDeletes = subscribeStructuralChanges;
|
|
@@ -549,10 +550,10 @@ export function getStructuralProjectionReports(doc) {
|
|
|
549
550
|
* Frames may report only their document-local outcome. The controller owns
|
|
550
551
|
* these reports and filters them against its current canonical change set.
|
|
551
552
|
*/
|
|
552
|
-
export function recordCanvasStructuralProjectionReports(cardId, revision, reports) {
|
|
553
|
+
export function recordCanvasStructuralProjectionReports(cardId, revision, reports, projection) {
|
|
553
554
|
if (!Number.isSafeInteger(revision) || revision < 0 || !reports.every(isStructuralProjectionReport))
|
|
554
555
|
return;
|
|
555
|
-
const expected = new Set(getStructuralChanges().map((change) => change.id));
|
|
556
|
+
const expected = projection ? new Set(projection.structuralChanges.map((record) => record.id)) : new Set(getStructuralChanges().map((change) => change.id));
|
|
556
557
|
if (reports.length !== expected.size || new Set(reports.map((report) => report.changeId)).size !== reports.length
|
|
557
558
|
|| reports.some((report) => !expected.has(report.changeId)))
|
|
558
559
|
return;
|
|
@@ -562,7 +563,8 @@ export function recordCanvasStructuralProjectionReports(cardId, revision, report
|
|
|
562
563
|
const next = reports.map((report) => ({ ...report }));
|
|
563
564
|
if (existing && existing.revision === revision && sameReports(existing.reports, next))
|
|
564
565
|
return;
|
|
565
|
-
|
|
566
|
+
const current = getActiveDraftChanges();
|
|
567
|
+
reportsByCanvasCard.set(cardId, { revision, reports: next, draftId: projection?.draftId ?? current.draftId, draftRevision: projection?.draftRevision ?? current.revision });
|
|
566
568
|
notifyDiagnostics();
|
|
567
569
|
}
|
|
568
570
|
export function clearCanvasStructuralProjectionReports(cardId) {
|
|
@@ -594,6 +596,8 @@ export function getStructuralChangeDiagnostics(changeId) {
|
|
|
594
596
|
if (host)
|
|
595
597
|
diagnostics.push({ ...host, document: "Inspect" });
|
|
596
598
|
for (const [cardId, entry] of reportsByCanvasCard) {
|
|
599
|
+
if (entry.draftId !== getActiveDraftChanges().draftId || entry.draftRevision !== getDraftChanges(entry.draftId).revision)
|
|
600
|
+
continue;
|
|
597
601
|
const report = entry.reports.find((candidate) => candidate.changeId === changeId);
|
|
598
602
|
if (report)
|
|
599
603
|
diagnostics.push({ ...report, document: `Canvas ${cardId}` });
|
|
@@ -615,7 +619,7 @@ export function resetStructuralDeleteProjection() {
|
|
|
615
619
|
reportsByCanvasCard.clear();
|
|
616
620
|
nextStructuralId = 1;
|
|
617
621
|
diagnosticRevision = 0;
|
|
618
|
-
|
|
622
|
+
resetDraftChanges();
|
|
619
623
|
notifyDiagnostics();
|
|
620
624
|
}
|
|
621
625
|
/**
|