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,231 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
import { getCanvasCards, selectCard, focusCanvasCards, activateIframeWorkspace } from "../canvas/canvasStore.js";
|
|
3
|
+
import { contentEditTarget } from "../canvas/frameContent.js";
|
|
4
|
+
import { deserializeChange, isSerializableChange, serializeChange, } from "../changes/codecs.js";
|
|
5
|
+
import { getActiveDraftChanges, getDraftChanges, restoreDraftChanges, subscribeDraftChanges, setActiveDraft, clearDraftChanges, listDraftIds, resetDraftChanges, } from "../changes/draftChanges.js";
|
|
6
|
+
import { refreshDraftPreview } from "../changes/changesLog.js";
|
|
7
|
+
import { isStructuralChange } from "../projection/structuralProjection.js";
|
|
8
|
+
import { canWriteWorkspace } from "../canvas/workspaceLease.js";
|
|
9
|
+
import { getSketchStoreSnapshot, subscribeSketchStore } from "../sketch/store.js";
|
|
10
|
+
import { documentTarget, isDraftTarget, targetFromKey, targetKey } from "./model.js";
|
|
11
|
+
let projectId = "";
|
|
12
|
+
let persistent = true;
|
|
13
|
+
/** Overrides the capture-URL owner for sketches drawn while another draft was active. */
|
|
14
|
+
const sketchOwners = new Map();
|
|
15
|
+
const contentDrafts = new WeakMap();
|
|
16
|
+
const listeners = new Set();
|
|
17
|
+
let snapshot = null;
|
|
18
|
+
function publish() {
|
|
19
|
+
snapshot = null;
|
|
20
|
+
for (const listener of listeners)
|
|
21
|
+
listener();
|
|
22
|
+
}
|
|
23
|
+
subscribeDraftChanges(publish);
|
|
24
|
+
subscribeSketchStore(publish);
|
|
25
|
+
function storageKey() { return `nudge-ui-drafts:${projectId}:v2`; }
|
|
26
|
+
export function draftIdForContent(content) {
|
|
27
|
+
let id = contentDrafts.get(content);
|
|
28
|
+
if (id === undefined) {
|
|
29
|
+
id = targetKey(contentEditTarget(content));
|
|
30
|
+
contentDrafts.set(content, id);
|
|
31
|
+
}
|
|
32
|
+
return id;
|
|
33
|
+
}
|
|
34
|
+
export function draftIdForCard(cardId) {
|
|
35
|
+
const card = getCanvasCards().find((candidate) => candidate.id === cardId);
|
|
36
|
+
return card ? draftIdForContent(card.content) : null;
|
|
37
|
+
}
|
|
38
|
+
export function sketchOwner(document) {
|
|
39
|
+
const owner = sketchOwners.get(document.id);
|
|
40
|
+
if (owner)
|
|
41
|
+
return owner;
|
|
42
|
+
try {
|
|
43
|
+
return targetKey(documentTarget(document.capture.url));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function readDraft(id, target) {
|
|
50
|
+
const workspace = getDraftChanges(id);
|
|
51
|
+
return {
|
|
52
|
+
id,
|
|
53
|
+
target,
|
|
54
|
+
revision: workspace.revision,
|
|
55
|
+
contents: { changes: workspace.changes, structuralChanges: workspace.structuralChanges },
|
|
56
|
+
sketchIds: getSketchStoreSnapshot().documents.filter((document) => sketchOwner(document) === id).map((document) => document.id),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function getDraft(id) {
|
|
60
|
+
const target = targetFromKey(id);
|
|
61
|
+
return target ? readDraft(id, target) : null;
|
|
62
|
+
}
|
|
63
|
+
export function getDraftForCard(cardId) {
|
|
64
|
+
const id = draftIdForCard(cardId);
|
|
65
|
+
return id ? getDraft(id) : null;
|
|
66
|
+
}
|
|
67
|
+
export function getDraftContentsForCard(cardId) {
|
|
68
|
+
const draft = getDraftForCard(cardId);
|
|
69
|
+
return draft ? { changes: draft.contents.changes, structuralChanges: draft.contents.structuralChanges } : null;
|
|
70
|
+
}
|
|
71
|
+
export function persistDrafts() {
|
|
72
|
+
if (!projectId || !persistent || !canWriteWorkspace())
|
|
73
|
+
return;
|
|
74
|
+
const sketches = getSketchStoreSnapshot();
|
|
75
|
+
const liveSketches = new Set(sketches.documents.map((document) => document.id));
|
|
76
|
+
const value = {
|
|
77
|
+
schemaVersion: 2, projectId,
|
|
78
|
+
drafts: listDraftIds().flatMap((id) => {
|
|
79
|
+
const target = targetFromKey(id);
|
|
80
|
+
const workspace = getDraftChanges(id);
|
|
81
|
+
if (!target || workspace.changes.length + workspace.structuralChanges.length === 0)
|
|
82
|
+
return [];
|
|
83
|
+
return [{
|
|
84
|
+
target,
|
|
85
|
+
revision: workspace.revision,
|
|
86
|
+
contents: {
|
|
87
|
+
changes: workspace.changes.flatMap((change) => {
|
|
88
|
+
const serialized = serializeChange(change);
|
|
89
|
+
return serialized ? [serialized] : [];
|
|
90
|
+
}),
|
|
91
|
+
structuralChanges: workspace.structuralChanges,
|
|
92
|
+
},
|
|
93
|
+
}];
|
|
94
|
+
}),
|
|
95
|
+
sketchOwners: Object.fromEntries([...sketchOwners].filter(([id]) => !sketches.ready || liveSketches.has(id))),
|
|
96
|
+
};
|
|
97
|
+
try {
|
|
98
|
+
localStorage.setItem(storageKey(), JSON.stringify(value));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function validPersisted(value) {
|
|
105
|
+
if (!value || typeof value !== "object")
|
|
106
|
+
return false;
|
|
107
|
+
const candidate = value;
|
|
108
|
+
if (candidate.schemaVersion !== 2 || candidate.projectId !== projectId || !Array.isArray(candidate.drafts))
|
|
109
|
+
return false;
|
|
110
|
+
const targets = new Set();
|
|
111
|
+
for (const draft of candidate.drafts) {
|
|
112
|
+
if (!draft || !isDraftTarget(draft.target) || targets.has(targetKey(draft.target))
|
|
113
|
+
|| !Number.isSafeInteger(draft.revision) || draft.revision < 1
|
|
114
|
+
|| !draft.contents || !Array.isArray(draft.contents.changes) || !draft.contents.changes.every(isSerializableChange)
|
|
115
|
+
|| !Array.isArray(draft.contents.structuralChanges) || !draft.contents.structuralChanges.every(isStructuralChange))
|
|
116
|
+
return false;
|
|
117
|
+
const textIds = draft.contents.changes.filter((change) => change.kind === "text-content").map((change) => "id" in change ? change.id : "");
|
|
118
|
+
if (new Set(textIds).size !== textIds.length)
|
|
119
|
+
return false;
|
|
120
|
+
targets.add(targetKey(draft.target));
|
|
121
|
+
}
|
|
122
|
+
return !!candidate.sketchOwners && typeof candidate.sketchOwners === "object" && !Array.isArray(candidate.sketchOwners)
|
|
123
|
+
&& Object.values(candidate.sketchOwners).every((id) => typeof id === "string" && targetFromKey(id) !== null);
|
|
124
|
+
}
|
|
125
|
+
/** Replaces every in-memory draft with the stored drafts, so a tab that takes over never writes stale edits. */
|
|
126
|
+
export function loadDrafts(nextProjectId, options = {}) {
|
|
127
|
+
projectId = nextProjectId;
|
|
128
|
+
persistent = options.persistent ?? true;
|
|
129
|
+
sketchOwners.clear();
|
|
130
|
+
resetDraftChanges();
|
|
131
|
+
try {
|
|
132
|
+
const raw = persistent ? localStorage.getItem(storageKey()) : null;
|
|
133
|
+
const stored = raw ? JSON.parse(raw) : null;
|
|
134
|
+
if (validPersisted(stored)) {
|
|
135
|
+
for (const draft of stored.drafts) {
|
|
136
|
+
restoreDraftChanges(targetKey(draft.target), { changes: draft.contents.changes.map(deserializeChange), structuralChanges: draft.contents.structuralChanges }, draft.revision);
|
|
137
|
+
}
|
|
138
|
+
for (const [sketchId, draftId] of Object.entries(stored.sketchOwners))
|
|
139
|
+
sketchOwners.set(sketchId, draftId);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
resetDraftChanges();
|
|
144
|
+
sketchOwners.clear();
|
|
145
|
+
}
|
|
146
|
+
publish();
|
|
147
|
+
refreshDraftPreview();
|
|
148
|
+
}
|
|
149
|
+
export function activateDraftForCard(cardId) {
|
|
150
|
+
const draftId = draftIdForCard(cardId);
|
|
151
|
+
if (draftId)
|
|
152
|
+
activateDraft(draftId, cardId);
|
|
153
|
+
}
|
|
154
|
+
export function activateDraft(draftId, preferredCardId) {
|
|
155
|
+
const previous = getActiveDraftChanges().draftId;
|
|
156
|
+
setActiveDraft(draftId, () => revealDraft(draftId, preferredCardId));
|
|
157
|
+
if (previous !== draftId)
|
|
158
|
+
refreshDraftPreview();
|
|
159
|
+
}
|
|
160
|
+
function revealDraft(draftId, preferredCardId) {
|
|
161
|
+
const cards = getCanvasCards();
|
|
162
|
+
let frame = cards.find((card) => card.id === preferredCardId && draftIdForContent(card.content) === draftId)
|
|
163
|
+
?? cards.find((card) => draftIdForContent(card.content) === draftId);
|
|
164
|
+
const target = targetFromKey(draftId);
|
|
165
|
+
if (!frame && target?.kind === "application") {
|
|
166
|
+
const original = cards.find((card) => card.id === preferredCardId && card.content.kind === "route");
|
|
167
|
+
if (original) {
|
|
168
|
+
selectCard(original.id);
|
|
169
|
+
frame = activateIframeWorkspace(target.route, { width: window.innerWidth, height: window.innerHeight }, { replaceActiveCard: true }) ?? undefined;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (!frame)
|
|
173
|
+
return;
|
|
174
|
+
activateDraftForCard(frame.id);
|
|
175
|
+
selectCard(frame.id);
|
|
176
|
+
focusCanvasCards([frame.id]);
|
|
177
|
+
}
|
|
178
|
+
export function clearSavedIterationDraft(cardId, expectedRevision) {
|
|
179
|
+
const draftId = draftIdForCard(cardId);
|
|
180
|
+
return !!draftId && clearDraftChanges(draftId, expectedRevision);
|
|
181
|
+
}
|
|
182
|
+
/** Returns the first preferred frame that exists, then the first frame. */
|
|
183
|
+
export function getEditableCardId(...preferredCardIds) {
|
|
184
|
+
const cards = getCanvasCards();
|
|
185
|
+
return preferredCardIds.find((id) => !!id && cards.some((card) => card.id === id)) ?? cards[0]?.id ?? null;
|
|
186
|
+
}
|
|
187
|
+
export function assignSketchToDraft(draftId, sketchId) {
|
|
188
|
+
if (sketchOwners.get(sketchId) === draftId || !targetFromKey(draftId))
|
|
189
|
+
return;
|
|
190
|
+
sketchOwners.set(sketchId, draftId);
|
|
191
|
+
publish();
|
|
192
|
+
}
|
|
193
|
+
export function sketchBelongsToCard(document, cardId) {
|
|
194
|
+
if (!projectId || !cardId)
|
|
195
|
+
return true;
|
|
196
|
+
const draftId = draftIdForCard(cardId);
|
|
197
|
+
return !!draftId && sketchOwner(document) === draftId;
|
|
198
|
+
}
|
|
199
|
+
export function subscribeDrafts(listener) {
|
|
200
|
+
listeners.add(listener);
|
|
201
|
+
return () => listeners.delete(listener);
|
|
202
|
+
}
|
|
203
|
+
export function getDraftsSnapshot() {
|
|
204
|
+
if (!snapshot) {
|
|
205
|
+
const ids = new Set([...listDraftIds(), ...getSketchStoreSnapshot().documents.flatMap((document) => sketchOwner(document) ?? [])]);
|
|
206
|
+
snapshot = { projectId, drafts: [...ids].flatMap((id) => getDraft(id) ?? []) };
|
|
207
|
+
}
|
|
208
|
+
return snapshot;
|
|
209
|
+
}
|
|
210
|
+
export function useDrafts() { return useSyncExternalStore(subscribeDrafts, getDraftsSnapshot, getDraftsSnapshot); }
|
|
211
|
+
/** Discards stored and in-memory drafts for the current project. */
|
|
212
|
+
export function clearDrafts() {
|
|
213
|
+
if (projectId && persistent && canWriteWorkspace()) {
|
|
214
|
+
try {
|
|
215
|
+
localStorage.removeItem(storageKey());
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
sketchOwners.clear();
|
|
222
|
+
resetDraftChanges();
|
|
223
|
+
publish();
|
|
224
|
+
}
|
|
225
|
+
export function resetDrafts() {
|
|
226
|
+
projectId = "";
|
|
227
|
+
persistent = true;
|
|
228
|
+
sketchOwners.clear();
|
|
229
|
+
resetDraftChanges();
|
|
230
|
+
publish();
|
|
231
|
+
}
|
package/dist/inspector/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { startWorkspaceController } from "./workspace/controller.js";
|
|
1
2
|
import { createElement } from "react";
|
|
2
3
|
import { createRoot } from "react-dom/client";
|
|
3
4
|
import { InspectorShell, toggleInspector, setInspectorOpen } from "./shell/InspectorShell.js";
|
|
@@ -8,20 +9,19 @@ import { isCanvasRenderer } from "./canvas/roleDetection.js";
|
|
|
8
9
|
import { isKeyboardShortcutMessage } from "./canvas/frameProtocol.js";
|
|
9
10
|
import { subscribeCanvasRendererMessages } from "./canvas/rendererMessageRouter.js";
|
|
10
11
|
import { bootstrapRenderer } from "./canvas/rendererBootstrap.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { acquireLease, enableWriteGuard, hasWriteLease, releaseLease, subscribeOwnership, } from "./canvas/workspaceLease.js";
|
|
12
|
+
import { setRestoreCount, } from "./canvas/sessionStore.js";
|
|
13
|
+
import { getChangesList } from "./changes/changesLog.js";
|
|
14
|
+
import { acquireLease, enableWriteGuard, hasWriteLease, subscribeOwnership, } from "./canvas/workspaceLease.js";
|
|
15
15
|
import { cancelStaleDetection, startStaleDetection } from "./canvas/staleChangeDetector.js";
|
|
16
16
|
import { LockedWorkspaceNotice } from "./canvas/LockedWorkspaceNotice.js";
|
|
17
17
|
import { AppShell } from "./shell/AppShell.js";
|
|
18
18
|
import { installInspectionBridge } from "./inspection/bridge.js";
|
|
19
|
-
import { releaseDocumentProjection,
|
|
19
|
+
import { releaseDocumentProjection, getStructuralChanges } from "./projection/structuralProjection.js";
|
|
20
20
|
import { cancelInlineTextEdit } from "./inline-text/inlineTextEditor.js";
|
|
21
21
|
import { configureNudgeUiRuntime, getNudgeUiRuntimeConfig, isDemoRuntime } from "./runtime/runtimeConfig.js";
|
|
22
|
-
import { setCanvasMode } from "./canvas/
|
|
22
|
+
import { setCanvasMode } from "./canvas/viewStore.js";
|
|
23
23
|
import { isNudgeUiDev, setNudgeUiHostDevFlag } from "./runtime/devFlag.js";
|
|
24
|
-
import { clearClipboardHandoff,
|
|
24
|
+
import { clearClipboardHandoff, } from "./prompt/clipboardHandoff.js";
|
|
25
25
|
import { createWorkspace, InspectorSessionProvider, } from "./session/index.js";
|
|
26
26
|
import { clearSketchClipboardHandoff } from "./sketch/handoff.js";
|
|
27
27
|
import { cancelSketchInteraction, isSketchInteractionActive } from "./sketch/interaction.js";
|
|
@@ -34,8 +34,7 @@ let lockedRoot = null;
|
|
|
34
34
|
const workspace = createWorkspace();
|
|
35
35
|
let inspectorSession = null;
|
|
36
36
|
let rendererBootstrapHandle = null;
|
|
37
|
-
let
|
|
38
|
-
let persistenceSubscribed = false;
|
|
37
|
+
let workspaceController = null;
|
|
39
38
|
let unsubscribeOwnership = null;
|
|
40
39
|
let removeInspectionBridge = null;
|
|
41
40
|
let keyboardPeekHeld = false;
|
|
@@ -105,6 +104,8 @@ export function bootstrapNudgeUi(inspectorHost) {
|
|
|
105
104
|
// The public demo shares the iframe editor while keeping the write lease,
|
|
106
105
|
// persistence, and agent bridge disabled.
|
|
107
106
|
setCanvasMode("canvas");
|
|
107
|
+
workspaceController?.dispose();
|
|
108
|
+
workspaceController = startWorkspaceController();
|
|
108
109
|
mountInspector(inspectorHost);
|
|
109
110
|
setInspectorOpen(false);
|
|
110
111
|
return;
|
|
@@ -142,12 +143,17 @@ function startController(inspectorHost) {
|
|
|
142
143
|
void initializeSketchStore(getNudgeUiRuntimeConfig().projectId).catch(() => undefined);
|
|
143
144
|
unsubscribeOwnership?.();
|
|
144
145
|
unsubscribeOwnership = subscribeOwnership((hasLease) => {
|
|
145
|
-
if (!hasLease)
|
|
146
|
+
if (!hasLease) {
|
|
147
|
+
workspaceController?.dispose();
|
|
148
|
+
workspaceController = null;
|
|
146
149
|
mountLockedNotice(inspectorHost);
|
|
150
|
+
}
|
|
147
151
|
});
|
|
148
|
-
|
|
152
|
+
workspaceController?.dispose();
|
|
153
|
+
const result = startWorkspaceController();
|
|
154
|
+
workspaceController = result;
|
|
149
155
|
if (result.restored) {
|
|
150
|
-
setRestoreCount(
|
|
156
|
+
setRestoreCount(getChangesList().length + getStructuralChanges().length);
|
|
151
157
|
const restored = getChangesList();
|
|
152
158
|
if (restored.length > 0) {
|
|
153
159
|
startStaleDetection(restored);
|
|
@@ -159,21 +165,6 @@ function startController(inspectorHost) {
|
|
|
159
165
|
setCanvasMode("canvas");
|
|
160
166
|
}
|
|
161
167
|
mountInspector(inspectorHost);
|
|
162
|
-
enableAutoSave();
|
|
163
|
-
if (!beforeUnloadAttached) {
|
|
164
|
-
// enableAutoSave registers its flush listener first. Release the lease only
|
|
165
|
-
// after the pending session write has had a chance to pass the ownership
|
|
166
|
-
// gate during beforeunload.
|
|
167
|
-
window.addEventListener("beforeunload", releaseLease);
|
|
168
|
-
beforeUnloadAttached = true;
|
|
169
|
-
}
|
|
170
|
-
if (!persistenceSubscribed) {
|
|
171
|
-
subscribeChanges(() => scheduleAutoSave());
|
|
172
|
-
subscribeStructuralChanges(() => scheduleAutoSave());
|
|
173
|
-
subscribeClipboardHandoff(() => scheduleAutoSave());
|
|
174
|
-
subscribeCanvas(() => scheduleCanvasSave());
|
|
175
|
-
persistenceSubscribed = true;
|
|
176
|
-
}
|
|
177
168
|
}
|
|
178
169
|
function mountLockedNotice(host) {
|
|
179
170
|
if (reactRoot) {
|
|
@@ -239,6 +230,8 @@ export function mountInspector(host) {
|
|
|
239
230
|
export function unmountInspector() {
|
|
240
231
|
rendererBootstrapHandle?.teardown();
|
|
241
232
|
rendererBootstrapHandle = null;
|
|
233
|
+
workspaceController?.dispose();
|
|
234
|
+
workspaceController = null;
|
|
242
235
|
unsubscribeOwnership?.();
|
|
243
236
|
unsubscribeOwnership = null;
|
|
244
237
|
if (lockedRoot) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { canEditDraft } from "../changes/draftChanges.js";
|
|
1
2
|
import { useSyncExternalStore } from "react";
|
|
2
3
|
import { appendChange } from "../changes/changesLog.js";
|
|
3
4
|
import { registerInlineTextClearHandler } from "./inlineTextLifecycle.js";
|
|
@@ -1029,6 +1030,9 @@ export const inlineTextEditor = {
|
|
|
1029
1030
|
/** Re-enter an existing canonical rendered-text projection through its empty
|
|
1030
1031
|
* inspector affordance. This never guesses a new DOM target or source node. */
|
|
1031
1032
|
export function beginInlineTextEditFromEmptyProjection(marker) {
|
|
1033
|
+
if (!canEditDraft()) {
|
|
1034
|
+
return { kind: "rejected", reason: "editing-active", message: "Wait for the iteration to finish saving before editing text." };
|
|
1035
|
+
}
|
|
1032
1036
|
if (!isNudgeUiDev()) {
|
|
1033
1037
|
return {
|
|
1034
1038
|
kind: "rejected",
|
|
@@ -1063,6 +1067,9 @@ export function beginInlineTextEditFromEmptyProjection(marker) {
|
|
|
1063
1067
|
}
|
|
1064
1068
|
}
|
|
1065
1069
|
export function beginInlineTextEdit(element, point) {
|
|
1070
|
+
if (!canEditDraft()) {
|
|
1071
|
+
return { kind: "rejected", reason: "editing-active", message: "Wait for the iteration to finish saving before editing text." };
|
|
1072
|
+
}
|
|
1066
1073
|
if (!isNudgeUiDev()) {
|
|
1067
1074
|
return {
|
|
1068
1075
|
kind: "rejected",
|
|
@@ -5,6 +5,12 @@ const EMPTY_SELECTION = [];
|
|
|
5
5
|
function isSelectedElementList(selected) {
|
|
6
6
|
return Array.isArray(selected);
|
|
7
7
|
}
|
|
8
|
+
const EMPTY_VIEW = {
|
|
9
|
+
element: null,
|
|
10
|
+
elements: [],
|
|
11
|
+
stableProperties: [],
|
|
12
|
+
documentTokens: null,
|
|
13
|
+
};
|
|
8
14
|
function normalizeSelection(selected) {
|
|
9
15
|
if (!selected)
|
|
10
16
|
return EMPTY_SELECTION;
|
|
@@ -22,6 +28,9 @@ function inspectStableView(session, selected, state, includeDocumentTokens, elem
|
|
|
22
28
|
: [];
|
|
23
29
|
const documentTokens = includeDocumentTokens ? session.inspectTokens() : null;
|
|
24
30
|
return {
|
|
31
|
+
selection: selected,
|
|
32
|
+
state,
|
|
33
|
+
session,
|
|
25
34
|
element: primary,
|
|
26
35
|
elements,
|
|
27
36
|
stableProperties,
|
|
@@ -155,5 +164,10 @@ export function useBrowserCssInspection(selected, state = "base", options = {})
|
|
|
155
164
|
const timeoutId = setTimeout(warm, 200);
|
|
156
165
|
return () => clearTimeout(timeoutId);
|
|
157
166
|
}, [session, selectedDocument]);
|
|
158
|
-
|
|
167
|
+
// Selection effects run after children render. Never expose the previous
|
|
168
|
+
// element's authored rows while the new selection is waiting for inspection.
|
|
169
|
+
const matchesSelection = view.session === session && view.state === state
|
|
170
|
+
&& view.selection.length === selectedElements.length
|
|
171
|
+
&& view.selection.every((element, index) => element.domElement === selectedElements[index]?.domElement);
|
|
172
|
+
return matchesSelection ? view : EMPTY_VIEW;
|
|
159
173
|
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { HTML_ARTIFACT_REVISION_ATTRIBUTE } from "../../transport/artifacts.js";
|
|
2
|
+
/** Captures the visible DOM of a same-origin frame as a script-free HTML iteration. */
|
|
3
|
+
export async function captureHtmlIteration(frame) {
|
|
4
|
+
const source = frame.contentDocument;
|
|
5
|
+
const view = frame.contentWindow;
|
|
6
|
+
if (!source || !view?.document.documentElement)
|
|
7
|
+
throw new Error("The frame is not ready to capture.");
|
|
8
|
+
await source.fonts?.ready;
|
|
9
|
+
await Promise.all([...source.images].map((image) => image.decode?.().catch(() => undefined)));
|
|
10
|
+
const original = source.documentElement;
|
|
11
|
+
const copy = original.cloneNode(true);
|
|
12
|
+
copy.removeAttribute(HTML_ARTIFACT_REVISION_ATTRIBUTE);
|
|
13
|
+
const sourceElements = [original, ...original.querySelectorAll("*")];
|
|
14
|
+
const copiedElements = [copy, ...copy.querySelectorAll("*")];
|
|
15
|
+
const assets = new Map();
|
|
16
|
+
for (let index = 0; index < sourceElements.length; index += 1) {
|
|
17
|
+
const sourceElement = sourceElements[index];
|
|
18
|
+
const copied = copiedElements[index];
|
|
19
|
+
if (sourceElement.nodeType !== 1 || copied.nodeType !== 1)
|
|
20
|
+
continue;
|
|
21
|
+
// Renderer IDs belong to the source iframe's node registry. The iteration's
|
|
22
|
+
// fresh registry can reuse those values for different nodes.
|
|
23
|
+
copied.removeAttribute("data-renderer-id");
|
|
24
|
+
// Preserve authored inline values (percentages, variables, and calc), rather
|
|
25
|
+
// than replacing them with computed dimensions at the capture viewport.
|
|
26
|
+
const inline = copied.getAttribute("style");
|
|
27
|
+
if (inline)
|
|
28
|
+
copied.setAttribute("style", await resolveCssUrls(inline, source.baseURI, assets));
|
|
29
|
+
if (sourceElement.tagName === "INPUT" && copied.tagName === "INPUT") {
|
|
30
|
+
const input = sourceElement;
|
|
31
|
+
copied.setAttribute("value", input.value);
|
|
32
|
+
if (input.checked)
|
|
33
|
+
copied.setAttribute("checked", "");
|
|
34
|
+
else
|
|
35
|
+
copied.removeAttribute("checked");
|
|
36
|
+
}
|
|
37
|
+
if (sourceElement.tagName === "TEXTAREA" && copied.tagName === "TEXTAREA")
|
|
38
|
+
copied.textContent = sourceElement.value;
|
|
39
|
+
if (sourceElement.tagName === "CANVAS" && copied.tagName === "CANVAS") {
|
|
40
|
+
try {
|
|
41
|
+
const image = source.createElement("img");
|
|
42
|
+
image.src = sourceElement.toDataURL();
|
|
43
|
+
for (const attribute of [...copied.attributes])
|
|
44
|
+
image.setAttribute(attribute.name, attribute.value);
|
|
45
|
+
copied.replaceWith(image);
|
|
46
|
+
}
|
|
47
|
+
catch { /* Tainted canvases remain empty. */ }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const base = source.createElement("base");
|
|
51
|
+
base.href = source.querySelector("base")?.href ?? source.location.href;
|
|
52
|
+
copy.querySelectorAll("base").forEach((element) => element.remove());
|
|
53
|
+
(copy.querySelector("head") ?? copy).prepend(base);
|
|
54
|
+
copy.querySelectorAll('script, iframe, object, embed, meta[http-equiv="refresh"], meta[http-equiv="Content-Security-Policy"], link[rel=modulepreload], link[rel=preload]').forEach((element) => element.remove());
|
|
55
|
+
copy.querySelectorAll("[data-nudge-ui-mount], #nudge-ui-root").forEach((element) => element.remove());
|
|
56
|
+
copy.querySelectorAll("a, form").forEach((element) => {
|
|
57
|
+
element.removeAttribute("href");
|
|
58
|
+
element.removeAttribute("action");
|
|
59
|
+
});
|
|
60
|
+
copy.querySelectorAll("*").forEach((element) => {
|
|
61
|
+
for (const attribute of [...element.attributes]) {
|
|
62
|
+
if (/^on/i.test(attribute.name))
|
|
63
|
+
element.removeAttribute(attribute.name);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
for (const image of copy.querySelectorAll("img")) {
|
|
67
|
+
const url = image.getAttribute("src");
|
|
68
|
+
if (url)
|
|
69
|
+
image.src = await inlineAsset(url, base.href, assets);
|
|
70
|
+
}
|
|
71
|
+
await captureStylesheets(source, copy, sourceElements, copiedElements, assets);
|
|
72
|
+
return `<!doctype html>\n${copy.outerHTML}`;
|
|
73
|
+
}
|
|
74
|
+
/** Copies live CSSOM rules in place so stylesheet order and media stay intact. */
|
|
75
|
+
async function captureStylesheets(document, copy, sourceElements, copiedElements, assets) {
|
|
76
|
+
for (const sheet of [...document.styleSheets]) {
|
|
77
|
+
const owner = sheet.ownerNode;
|
|
78
|
+
// Some DOM implementations do not expose ownerNode.
|
|
79
|
+
const sourceNode = owner ?? sourceElements.find((element) => "sheet" in element && element.sheet === sheet);
|
|
80
|
+
const index = sourceNode ? sourceElements.indexOf(sourceNode) : -1;
|
|
81
|
+
const copiedNode = index >= 0 ? copiedElements[index] : undefined;
|
|
82
|
+
if (!copiedNode?.parentNode)
|
|
83
|
+
continue;
|
|
84
|
+
if (sheet.disabled) {
|
|
85
|
+
copiedNode.remove();
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
let css;
|
|
89
|
+
try {
|
|
90
|
+
css = [...sheet.cssRules].map((rule) => rule.cssText).join("\n");
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
if (!sheet.href)
|
|
94
|
+
continue;
|
|
95
|
+
try {
|
|
96
|
+
const response = await fetch(sheet.href);
|
|
97
|
+
if (!response.ok)
|
|
98
|
+
continue;
|
|
99
|
+
css = await response.text();
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Keep the stylesheet link when CORS prevents embedding it. Its URL
|
|
103
|
+
// still resolves against the original base and retains responsiveness.
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const style = document.createElement("style");
|
|
108
|
+
for (const attribute of [...copiedNode.attributes]) {
|
|
109
|
+
if (!["href", "rel", "integrity", "crossorigin"].includes(attribute.name)) {
|
|
110
|
+
style.setAttribute(attribute.name, attribute.value);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
style.textContent = await resolveCssUrls(css, sheet.href ?? document.baseURI, assets);
|
|
114
|
+
// Captured edits become the iteration's base. The renderer must create its own
|
|
115
|
+
// managed stylesheet for new edits instead of clearing these captured rules.
|
|
116
|
+
if (style.id === "nudge-ui-styles") {
|
|
117
|
+
style.removeAttribute("id");
|
|
118
|
+
style.removeAttribute("data-nudge-ui");
|
|
119
|
+
style.setAttribute("data-nudge-capture-overrides", "");
|
|
120
|
+
}
|
|
121
|
+
copiedNode.replaceWith(style);
|
|
122
|
+
}
|
|
123
|
+
for (const sheet of document.adoptedStyleSheets ?? []) {
|
|
124
|
+
if (sheet.disabled)
|
|
125
|
+
continue;
|
|
126
|
+
const style = document.createElement("style");
|
|
127
|
+
style.textContent = await resolveCssUrls([...sheet.cssRules].map((rule) => rule.cssText).join("\n"), document.baseURI, assets);
|
|
128
|
+
(copy.querySelector("head") ?? copy).append(style);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function resolveCssUrls(css, base, assets) {
|
|
132
|
+
const matches = [...css.matchAll(/url\(\s*(["']?)([^)"']+)\1\s*\)/g)];
|
|
133
|
+
const values = await Promise.all(matches.map((match) => inlineAsset(match[2], base, assets)));
|
|
134
|
+
let index = 0;
|
|
135
|
+
return css.replace(/url\(\s*(["']?)([^)"']+)\1\s*\)/g, () => `url("${values[index++]}")`);
|
|
136
|
+
}
|
|
137
|
+
async function inlineAsset(path, base, assets) {
|
|
138
|
+
if (path.trim().startsWith("#"))
|
|
139
|
+
return path;
|
|
140
|
+
let url;
|
|
141
|
+
try {
|
|
142
|
+
url = new URL(path, base).href;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return path;
|
|
146
|
+
}
|
|
147
|
+
if (!/^https?:/.test(url))
|
|
148
|
+
return url;
|
|
149
|
+
const existing = assets.get(url);
|
|
150
|
+
if (existing)
|
|
151
|
+
return existing;
|
|
152
|
+
const value = (async () => {
|
|
153
|
+
try {
|
|
154
|
+
const response = await fetch(url);
|
|
155
|
+
if (!response.ok || Number(response.headers.get("content-length")) > 4_000_000)
|
|
156
|
+
return url;
|
|
157
|
+
const blob = await response.blob();
|
|
158
|
+
if (blob.size > 4_000_000)
|
|
159
|
+
return url;
|
|
160
|
+
const bytes = new Uint8Array(await blob.arrayBuffer());
|
|
161
|
+
let binary = "";
|
|
162
|
+
for (let offset = 0; offset < bytes.length; offset += 8192) {
|
|
163
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + 8192));
|
|
164
|
+
}
|
|
165
|
+
return `data:${blob.type || "application/octet-stream"};base64,${btoa(binary)}`;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return url;
|
|
169
|
+
}
|
|
170
|
+
})();
|
|
171
|
+
assets.set(url, value);
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type HtmlArtifactRevision } from "../../transport/artifacts.js";
|
|
2
|
+
export declare function artifactDocumentUrl(id: string): string;
|
|
3
|
+
export declare function readHtmlArtifactRevision(id: string): Promise<HtmlArtifactRevision>;
|
|
4
|
+
/** Checks the editing base before saving and promoting a captured document. */
|
|
5
|
+
export declare function commitHtmlArtifact(id: string, expected: HtmlArtifactRevision, html?: string): Promise<HtmlArtifactRevision>;
|
|
6
|
+
export declare function createHtmlArtifact(frame: HTMLIFrameElement, sourceUrl: string, title: string, capturedHtml?: string): Promise<string>;
|
|
7
|
+
export declare function removeHtmlArtifact(id: string): Promise<void>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { isHtmlArtifactRevision, HTML_ITERATION_CONFLICT } from "../../transport/artifacts.js";
|
|
2
|
+
import { NUDGE_UI_ARTIFACTS_PATH } from "../../transport/routes.js";
|
|
3
|
+
import { captureHtmlIteration } from "./capture.js";
|
|
4
|
+
export function artifactDocumentUrl(id) {
|
|
5
|
+
return `${NUDGE_UI_ARTIFACTS_PATH}/${id}/preview`;
|
|
6
|
+
}
|
|
7
|
+
export async function readHtmlArtifactRevision(id) {
|
|
8
|
+
const response = await fetch(`${NUDGE_UI_ARTIFACTS_PATH}/${id}/revision`, { cache: "no-store", signal: AbortSignal.timeout(10000) });
|
|
9
|
+
if (!response.ok)
|
|
10
|
+
throw new Error("The HTML iteration could not be refreshed.");
|
|
11
|
+
const revision = await response.json();
|
|
12
|
+
if (!isHtmlArtifactRevision(revision))
|
|
13
|
+
throw new Error("The HTML iteration returned an invalid revision.");
|
|
14
|
+
return revision;
|
|
15
|
+
}
|
|
16
|
+
/** Checks the editing base before saving and promoting a captured document. */
|
|
17
|
+
export async function commitHtmlArtifact(id, expected, html) {
|
|
18
|
+
const response = await fetch(`${NUDGE_UI_ARTIFACTS_PATH}/${id}/commit`, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
signal: AbortSignal.timeout(10000),
|
|
21
|
+
headers: { "content-type": "application/json" },
|
|
22
|
+
body: JSON.stringify({ expected, ...(html === undefined ? {} : { html }) }),
|
|
23
|
+
});
|
|
24
|
+
if (response.status === 409)
|
|
25
|
+
throw new Error(HTML_ITERATION_CONFLICT);
|
|
26
|
+
if (!response.ok)
|
|
27
|
+
throw new Error((await response.text()) || "The HTML iteration could not be saved.");
|
|
28
|
+
const revision = await response.json();
|
|
29
|
+
if (!isHtmlArtifactRevision(revision))
|
|
30
|
+
throw new Error("The HTML iteration returned an invalid revision.");
|
|
31
|
+
return revision;
|
|
32
|
+
}
|
|
33
|
+
export async function createHtmlArtifact(frame, sourceUrl, title, capturedHtml) {
|
|
34
|
+
const id = crypto.randomUUID();
|
|
35
|
+
const html = capturedHtml ?? await captureHtmlIteration(frame);
|
|
36
|
+
const response = await fetch(NUDGE_UI_ARTIFACTS_PATH, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: { "content-type": "application/json" },
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
id, html, sourceUrl, title,
|
|
41
|
+
viewport: { width: frame.clientWidth, height: frame.clientHeight },
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
if (!response.ok)
|
|
45
|
+
throw new Error((await response.text()) || "The HTML iteration could not be saved.");
|
|
46
|
+
return id;
|
|
47
|
+
}
|
|
48
|
+
export async function removeHtmlArtifact(id) {
|
|
49
|
+
const response = await fetch(`${NUDGE_UI_ARTIFACTS_PATH}/${id}`, { method: "DELETE", keepalive: true });
|
|
50
|
+
if (!response.ok)
|
|
51
|
+
throw new Error((await response.text()) || "The HTML iteration could not be removed.");
|
|
52
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HtmlArtifactRevision } from "../../transport/artifacts.js";
|
|
2
|
+
import { type DraftContents } from "../changes/draftChanges.js";
|
|
3
|
+
/** Owned transport; tests use a working in-memory artifact store. */
|
|
4
|
+
export interface HtmlIterationStorage {
|
|
5
|
+
readRevision(id: string): Promise<HtmlArtifactRevision>;
|
|
6
|
+
commit(id: string, expected: HtmlArtifactRevision, html?: string): Promise<HtmlArtifactRevision>;
|
|
7
|
+
}
|
|
8
|
+
/** Owns save ordering, draft settlement, and external document promotion. */
|
|
9
|
+
export declare function createHtmlIterationCoordinator(storage: HtmlIterationStorage): {
|
|
10
|
+
prepare(cardId: string, artifactId: string, baseRevision: string, capture: () => Promise<string>): Promise<DraftContents>;
|
|
11
|
+
refresh(cardId: string, artifactId: string, baseRevision: string, available: () => boolean): Promise<boolean>;
|
|
12
|
+
};
|
|
13
|
+
/** Materializes pending intent before the agent can start editing document.html. */
|
|
14
|
+
export declare function prepareHtmlIterationHandoff(cardId: string, artifactId: string, frame: HTMLIFrameElement): Promise<DraftContents>;
|
|
15
|
+
export declare function refreshHtmlIteration(cardId: string, artifactId: string, frame: HTMLIFrameElement): Promise<boolean>;
|