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.
Files changed (201) hide show
  1. package/bin/nudge-ui.mjs +3 -1
  2. package/dist/cli/agentCommand.d.ts +8 -0
  3. package/dist/cli/agentCommand.js +59 -0
  4. package/dist/client.mjs +872 -12
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
  6. package/dist/hosts/astro/clientTransport.js +60 -11
  7. package/dist/hosts/next/sidecar.d.ts +4 -0
  8. package/dist/hosts/next/sidecar.js +49 -6
  9. package/dist/hosts/next/wrapper.js +30 -2
  10. package/dist/hosts/projectBridge.d.ts +20 -0
  11. package/dist/hosts/projectBridge.js +97 -0
  12. package/dist/hosts/static/cli.d.ts +1 -1
  13. package/dist/hosts/static/cli.js +6 -1
  14. package/dist/hosts/static/manifest.d.ts +1 -1
  15. package/dist/hosts/static/manifest.js +1 -1
  16. package/dist/hosts/static/server.js +44 -3
  17. package/dist/hosts/vite/vite.d.ts +5 -1
  18. package/dist/hosts/vite/vite.js +78 -14
  19. package/dist/inspector/agent/McpConnectionDialog.d.ts +2 -2
  20. package/dist/inspector/agent/McpConnectionDialog.js +11 -35
  21. package/dist/inspector/agent/client.d.ts +10 -3
  22. package/dist/inspector/agent/client.js +80 -9
  23. package/dist/inspector/agent/connectionStatus.js +3 -3
  24. package/dist/inspector/agent/httpTransport.d.ts +2 -0
  25. package/dist/inspector/agent/httpTransport.js +37 -17
  26. package/dist/inspector/agent/protocol.d.ts +8 -3
  27. package/dist/inspector/agent/protocol.js +1 -1
  28. package/dist/inspector/agent/verification.d.ts +1 -1
  29. package/dist/inspector/agent/verification.js +69 -12
  30. package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
  31. package/dist/inspector/canvas/CanvasCard.js +181 -91
  32. package/dist/inspector/canvas/CanvasElementOverlay.js +246 -52
  33. package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
  34. package/dist/inspector/canvas/CanvasToolbar.js +24 -0
  35. package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
  36. package/dist/inspector/canvas/CanvasWorkspace.js +392 -73
  37. package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
  38. package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
  39. package/dist/inspector/canvas/canvasResize.d.ts +30 -0
  40. package/dist/inspector/canvas/canvasResize.js +52 -0
  41. package/dist/inspector/canvas/canvasStore.d.ts +30 -6
  42. package/dist/inspector/canvas/canvasStore.js +128 -61
  43. package/dist/inspector/canvas/frameProtocol.d.ts +69 -5
  44. package/dist/inspector/canvas/frameProtocol.js +56 -2
  45. package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -0
  46. package/dist/inspector/canvas/keyboardShortcuts.js +14 -0
  47. package/dist/inspector/canvas/projection.d.ts +7 -0
  48. package/dist/inspector/canvas/projection.js +118 -25
  49. package/dist/inspector/canvas/rendererBootstrap.js +29 -29
  50. package/dist/inspector/canvas/rendererElementSelector.js +341 -30
  51. package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
  52. package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
  53. package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
  54. package/dist/inspector/canvas/sessionStore.d.ts +1 -0
  55. package/dist/inspector/canvas/sessionStore.js +12 -4
  56. package/dist/inspector/canvas/staleChangeDetector.js +16 -14
  57. package/dist/inspector/changes/changesLog.js +31 -1
  58. package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
  59. package/dist/inspector/changes/managedStyleProjection.js +3 -3
  60. package/dist/inspector/changes/structuralTypes.d.ts +4 -0
  61. package/dist/inspector/clientManifest.d.ts +5 -0
  62. package/dist/inspector/clientManifest.js +11 -0
  63. package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
  64. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
  65. package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
  66. package/dist/inspector/index.d.ts +2 -0
  67. package/dist/inspector/index.js +74 -24
  68. package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
  69. package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
  70. package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
  71. package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
  72. package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
  73. package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
  74. package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
  75. package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
  76. package/dist/inspector/inline-text/textEditChange.js +165 -0
  77. package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
  78. package/dist/inspector/inspection/selectionProperty.js +1 -0
  79. package/dist/inspector/overlay/selectedGeometry.js +21 -2
  80. package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
  81. package/dist/inspector/overlay/spacingGestures.js +353 -0
  82. package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
  83. package/dist/inspector/overlay/structuralGestures.js +12 -10
  84. package/dist/inspector/projection/managedStylesheet.js +4 -1
  85. package/dist/inspector/projection/renderedInstance.d.ts +2 -0
  86. package/dist/inspector/projection/renderedInstance.js +5 -1
  87. package/dist/inspector/projection/structuralProjection.d.ts +2 -0
  88. package/dist/inspector/projection/structuralProjection.js +85 -1
  89. package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
  90. package/dist/inspector/projection/textProjection.d.ts +2 -0
  91. package/dist/inspector/projection/textProjection.js +10 -4
  92. package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
  93. package/dist/inspector/prompt/clipboardHandoff.js +3 -3
  94. package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
  95. package/dist/inspector/prompt/generatePrompt.js +19 -2
  96. package/dist/inspector/runtime/editorShell.d.ts +2 -0
  97. package/dist/inspector/runtime/editorShell.js +4 -0
  98. package/dist/inspector/runtime/runtimeConfig.d.ts +4 -0
  99. package/dist/inspector/runtime/runtimeConfig.js +4 -0
  100. package/dist/inspector/selection/selectionStore.js +2 -0
  101. package/dist/inspector/settings/SettingsDialog.js +10 -0
  102. package/dist/inspector/shell/AppShell.js +5 -1
  103. package/dist/inspector/shell/ChangesLog.js +8 -4
  104. package/dist/inspector/shell/CopyPromptButton.js +95 -19
  105. package/dist/inspector/shell/EmptyState.js +1 -1
  106. package/dist/inspector/shell/InspectorShell.d.ts +0 -1
  107. package/dist/inspector/shell/InspectorShell.js +37 -38
  108. package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
  109. package/dist/inspector/shell/PeekOriginalButton.js +57 -0
  110. package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
  111. package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
  112. package/dist/inspector/shell/originalPreview.d.ts +9 -0
  113. package/dist/inspector/shell/originalPreview.js +61 -0
  114. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  115. package/dist/inspector/shell/shortcuts.js +15 -0
  116. package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
  117. package/dist/inspector/sketch/SketchChanges.js +72 -0
  118. package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
  119. package/dist/inspector/sketch/SketchEditor.js +402 -0
  120. package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
  121. package/dist/inspector/sketch/SketchEntryButton.js +11 -0
  122. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
  123. package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
  124. package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
  125. package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
  126. package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
  127. package/dist/inspector/sketch/SketchOverlay.js +420 -0
  128. package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
  129. package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
  130. package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
  131. package/dist/inspector/sketch/SketchWorkspace.js +152 -0
  132. package/dist/inspector/sketch/capture.d.ts +55 -0
  133. package/dist/inspector/sketch/capture.js +350 -0
  134. package/dist/inspector/sketch/freehand.d.ts +13 -0
  135. package/dist/inspector/sketch/freehand.js +64 -0
  136. package/dist/inspector/sketch/geometry.d.ts +17 -0
  137. package/dist/inspector/sketch/geometry.js +98 -0
  138. package/dist/inspector/sketch/handoff.d.ts +21 -0
  139. package/dist/inspector/sketch/handoff.js +72 -0
  140. package/dist/inspector/sketch/interaction.d.ts +21 -0
  141. package/dist/inspector/sketch/interaction.js +98 -0
  142. package/dist/inspector/sketch/model.d.ts +104 -0
  143. package/dist/inspector/sketch/model.js +282 -0
  144. package/dist/inspector/sketch/persistence.d.ts +21 -0
  145. package/dist/inspector/sketch/persistence.js +205 -0
  146. package/dist/inspector/sketch/raster.d.ts +22 -0
  147. package/dist/inspector/sketch/raster.js +203 -0
  148. package/dist/inspector/sketch/sketchNote.d.ts +10 -0
  149. package/dist/inspector/sketch/sketchNote.js +30 -0
  150. package/dist/inspector/sketch/store.d.ts +47 -0
  151. package/dist/inspector/sketch/store.js +409 -0
  152. package/dist/inspector/styleEditors/BorderEditor.js +15 -19
  153. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
  154. package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
  155. package/dist/inspector/styleEditors/ColorPicker.js +11 -36
  156. package/dist/inspector/styleEditors/GapField.d.ts +3 -2
  157. package/dist/inspector/styleEditors/GapField.js +2 -2
  158. package/dist/inspector/styleEditors/GridSection.js +1 -1
  159. package/dist/inspector/styleEditors/LayoutSection.js +11 -8
  160. package/dist/inspector/styleEditors/SpacingBox.js +23 -30
  161. package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
  162. package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
  163. package/dist/inspector/styleEditors/layoutValue.js +3 -0
  164. package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
  165. package/dist/inspector/styleEditors/nudgeValue.js +91 -5
  166. package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
  167. package/dist/inspector/styleEditors/stylePresence.js +22 -0
  168. package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
  169. package/dist/inspector/styleEditors/textRelevance.js +35 -0
  170. package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
  171. package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
  172. package/dist/inspector/tokens/TokenField.js +111 -7
  173. package/dist/inspector/tokens/editActions.d.ts +2 -0
  174. package/dist/inspector/tokens/editActions.js +2 -2
  175. package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
  176. package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
  177. package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
  178. package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
  179. package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
  180. package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
  181. package/dist/inspector/tokens/resolution.d.ts +3 -26
  182. package/dist/inspector/tokens/resolution.js +44 -735
  183. package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
  184. package/dist/inspector/tokens/tokenAvailability.js +204 -0
  185. package/dist/inspector/ui/Button.d.ts +1 -1
  186. package/dist/inspector/ui/SideValuesField.js +2 -2
  187. package/dist/inspector/ui/StatusCallout.d.ts +4 -1
  188. package/dist/inspector/ui/StatusCallout.js +2 -2
  189. package/dist/inspector/ui/styles.js +13 -11
  190. package/dist/internal/agent-protocol.d.ts +65 -1
  191. package/dist/internal/agent-protocol.js +183 -3
  192. package/dist/nudge-ui.mjs +261 -57
  193. package/dist/transport/editor.d.ts +28 -0
  194. package/dist/transport/editor.js +132 -0
  195. package/dist/transport/index.d.ts +1 -0
  196. package/dist/transport/index.js +1 -0
  197. package/dist/transport/routes.d.ts +7 -0
  198. package/dist/transport/routes.js +7 -0
  199. package/package.json +3 -1
  200. package/dist/inspector/shell/panelLayout.d.ts +0 -2
  201. 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;