nudge-ui 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/nudge-ui.mjs +3 -1
- package/dist/cli/agentCommand.d.ts +8 -0
- package/dist/cli/agentCommand.js +59 -0
- package/dist/client.mjs +872 -12
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
- package/dist/hosts/astro/clientTransport.js +60 -11
- package/dist/hosts/next/sidecar.d.ts +4 -0
- package/dist/hosts/next/sidecar.js +49 -6
- package/dist/hosts/next/wrapper.js +30 -2
- package/dist/hosts/projectBridge.d.ts +20 -0
- package/dist/hosts/projectBridge.js +97 -0
- package/dist/hosts/static/cli.d.ts +1 -1
- package/dist/hosts/static/cli.js +6 -1
- package/dist/hosts/static/manifest.d.ts +1 -1
- package/dist/hosts/static/manifest.js +1 -1
- package/dist/hosts/static/server.js +44 -3
- package/dist/hosts/vite/vite.d.ts +5 -1
- package/dist/hosts/vite/vite.js +78 -14
- package/dist/inspector/agent/McpConnectionDialog.d.ts +2 -2
- package/dist/inspector/agent/McpConnectionDialog.js +11 -35
- package/dist/inspector/agent/client.d.ts +10 -3
- package/dist/inspector/agent/client.js +80 -9
- package/dist/inspector/agent/connectionStatus.js +3 -3
- package/dist/inspector/agent/httpTransport.d.ts +2 -0
- package/dist/inspector/agent/httpTransport.js +37 -17
- package/dist/inspector/agent/protocol.d.ts +8 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +1 -1
- package/dist/inspector/agent/verification.js +69 -12
- package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
- package/dist/inspector/canvas/CanvasCard.js +181 -91
- package/dist/inspector/canvas/CanvasElementOverlay.js +246 -52
- package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
- package/dist/inspector/canvas/CanvasToolbar.js +24 -0
- package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
- package/dist/inspector/canvas/CanvasWorkspace.js +392 -73
- package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
- package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
- package/dist/inspector/canvas/canvasResize.d.ts +30 -0
- package/dist/inspector/canvas/canvasResize.js +52 -0
- package/dist/inspector/canvas/canvasStore.d.ts +30 -6
- package/dist/inspector/canvas/canvasStore.js +128 -61
- package/dist/inspector/canvas/frameProtocol.d.ts +69 -5
- package/dist/inspector/canvas/frameProtocol.js +56 -2
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -0
- package/dist/inspector/canvas/keyboardShortcuts.js +14 -0
- package/dist/inspector/canvas/projection.d.ts +7 -0
- package/dist/inspector/canvas/projection.js +118 -25
- package/dist/inspector/canvas/rendererBootstrap.js +29 -29
- package/dist/inspector/canvas/rendererElementSelector.js +341 -30
- package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
- package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
- package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
- package/dist/inspector/canvas/sessionStore.d.ts +1 -0
- package/dist/inspector/canvas/sessionStore.js +12 -4
- package/dist/inspector/canvas/staleChangeDetector.js +16 -14
- package/dist/inspector/changes/changesLog.js +31 -1
- package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
- package/dist/inspector/changes/managedStyleProjection.js +3 -3
- package/dist/inspector/changes/structuralTypes.d.ts +4 -0
- package/dist/inspector/clientManifest.d.ts +5 -0
- package/dist/inspector/clientManifest.js +11 -0
- package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
- package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
- package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
- package/dist/inspector/index.d.ts +2 -0
- package/dist/inspector/index.js +74 -24
- package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
- package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
- package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
- package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
- package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
- package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
- package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
- package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
- package/dist/inspector/inline-text/textEditChange.js +165 -0
- package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
- package/dist/inspector/inspection/selectionProperty.js +1 -0
- package/dist/inspector/overlay/selectedGeometry.js +21 -2
- package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
- package/dist/inspector/overlay/spacingGestures.js +353 -0
- package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
- package/dist/inspector/overlay/structuralGestures.js +12 -10
- package/dist/inspector/projection/managedStylesheet.js +4 -1
- package/dist/inspector/projection/renderedInstance.d.ts +2 -0
- package/dist/inspector/projection/renderedInstance.js +5 -1
- package/dist/inspector/projection/structuralProjection.d.ts +2 -0
- package/dist/inspector/projection/structuralProjection.js +85 -1
- package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
- package/dist/inspector/projection/textProjection.d.ts +2 -0
- package/dist/inspector/projection/textProjection.js +10 -4
- package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
- package/dist/inspector/prompt/clipboardHandoff.js +3 -3
- package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
- package/dist/inspector/prompt/generatePrompt.js +19 -2
- package/dist/inspector/runtime/editorShell.d.ts +2 -0
- package/dist/inspector/runtime/editorShell.js +4 -0
- package/dist/inspector/runtime/runtimeConfig.d.ts +4 -0
- package/dist/inspector/runtime/runtimeConfig.js +4 -0
- package/dist/inspector/selection/selectionStore.js +2 -0
- package/dist/inspector/settings/SettingsDialog.js +10 -0
- package/dist/inspector/shell/AppShell.js +5 -1
- package/dist/inspector/shell/ChangesLog.js +8 -4
- package/dist/inspector/shell/CopyPromptButton.js +95 -19
- package/dist/inspector/shell/EmptyState.js +1 -1
- package/dist/inspector/shell/InspectorShell.d.ts +0 -1
- package/dist/inspector/shell/InspectorShell.js +37 -38
- package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalButton.js +57 -0
- package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
- package/dist/inspector/shell/originalPreview.d.ts +9 -0
- package/dist/inspector/shell/originalPreview.js +61 -0
- package/dist/inspector/shell/shortcuts.d.ts +3 -0
- package/dist/inspector/shell/shortcuts.js +15 -0
- package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
- package/dist/inspector/sketch/SketchChanges.js +72 -0
- package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
- package/dist/inspector/sketch/SketchEditor.js +402 -0
- package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
- package/dist/inspector/sketch/SketchEntryButton.js +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
- package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
- package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
- package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
- package/dist/inspector/sketch/SketchOverlay.js +420 -0
- package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
- package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
- package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
- package/dist/inspector/sketch/SketchWorkspace.js +152 -0
- package/dist/inspector/sketch/capture.d.ts +55 -0
- package/dist/inspector/sketch/capture.js +350 -0
- package/dist/inspector/sketch/freehand.d.ts +13 -0
- package/dist/inspector/sketch/freehand.js +64 -0
- package/dist/inspector/sketch/geometry.d.ts +17 -0
- package/dist/inspector/sketch/geometry.js +98 -0
- package/dist/inspector/sketch/handoff.d.ts +21 -0
- package/dist/inspector/sketch/handoff.js +72 -0
- package/dist/inspector/sketch/interaction.d.ts +21 -0
- package/dist/inspector/sketch/interaction.js +98 -0
- package/dist/inspector/sketch/model.d.ts +104 -0
- package/dist/inspector/sketch/model.js +282 -0
- package/dist/inspector/sketch/persistence.d.ts +21 -0
- package/dist/inspector/sketch/persistence.js +205 -0
- package/dist/inspector/sketch/raster.d.ts +22 -0
- package/dist/inspector/sketch/raster.js +203 -0
- package/dist/inspector/sketch/sketchNote.d.ts +10 -0
- package/dist/inspector/sketch/sketchNote.js +30 -0
- package/dist/inspector/sketch/store.d.ts +47 -0
- package/dist/inspector/sketch/store.js +409 -0
- package/dist/inspector/styleEditors/BorderEditor.js +15 -19
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
- package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
- package/dist/inspector/styleEditors/ColorPicker.js +11 -36
- package/dist/inspector/styleEditors/GapField.d.ts +3 -2
- package/dist/inspector/styleEditors/GapField.js +2 -2
- package/dist/inspector/styleEditors/GridSection.js +1 -1
- package/dist/inspector/styleEditors/LayoutSection.js +11 -8
- package/dist/inspector/styleEditors/SpacingBox.js +23 -30
- package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
- package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
- package/dist/inspector/styleEditors/layoutValue.js +3 -0
- package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
- package/dist/inspector/styleEditors/nudgeValue.js +91 -5
- package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
- package/dist/inspector/styleEditors/stylePresence.js +22 -0
- package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
- package/dist/inspector/styleEditors/textRelevance.js +35 -0
- package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
- package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
- package/dist/inspector/tokens/TokenField.js +111 -7
- package/dist/inspector/tokens/editActions.d.ts +2 -0
- package/dist/inspector/tokens/editActions.js +2 -2
- package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
- package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
- package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
- package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
- package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
- package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
- package/dist/inspector/tokens/resolution.d.ts +3 -26
- package/dist/inspector/tokens/resolution.js +44 -735
- package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
- package/dist/inspector/tokens/tokenAvailability.js +204 -0
- package/dist/inspector/ui/Button.d.ts +1 -1
- package/dist/inspector/ui/SideValuesField.js +2 -2
- package/dist/inspector/ui/StatusCallout.d.ts +4 -1
- package/dist/inspector/ui/StatusCallout.js +2 -2
- package/dist/inspector/ui/styles.js +13 -11
- package/dist/internal/agent-protocol.d.ts +65 -1
- package/dist/internal/agent-protocol.js +183 -3
- package/dist/nudge-ui.mjs +261 -57
- package/dist/transport/editor.d.ts +28 -0
- package/dist/transport/editor.js +132 -0
- package/dist/transport/index.d.ts +1 -0
- package/dist/transport/index.js +1 -0
- package/dist/transport/routes.d.ts +7 -0
- package/dist/transport/routes.js +7 -0
- package/package.json +3 -1
- package/dist/inspector/shell/panelLayout.d.ts +0 -2
- package/dist/inspector/shell/panelLayout.js +0 -29
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
export const SKETCH_SCHEMA_VERSION = 1;
|
|
2
|
+
export const SKETCH_DATABASE_NAME = "nudge-ui-sketches";
|
|
3
|
+
export const SKETCH_DATABASE_VERSION = 1;
|
|
4
|
+
export const SKETCH_DOCUMENTS_STORE = "documents";
|
|
5
|
+
export const SKETCH_HANDOFFS_STORE = "handoffs";
|
|
6
|
+
export const SKETCH_LIMITS = {
|
|
7
|
+
completedSketches: 4,
|
|
8
|
+
imageEdge: 2_048,
|
|
9
|
+
imageBytes: 2 * 1024 * 1024,
|
|
10
|
+
projectBytes: 20 * 1024 * 1024,
|
|
11
|
+
strokes: 500,
|
|
12
|
+
points: 20_000,
|
|
13
|
+
annotations: 50,
|
|
14
|
+
description: 2_000,
|
|
15
|
+
url: 2_048,
|
|
16
|
+
title: 512,
|
|
17
|
+
};
|
|
18
|
+
export const SKETCH_STROKE_WIDTH = 6;
|
|
19
|
+
export const SKETCH_STROKE_COLOR = "#0096ff";
|
|
20
|
+
export const SKETCH_STROKE_OUTLINE = "";
|
|
21
|
+
export const SKETCH_ANNOTATION_RADIUS = 16;
|
|
22
|
+
// Keep accepting sketches saved before the annotation color update. Existing
|
|
23
|
+
// PNGs remain unchanged until a user edits and saves them again.
|
|
24
|
+
const LEGACY_SKETCH_STROKE_COLOR = "#ef4444";
|
|
25
|
+
const LEGACY_SKETCH_STROKE_OUTLINE = "#111827";
|
|
26
|
+
export function createSketchFilename(id, revision) {
|
|
27
|
+
return `sketch-${id}-r${revision}.png`;
|
|
28
|
+
}
|
|
29
|
+
export function isFiniteNumber(value) {
|
|
30
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
31
|
+
}
|
|
32
|
+
function boundedString(value, maximum, allowEmpty = false) {
|
|
33
|
+
return typeof value === "string" && value.length <= maximum && (allowEmpty || value.length > 0);
|
|
34
|
+
}
|
|
35
|
+
function isBlob(value, mimeType) {
|
|
36
|
+
return typeof Blob !== "undefined"
|
|
37
|
+
&& value instanceof Blob
|
|
38
|
+
&& (mimeType === undefined || value.type === mimeType);
|
|
39
|
+
}
|
|
40
|
+
function validHttpUrl(value, maximum) {
|
|
41
|
+
if (!boundedString(value, maximum))
|
|
42
|
+
return false;
|
|
43
|
+
try {
|
|
44
|
+
const url = new URL(value);
|
|
45
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function isSketchCaptureMetadata(value) {
|
|
52
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
53
|
+
return false;
|
|
54
|
+
const capture = value;
|
|
55
|
+
const keys = [
|
|
56
|
+
"url",
|
|
57
|
+
"title",
|
|
58
|
+
"timestamp",
|
|
59
|
+
"viewportWidth",
|
|
60
|
+
"viewportHeight",
|
|
61
|
+
"scrollX",
|
|
62
|
+
"scrollY",
|
|
63
|
+
"devicePixelRatio",
|
|
64
|
+
"host",
|
|
65
|
+
"framework",
|
|
66
|
+
"imageWidth",
|
|
67
|
+
"imageHeight",
|
|
68
|
+
];
|
|
69
|
+
if (!Object.keys(capture).every((key) => keys.includes(key)))
|
|
70
|
+
return false;
|
|
71
|
+
return validHttpUrl(capture.url, SKETCH_LIMITS.url)
|
|
72
|
+
&& boundedString(capture.title, SKETCH_LIMITS.title, true)
|
|
73
|
+
&& Number.isSafeInteger(capture.timestamp)
|
|
74
|
+
&& capture.timestamp > 0
|
|
75
|
+
&& Number.isSafeInteger(capture.viewportWidth)
|
|
76
|
+
&& capture.viewportWidth > 0
|
|
77
|
+
&& capture.viewportWidth <= 100_000
|
|
78
|
+
&& Number.isSafeInteger(capture.viewportHeight)
|
|
79
|
+
&& capture.viewportHeight > 0
|
|
80
|
+
&& capture.viewportHeight <= 100_000
|
|
81
|
+
&& isFiniteNumber(capture.scrollX)
|
|
82
|
+
&& capture.scrollX >= 0
|
|
83
|
+
&& isFiniteNumber(capture.scrollY)
|
|
84
|
+
&& capture.scrollY >= 0
|
|
85
|
+
&& isFiniteNumber(capture.devicePixelRatio)
|
|
86
|
+
&& capture.devicePixelRatio > 0
|
|
87
|
+
&& capture.devicePixelRatio <= 100
|
|
88
|
+
&& boundedString(capture.host, 128)
|
|
89
|
+
&& boundedString(capture.framework, 128)
|
|
90
|
+
&& Number.isSafeInteger(capture.imageWidth)
|
|
91
|
+
&& capture.imageWidth > 0
|
|
92
|
+
&& capture.imageWidth <= SKETCH_LIMITS.imageEdge
|
|
93
|
+
&& Number.isSafeInteger(capture.imageHeight)
|
|
94
|
+
&& capture.imageHeight > 0
|
|
95
|
+
&& capture.imageHeight <= SKETCH_LIMITS.imageEdge;
|
|
96
|
+
}
|
|
97
|
+
export function isSketchPoint(value) {
|
|
98
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
99
|
+
return false;
|
|
100
|
+
const point = value;
|
|
101
|
+
return Object.keys(point).every((key) => key === "x" || key === "y")
|
|
102
|
+
&& isFiniteNumber(point.x)
|
|
103
|
+
&& isFiniteNumber(point.y)
|
|
104
|
+
&& point.x >= 0
|
|
105
|
+
&& point.x <= SKETCH_LIMITS.imageEdge
|
|
106
|
+
&& point.y >= 0
|
|
107
|
+
&& point.y <= SKETCH_LIMITS.imageEdge;
|
|
108
|
+
}
|
|
109
|
+
export function isSketchAnnotation(value) {
|
|
110
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
111
|
+
return false;
|
|
112
|
+
const annotation = value;
|
|
113
|
+
return Object.keys(annotation).every((key) => ["id", "number", "point", "description"].includes(key))
|
|
114
|
+
&& boundedString(annotation.id, 256)
|
|
115
|
+
&& Number.isSafeInteger(annotation.number)
|
|
116
|
+
&& annotation.number >= 1
|
|
117
|
+
&& annotation.number <= SKETCH_LIMITS.annotations
|
|
118
|
+
&& isSketchPoint(annotation.point)
|
|
119
|
+
&& boundedString(annotation.description, SKETCH_LIMITS.description);
|
|
120
|
+
}
|
|
121
|
+
export function isSketchStroke(value) {
|
|
122
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
123
|
+
return false;
|
|
124
|
+
const stroke = value;
|
|
125
|
+
return Object.keys(stroke).every((key) => ["id", "kind", "width", "color", "outlineColor", "points"].includes(key))
|
|
126
|
+
&& boundedString(stroke.id, 256)
|
|
127
|
+
&& (stroke.kind === undefined || stroke.kind === "freehand" || stroke.kind === "rectangle")
|
|
128
|
+
&& isFiniteNumber(stroke.width)
|
|
129
|
+
&& stroke.width > 0
|
|
130
|
+
&& stroke.width <= 128
|
|
131
|
+
&& (stroke.color === SKETCH_STROKE_COLOR || stroke.color === LEGACY_SKETCH_STROKE_COLOR)
|
|
132
|
+
&& (stroke.outlineColor === SKETCH_STROKE_OUTLINE || stroke.outlineColor === LEGACY_SKETCH_STROKE_OUTLINE)
|
|
133
|
+
&& Array.isArray(stroke.points)
|
|
134
|
+
&& stroke.points.length > 0
|
|
135
|
+
&& stroke.points.length <= SKETCH_LIMITS.points
|
|
136
|
+
&& stroke.points.every(isSketchPoint);
|
|
137
|
+
}
|
|
138
|
+
export function isSketchDocument(value) {
|
|
139
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
140
|
+
return false;
|
|
141
|
+
const document = value;
|
|
142
|
+
return Object.keys(document).every((key) => [
|
|
143
|
+
"schemaVersion",
|
|
144
|
+
"projectId",
|
|
145
|
+
"id",
|
|
146
|
+
"revision",
|
|
147
|
+
"createdAt",
|
|
148
|
+
"updatedAt",
|
|
149
|
+
"capture",
|
|
150
|
+
"description",
|
|
151
|
+
"strokes",
|
|
152
|
+
"annotations",
|
|
153
|
+
"imageWidth",
|
|
154
|
+
"imageHeight",
|
|
155
|
+
"originalByteSize",
|
|
156
|
+
"annotatedByteSize",
|
|
157
|
+
"filename",
|
|
158
|
+
"originalImage",
|
|
159
|
+
"annotatedImage",
|
|
160
|
+
].includes(key))
|
|
161
|
+
&& document.schemaVersion === SKETCH_SCHEMA_VERSION
|
|
162
|
+
&& boundedString(document.projectId, 256)
|
|
163
|
+
&& boundedString(document.id, 256)
|
|
164
|
+
&& Number.isSafeInteger(document.revision)
|
|
165
|
+
&& document.revision > 0
|
|
166
|
+
&& Number.isSafeInteger(document.createdAt)
|
|
167
|
+
&& Number.isSafeInteger(document.updatedAt)
|
|
168
|
+
&& isSketchCaptureMetadata(document.capture)
|
|
169
|
+
&& boundedString(document.description, SKETCH_LIMITS.description, true)
|
|
170
|
+
&& Array.isArray(document.strokes)
|
|
171
|
+
&& document.strokes.length <= SKETCH_LIMITS.strokes
|
|
172
|
+
&& document.strokes.every(isSketchStroke)
|
|
173
|
+
&& document.strokes.reduce((total, stroke) => total + stroke.points.length, 0) <= SKETCH_LIMITS.points
|
|
174
|
+
&& (document.annotations === undefined
|
|
175
|
+
|| (Array.isArray(document.annotations)
|
|
176
|
+
&& document.annotations.length <= SKETCH_LIMITS.annotations
|
|
177
|
+
&& document.annotations.every(isSketchAnnotation)
|
|
178
|
+
&& new Set(document.annotations.map((annotation) => annotation.number)).size === document.annotations.length))
|
|
179
|
+
&& document.imageWidth === document.capture.imageWidth
|
|
180
|
+
&& document.imageHeight === document.capture.imageHeight
|
|
181
|
+
&& Number.isSafeInteger(document.originalByteSize)
|
|
182
|
+
&& document.originalByteSize > 0
|
|
183
|
+
&& document.originalByteSize <= SKETCH_LIMITS.imageBytes
|
|
184
|
+
&& Number.isSafeInteger(document.annotatedByteSize)
|
|
185
|
+
&& document.annotatedByteSize > 0
|
|
186
|
+
&& document.annotatedByteSize <= SKETCH_LIMITS.imageBytes
|
|
187
|
+
&& boundedString(document.filename, 256)
|
|
188
|
+
&& isBlob(document.originalImage, "image/png")
|
|
189
|
+
&& isBlob(document.annotatedImage, "image/png")
|
|
190
|
+
&& document.originalImage.size === document.originalByteSize
|
|
191
|
+
&& document.annotatedImage.size === document.annotatedByteSize;
|
|
192
|
+
}
|
|
193
|
+
export function isSketchHandoffRecord(value) {
|
|
194
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
195
|
+
return false;
|
|
196
|
+
const handoff = value;
|
|
197
|
+
return Object.keys(handoff).every((key) => [
|
|
198
|
+
"id",
|
|
199
|
+
"projectId",
|
|
200
|
+
"sketchId",
|
|
201
|
+
"sketchRevision",
|
|
202
|
+
"localBatchId",
|
|
203
|
+
"batchRevision",
|
|
204
|
+
"transport",
|
|
205
|
+
"agentRequestId",
|
|
206
|
+
"timestamp",
|
|
207
|
+
"state",
|
|
208
|
+
].includes(key))
|
|
209
|
+
&& boundedString(handoff.id, 256)
|
|
210
|
+
&& boundedString(handoff.projectId, 256)
|
|
211
|
+
&& boundedString(handoff.sketchId, 256)
|
|
212
|
+
&& Number.isSafeInteger(handoff.sketchRevision)
|
|
213
|
+
&& handoff.sketchRevision > 0
|
|
214
|
+
&& boundedString(handoff.localBatchId, 256)
|
|
215
|
+
&& (handoff.batchRevision === undefined
|
|
216
|
+
|| (Number.isSafeInteger(handoff.batchRevision) && handoff.batchRevision >= 0))
|
|
217
|
+
&& (handoff.transport === "local" || handoff.transport === "clipboard" || handoff.transport === "direct")
|
|
218
|
+
&& (handoff.agentRequestId === undefined || boundedString(handoff.agentRequestId, 256))
|
|
219
|
+
&& Number.isSafeInteger(handoff.timestamp)
|
|
220
|
+
&& (handoff.state === "pending"
|
|
221
|
+
|| handoff.state === "dispatching"
|
|
222
|
+
|| handoff.state === "handing-off"
|
|
223
|
+
|| handoff.state === "needs-review"
|
|
224
|
+
|| handoff.state === "unknown");
|
|
225
|
+
}
|
|
226
|
+
export function cloneSketchDocument(document) {
|
|
227
|
+
return {
|
|
228
|
+
...document,
|
|
229
|
+
capture: { ...document.capture },
|
|
230
|
+
strokes: document.strokes.map((stroke) => ({
|
|
231
|
+
...stroke,
|
|
232
|
+
points: stroke.points.map((point) => ({ ...point })),
|
|
233
|
+
})),
|
|
234
|
+
...(document.annotations
|
|
235
|
+
? {
|
|
236
|
+
annotations: document.annotations.map((annotation) => ({
|
|
237
|
+
...annotation,
|
|
238
|
+
point: { ...annotation.point },
|
|
239
|
+
})),
|
|
240
|
+
}
|
|
241
|
+
: {}),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
export function cloneSketchHandoff(handoff) {
|
|
245
|
+
return { ...handoff };
|
|
246
|
+
}
|
|
247
|
+
export function sketchContentFingerprint(description, strokes, annotations = []) {
|
|
248
|
+
return JSON.stringify({ description, strokes, annotations });
|
|
249
|
+
}
|
|
250
|
+
export function sameSketchContent(document, description, strokes, annotations = []) {
|
|
251
|
+
return sketchContentFingerprint(document.description, document.strokes, document.annotations ?? [])
|
|
252
|
+
=== sketchContentFingerprint(description, strokes, annotations);
|
|
253
|
+
}
|
|
254
|
+
export function toAgentSketchMetadata(document) {
|
|
255
|
+
const annotations = document.annotations?.map((annotation) => ({
|
|
256
|
+
number: annotation.number,
|
|
257
|
+
description: annotation.description,
|
|
258
|
+
}));
|
|
259
|
+
return {
|
|
260
|
+
id: document.id,
|
|
261
|
+
revision: document.revision,
|
|
262
|
+
filename: document.filename,
|
|
263
|
+
mimeType: "image/png",
|
|
264
|
+
width: document.imageWidth,
|
|
265
|
+
height: document.imageHeight,
|
|
266
|
+
byteSize: document.annotatedByteSize,
|
|
267
|
+
capture: {
|
|
268
|
+
url: document.capture.url,
|
|
269
|
+
title: document.capture.title,
|
|
270
|
+
timestamp: document.capture.timestamp,
|
|
271
|
+
viewportWidth: document.capture.viewportWidth,
|
|
272
|
+
viewportHeight: document.capture.viewportHeight,
|
|
273
|
+
scrollX: document.capture.scrollX,
|
|
274
|
+
scrollY: document.capture.scrollY,
|
|
275
|
+
devicePixelRatio: document.capture.devicePixelRatio,
|
|
276
|
+
host: document.capture.host,
|
|
277
|
+
framework: document.capture.framework,
|
|
278
|
+
},
|
|
279
|
+
...(document.description ? { description: document.description } : {}),
|
|
280
|
+
...(annotations?.length ? { annotations } : {}),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type SketchDocument, type SketchHandoffRecord } from "./model.js";
|
|
2
|
+
export declare class SketchStorageError extends Error {
|
|
3
|
+
readonly code = "sketch_storage_error";
|
|
4
|
+
constructor(message: string, options?: {
|
|
5
|
+
cause?: unknown;
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
export interface SketchPersistenceSnapshot {
|
|
9
|
+
readonly documents: SketchDocument[];
|
|
10
|
+
readonly handoffs: SketchHandoffRecord[];
|
|
11
|
+
}
|
|
12
|
+
export declare function readSketchPersistence(projectId: string): Promise<SketchPersistenceSnapshot>;
|
|
13
|
+
/** Writes a document and its initial/current handoff atomically. */
|
|
14
|
+
export declare function writeSketchPersistence(projectId: string, document: SketchDocument, handoff: SketchHandoffRecord): Promise<void>;
|
|
15
|
+
export declare function writeSketchHandoff(projectId: string, handoff: SketchHandoffRecord): Promise<void>;
|
|
16
|
+
export declare function writeSketchHandoffs(projectId: string, handoffs: readonly SketchHandoffRecord[]): Promise<void>;
|
|
17
|
+
export declare function deleteSketchPersistence(projectId: string, sketchId: string): Promise<void>;
|
|
18
|
+
export declare function clearSketchPersistence(projectId: string): Promise<void>;
|
|
19
|
+
export declare function closeSketchDatabase(): Promise<void>;
|
|
20
|
+
/** Test seam for the no-IndexedDB fallback; it does not delete browser data. */
|
|
21
|
+
export declare function resetSketchMemory(): void;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { SKETCH_DATABASE_NAME, SKETCH_DATABASE_VERSION, SKETCH_DOCUMENTS_STORE, SKETCH_HANDOFFS_STORE, } from "./model.js";
|
|
2
|
+
export class SketchStorageError extends Error {
|
|
3
|
+
code = "sketch_storage_error";
|
|
4
|
+
constructor(message, options) {
|
|
5
|
+
super(message, options);
|
|
6
|
+
this.name = "SketchStorageError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const memoryProjects = new Map();
|
|
10
|
+
let databasePromise = null;
|
|
11
|
+
function memoryState(projectId) {
|
|
12
|
+
let state = memoryProjects.get(projectId);
|
|
13
|
+
if (!state) {
|
|
14
|
+
state = { documents: new Map(), handoffs: new Map() };
|
|
15
|
+
memoryProjects.set(projectId, state);
|
|
16
|
+
}
|
|
17
|
+
return state;
|
|
18
|
+
}
|
|
19
|
+
function indexedDbFactory() {
|
|
20
|
+
if (typeof indexedDB === "undefined")
|
|
21
|
+
return null;
|
|
22
|
+
return indexedDB;
|
|
23
|
+
}
|
|
24
|
+
function requestResult(request) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
request.onsuccess = () => resolve(request.result);
|
|
27
|
+
request.onerror = () => reject(request.error ?? new Error("IndexedDB request failed."));
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function transactionDone(transaction) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
transaction.oncomplete = () => resolve();
|
|
33
|
+
transaction.onerror = () => reject(transaction.error ?? new Error("IndexedDB transaction failed."));
|
|
34
|
+
transaction.onabort = () => reject(transaction.error ?? new Error("IndexedDB transaction was aborted."));
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async function openDatabase() {
|
|
38
|
+
const factory = indexedDbFactory();
|
|
39
|
+
if (!factory)
|
|
40
|
+
return null;
|
|
41
|
+
if (!databasePromise) {
|
|
42
|
+
databasePromise = new Promise((resolve, reject) => {
|
|
43
|
+
let request;
|
|
44
|
+
try {
|
|
45
|
+
request = factory.open(SKETCH_DATABASE_NAME, SKETCH_DATABASE_VERSION);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
reject(error);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
request.onupgradeneeded = () => {
|
|
52
|
+
const database = request.result;
|
|
53
|
+
if (!database.objectStoreNames.contains(SKETCH_DOCUMENTS_STORE)) {
|
|
54
|
+
const documents = database.createObjectStore(SKETCH_DOCUMENTS_STORE, { keyPath: "id" });
|
|
55
|
+
documents.createIndex("projectId", "projectId", { unique: false });
|
|
56
|
+
}
|
|
57
|
+
if (!database.objectStoreNames.contains(SKETCH_HANDOFFS_STORE)) {
|
|
58
|
+
const handoffs = database.createObjectStore(SKETCH_HANDOFFS_STORE, { keyPath: "id" });
|
|
59
|
+
handoffs.createIndex("projectId", "projectId", { unique: false });
|
|
60
|
+
handoffs.createIndex("sketchKey", ["sketchId", "sketchRevision"], { unique: false });
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
request.onsuccess = () => {
|
|
64
|
+
const database = request.result;
|
|
65
|
+
database.onversionchange = () => database.close();
|
|
66
|
+
resolve(database);
|
|
67
|
+
};
|
|
68
|
+
request.onerror = () => reject(request.error ?? new Error("IndexedDB could not be opened."));
|
|
69
|
+
request.onblocked = () => reject(new Error("IndexedDB upgrade is blocked by another tab."));
|
|
70
|
+
}).catch((error) => {
|
|
71
|
+
databasePromise = null;
|
|
72
|
+
throw new SketchStorageError("Sketch storage is unavailable in this browser.", { cause: error });
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return databasePromise;
|
|
76
|
+
}
|
|
77
|
+
export async function readSketchPersistence(projectId) {
|
|
78
|
+
const database = await openDatabase();
|
|
79
|
+
if (!database) {
|
|
80
|
+
const state = memoryState(projectId);
|
|
81
|
+
return {
|
|
82
|
+
documents: [...state.documents.values()],
|
|
83
|
+
handoffs: [...state.handoffs.values()],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const transaction = database.transaction([SKETCH_DOCUMENTS_STORE, SKETCH_HANDOFFS_STORE], "readonly");
|
|
88
|
+
const done = transactionDone(transaction);
|
|
89
|
+
const documentsPromise = requestResult(transaction.objectStore(SKETCH_DOCUMENTS_STORE).index("projectId").getAll(projectId));
|
|
90
|
+
const handoffsPromise = requestResult(transaction.objectStore(SKETCH_HANDOFFS_STORE).index("projectId").getAll(projectId));
|
|
91
|
+
const [documents, handoffs] = await Promise.all([documentsPromise, handoffsPromise, done]);
|
|
92
|
+
return { documents, handoffs };
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
throw new SketchStorageError("Sketch storage could not be read.", { cause: error });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Writes a document and its initial/current handoff atomically. */
|
|
99
|
+
export async function writeSketchPersistence(projectId, document, handoff) {
|
|
100
|
+
const database = await openDatabase();
|
|
101
|
+
if (!database) {
|
|
102
|
+
const state = memoryState(projectId);
|
|
103
|
+
state.documents.set(document.id, document);
|
|
104
|
+
state.handoffs.set(handoff.id, handoff);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const transaction = database.transaction([SKETCH_DOCUMENTS_STORE, SKETCH_HANDOFFS_STORE], "readwrite");
|
|
109
|
+
transaction.objectStore(SKETCH_DOCUMENTS_STORE).put(document);
|
|
110
|
+
transaction.objectStore(SKETCH_HANDOFFS_STORE).put(handoff);
|
|
111
|
+
await transactionDone(transaction);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
throw new SketchStorageError("Sketch could not be saved to browser storage.", { cause: error });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export async function writeSketchHandoff(projectId, handoff) {
|
|
118
|
+
const database = await openDatabase();
|
|
119
|
+
if (!database) {
|
|
120
|
+
memoryState(projectId).handoffs.set(handoff.id, handoff);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const transaction = database.transaction(SKETCH_HANDOFFS_STORE, "readwrite");
|
|
125
|
+
transaction.objectStore(SKETCH_HANDOFFS_STORE).put(handoff);
|
|
126
|
+
await transactionDone(transaction);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
throw new SketchStorageError("Sketch handoff state could not be saved.", { cause: error });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export async function writeSketchHandoffs(projectId, handoffs) {
|
|
133
|
+
if (handoffs.length === 0)
|
|
134
|
+
return;
|
|
135
|
+
const database = await openDatabase();
|
|
136
|
+
if (!database) {
|
|
137
|
+
const state = memoryState(projectId);
|
|
138
|
+
handoffs.forEach((handoff) => state.handoffs.set(handoff.id, handoff));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const transaction = database.transaction(SKETCH_HANDOFFS_STORE, "readwrite");
|
|
143
|
+
const store = transaction.objectStore(SKETCH_HANDOFFS_STORE);
|
|
144
|
+
handoffs.forEach((handoff) => store.put(handoff));
|
|
145
|
+
await transactionDone(transaction);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
throw new SketchStorageError("Sketch handoff state could not be saved.", { cause: error });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export async function deleteSketchPersistence(projectId, sketchId) {
|
|
152
|
+
const existing = await readSketchPersistence(projectId);
|
|
153
|
+
const handoffIds = existing.handoffs
|
|
154
|
+
.filter((handoff) => handoff.sketchId === sketchId)
|
|
155
|
+
.map((handoff) => handoff.id);
|
|
156
|
+
const database = await openDatabase();
|
|
157
|
+
if (!database) {
|
|
158
|
+
const state = memoryState(projectId);
|
|
159
|
+
state.documents.delete(sketchId);
|
|
160
|
+
handoffIds.forEach((id) => state.handoffs.delete(id));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const transaction = database.transaction([SKETCH_DOCUMENTS_STORE, SKETCH_HANDOFFS_STORE], "readwrite");
|
|
165
|
+
transaction.objectStore(SKETCH_DOCUMENTS_STORE).delete(sketchId);
|
|
166
|
+
const handoffs = transaction.objectStore(SKETCH_HANDOFFS_STORE);
|
|
167
|
+
handoffIds.forEach((id) => handoffs.delete(id));
|
|
168
|
+
await transactionDone(transaction);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
throw new SketchStorageError("Sketch could not be removed from browser storage.", { cause: error });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export async function clearSketchPersistence(projectId) {
|
|
175
|
+
const existing = await readSketchPersistence(projectId);
|
|
176
|
+
const database = await openDatabase();
|
|
177
|
+
if (!database) {
|
|
178
|
+
const state = memoryState(projectId);
|
|
179
|
+
state.documents.clear();
|
|
180
|
+
state.handoffs.clear();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const transaction = database.transaction([SKETCH_DOCUMENTS_STORE, SKETCH_HANDOFFS_STORE], "readwrite");
|
|
185
|
+
const documents = transaction.objectStore(SKETCH_DOCUMENTS_STORE);
|
|
186
|
+
const handoffs = transaction.objectStore(SKETCH_HANDOFFS_STORE);
|
|
187
|
+
existing.documents.forEach((document) => documents.delete(document.id));
|
|
188
|
+
existing.handoffs.forEach((handoff) => handoffs.delete(handoff.id));
|
|
189
|
+
await transactionDone(transaction);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
throw new SketchStorageError("Sketch storage could not be cleared.", { cause: error });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
export async function closeSketchDatabase() {
|
|
196
|
+
if (!databasePromise)
|
|
197
|
+
return;
|
|
198
|
+
const database = await databasePromise.catch(() => null);
|
|
199
|
+
database?.close();
|
|
200
|
+
databasePromise = null;
|
|
201
|
+
}
|
|
202
|
+
/** Test seam for the no-IndexedDB fallback; it does not delete browser data. */
|
|
203
|
+
export function resetSketchMemory() {
|
|
204
|
+
memoryProjects.clear();
|
|
205
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type SketchAnnotation, type SketchStroke } from "./model.js";
|
|
2
|
+
export interface PngDimensions {
|
|
3
|
+
readonly width: number;
|
|
4
|
+
readonly height: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function readPngDimensions(bytes: Uint8Array): PngDimensions | null;
|
|
7
|
+
export declare function getPngDimensions(blob: Blob): Promise<PngDimensions | null>;
|
|
8
|
+
export declare function validatePngBlob(blob: Blob, expected?: PngDimensions): Promise<PngDimensions>;
|
|
9
|
+
export declare function drawSketchStroke(ctx: CanvasRenderingContext2D, stroke: Pick<SketchStroke, "points" | "width"> & Partial<Pick<SketchStroke, "kind" | "color">>, scaleX?: number, scaleY?: number): void;
|
|
10
|
+
export declare function drawSketchStrokes(ctx: CanvasRenderingContext2D, strokes: readonly SketchStroke[], scaleX?: number, scaleY?: number): void;
|
|
11
|
+
export declare function drawSketchAnnotation(ctx: CanvasRenderingContext2D, annotation: Pick<SketchAnnotation, "number" | "point">, scaleX?: number, scaleY?: number): void;
|
|
12
|
+
export declare function drawSketchAnnotations(ctx: CanvasRenderingContext2D, annotations: readonly SketchAnnotation[], scaleX?: number, scaleY?: number): void;
|
|
13
|
+
/** Renders the immutable screenshot and committed strokes into one PNG. */
|
|
14
|
+
export declare function renderAnnotatedPng(originalImage: Blob, imageWidth: number, imageHeight: number, strokes: readonly SketchStroke[], annotations?: readonly SketchAnnotation[]): Promise<{
|
|
15
|
+
readonly blob: Blob;
|
|
16
|
+
readonly width: number;
|
|
17
|
+
readonly height: number;
|
|
18
|
+
}>;
|
|
19
|
+
export declare function blobToBase64(blob: Blob): Promise<string>;
|
|
20
|
+
export declare function copyImageToClipboard(blob: Blob): Promise<void>;
|
|
21
|
+
export declare function copyImageAndTextToClipboard(blob: Blob, text: string): Promise<void>;
|
|
22
|
+
export declare function downloadImage(blob: Blob, filename: string): void;
|