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.
Files changed (226) hide show
  1. package/README.md +164 -46
  2. package/dist/client.mjs +252 -61
  3. package/dist/css/value-semantics/colorSemantics.d.ts +4 -0
  4. package/dist/css/value-semantics/colorSemantics.js +17 -0
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -0
  6. package/dist/hosts/astro/clientTransport.js +38 -32
  7. package/dist/hosts/astro/integration.js +5 -1
  8. package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
  9. package/dist/hosts/astro/markdownIdentity.js +81 -0
  10. package/dist/hosts/next/manifest.js +1 -1
  11. package/dist/hosts/next/sidecar.js +5 -1
  12. package/dist/hosts/static/manifest.js +1 -1
  13. package/dist/hosts/static/server.js +3 -0
  14. package/dist/hosts/vite/vite.d.ts +3 -0
  15. package/dist/hosts/vite/vite.js +24 -4
  16. package/dist/inspector/agent/McpConnectionDialog.js +13 -19
  17. package/dist/inspector/agent/activity.d.ts +18 -0
  18. package/dist/inspector/agent/activity.js +70 -0
  19. package/dist/inspector/agent/client.d.ts +2 -0
  20. package/dist/inspector/agent/client.js +29 -1
  21. package/dist/inspector/agent/connectionStatus.js +1 -1
  22. package/dist/inspector/agent/httpTransport.js +9 -2
  23. package/dist/inspector/agent/protocol.d.ts +6 -3
  24. package/dist/inspector/agent/protocol.js +1 -1
  25. package/dist/inspector/agent/verification.d.ts +11 -1
  26. package/dist/inspector/agent/verification.js +53 -25
  27. package/dist/inspector/canvas/AgentActivityOverlay.d.ts +13 -0
  28. package/dist/inspector/canvas/AgentActivityOverlay.js +111 -0
  29. package/dist/inspector/canvas/CanvasCard.d.ts +9 -2
  30. package/dist/inspector/canvas/CanvasCard.js +133 -38
  31. package/dist/inspector/canvas/CanvasElementOverlay.js +152 -16
  32. package/dist/inspector/canvas/CanvasToolIcons.d.ts +10 -0
  33. package/dist/inspector/canvas/CanvasToolIcons.js +20 -0
  34. package/dist/inspector/canvas/CanvasToolbar.js +8 -4
  35. package/dist/inspector/canvas/CanvasWorkspace.js +225 -57
  36. package/dist/inspector/canvas/StaleChangeIndicator.js +3 -13
  37. package/dist/inspector/canvas/agentPresentation.d.ts +8 -8
  38. package/dist/inspector/canvas/agentPresentation.js +64 -52
  39. package/dist/inspector/canvas/canvasGestures.d.ts +1 -1
  40. package/dist/inspector/canvas/canvasGestures.js +1 -1
  41. package/dist/inspector/canvas/canvasStore.d.ts +46 -82
  42. package/dist/inspector/canvas/canvasStore.js +251 -284
  43. package/dist/inspector/canvas/frameContent.d.ts +21 -0
  44. package/dist/inspector/canvas/frameContent.js +44 -0
  45. package/dist/inspector/canvas/frameProtocol.d.ts +21 -4
  46. package/dist/inspector/canvas/frameProtocol.js +3 -3
  47. package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -1
  48. package/dist/inspector/canvas/keyboardShortcuts.js +4 -4
  49. package/dist/inspector/canvas/layout.d.ts +52 -0
  50. package/dist/inspector/canvas/layout.js +106 -0
  51. package/dist/inspector/canvas/projection.d.ts +15 -5
  52. package/dist/inspector/canvas/projection.js +69 -13
  53. package/dist/inspector/canvas/rendererBootstrap.js +32 -4
  54. package/dist/inspector/canvas/rendererElementSelector.js +47 -6
  55. package/dist/inspector/canvas/rendererStylesheet.js +2 -2
  56. package/dist/inspector/canvas/resizeHandleScale.js +1 -1
  57. package/dist/inspector/canvas/sessionStore.d.ts +11 -26
  58. package/dist/inspector/canvas/sessionStore.js +43 -183
  59. package/dist/inspector/canvas/staleChangeDetector.js +9 -3
  60. package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
  61. package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
  62. package/dist/inspector/canvas/viewStore.d.ts +38 -0
  63. package/dist/inspector/canvas/viewStore.js +104 -0
  64. package/dist/inspector/canvas/wheelPan.d.ts +8 -0
  65. package/dist/inspector/canvas/wheelPan.js +8 -0
  66. package/dist/inspector/changes/changesLog.d.ts +6 -5
  67. package/dist/inspector/changes/changesLog.js +40 -32
  68. package/dist/inspector/changes/draftChanges.d.ts +56 -0
  69. package/dist/inspector/changes/draftChanges.js +300 -0
  70. package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
  71. package/dist/inspector/changes/previewDiagnostics.js +11 -25
  72. package/dist/inspector/clientManifest.d.ts +1 -1
  73. package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
  74. package/dist/inspector/comments/CommentsOverlay.js +250 -0
  75. package/dist/inspector/comments/element.d.ts +6 -0
  76. package/dist/inspector/comments/element.js +47 -0
  77. package/dist/inspector/comments/store.d.ts +29 -0
  78. package/dist/inspector/comments/store.js +121 -0
  79. package/dist/inspector/comments/verification.d.ts +5 -0
  80. package/dist/inspector/comments/verification.js +52 -0
  81. package/dist/inspector/drafts/model.d.ts +35 -0
  82. package/dist/inspector/drafts/model.js +45 -0
  83. package/dist/inspector/drafts/store.d.ts +28 -0
  84. package/dist/inspector/drafts/store.js +231 -0
  85. package/dist/inspector/index.js +20 -27
  86. package/dist/inspector/inline-text/inlineTextEditor.js +7 -0
  87. package/dist/inspector/inspection/useBrowserCssInspection.js +15 -1
  88. package/dist/inspector/iterations/capture.d.ts +2 -0
  89. package/dist/inspector/iterations/capture.js +173 -0
  90. package/dist/inspector/iterations/client.d.ts +7 -0
  91. package/dist/inspector/iterations/client.js +52 -0
  92. package/dist/inspector/iterations/coordinator.d.ts +15 -0
  93. package/dist/inspector/iterations/coordinator.js +130 -0
  94. package/dist/inspector/iterations/prompt.d.ts +3 -0
  95. package/dist/inspector/iterations/prompt.js +26 -0
  96. package/dist/inspector/overlay/DomNavigator.js +21 -15
  97. package/dist/inspector/overlay/interactionStyles.js +3 -1
  98. package/dist/inspector/overlay/spacingGestures.d.ts +13 -1
  99. package/dist/inspector/overlay/spacingGestures.js +82 -5
  100. package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
  101. package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
  102. package/dist/inspector/projection/renderedInstance.d.ts +2 -1
  103. package/dist/inspector/projection/renderedInstance.js +9 -5
  104. package/dist/inspector/projection/structuralProjection.d.ts +2 -1
  105. package/dist/inspector/projection/structuralProjection.js +19 -15
  106. package/dist/inspector/projection/textProjection.d.ts +2 -1
  107. package/dist/inspector/projection/textProjection.js +8 -4
  108. package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
  109. package/dist/inspector/prompt/clipboardHandoff.d.ts +10 -18
  110. package/dist/inspector/prompt/clipboardHandoff.js +92 -143
  111. package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
  112. package/dist/inspector/prompt/generatePrompt.js +17 -16
  113. package/dist/inspector/runtime/runtimeConfig.d.ts +9 -0
  114. package/dist/inspector/runtime/runtimeConfig.js +24 -0
  115. package/dist/inspector/settings/SettingsDialog.js +21 -8
  116. package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
  117. package/dist/inspector/shell/AgentStatusButton.js +36 -0
  118. package/dist/inspector/shell/AppShell.js +4 -2
  119. package/dist/inspector/shell/ChangesLog.js +46 -26
  120. package/dist/inspector/shell/CopyPromptButton.js +138 -89
  121. package/dist/inspector/shell/EmptyState.js +9 -8
  122. package/dist/inspector/shell/InspectorShell.js +13 -21
  123. package/dist/inspector/shell/PeekOriginalButton.js +24 -23
  124. package/dist/inspector/shell/originalPreview.js +5 -5
  125. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  126. package/dist/inspector/shell/shortcuts.js +11 -0
  127. package/dist/inspector/sketch/SketchEditor.js +14 -14
  128. package/dist/inspector/sketch/SketchEntryButton.js +1 -1
  129. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
  130. package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
  131. package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
  132. package/dist/inspector/sketch/SketchOverlay.js +7 -7
  133. package/dist/inspector/sketch/SketchWorkspace.js +18 -12
  134. package/dist/inspector/sketch/interaction.d.ts +1 -0
  135. package/dist/inspector/sketch/interaction.js +8 -4
  136. package/dist/inspector/sketch/model.d.ts +5 -0
  137. package/dist/inspector/sketch/model.js +6 -2
  138. package/dist/inspector/sketch/raster.d.ts +1 -2
  139. package/dist/inspector/sketch/raster.js +68 -4
  140. package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
  141. package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
  142. package/dist/inspector/styleEditors/BorderEditor.js +49 -78
  143. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
  144. package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
  145. package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
  146. package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
  147. package/dist/inspector/styleEditors/ColorPicker.js +10 -4
  148. package/dist/inspector/styleEditors/GapField.js +3 -5
  149. package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
  150. package/dist/inspector/styleEditors/GridChildSection.js +18 -9
  151. package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
  152. package/dist/inspector/styleEditors/GridPicker.js +8 -2
  153. package/dist/inspector/styleEditors/GridSection.js +1 -1
  154. package/dist/inspector/styleEditors/GridValueField.js +19 -14
  155. package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
  156. package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
  157. package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
  158. package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
  159. package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
  160. package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
  161. package/dist/inspector/styleEditors/LayoutSection.js +32 -11
  162. package/dist/inspector/styleEditors/SpacingBox.js +7 -5
  163. package/dist/inspector/styleEditors/Typography.js +11 -3
  164. package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
  165. package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
  166. package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
  167. package/dist/inspector/styleEditors/gradientValue.js +114 -0
  168. package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
  169. package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
  170. package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
  171. package/dist/inspector/styleEditors/layoutValue.js +0 -42
  172. package/dist/inspector/styleEditors/nudgeValue.d.ts +2 -2
  173. package/dist/inspector/styleEditors/nudgeValue.js +36 -8
  174. package/dist/inspector/tokens/TokenField.d.ts +7 -0
  175. package/dist/inspector/tokens/TokenField.js +29 -21
  176. package/dist/inspector/tokens/TokenLabel.js +5 -14
  177. package/dist/inspector/tokens/editActions.d.ts +1 -1
  178. package/dist/inspector/tokens/editActions.js +1 -1
  179. package/dist/inspector/ui/AtRuleContext.js +4 -6
  180. package/dist/inspector/ui/Button.d.ts +6 -2
  181. package/dist/inspector/ui/Button.js +4 -3
  182. package/dist/inspector/ui/ControlSurface.d.ts +2 -2
  183. package/dist/inspector/ui/ControlSurface.js +4 -3
  184. package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
  185. package/dist/inspector/ui/InlineStyleWarning.js +13 -0
  186. package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
  187. package/dist/inspector/ui/InspectorPopover.js +4 -5
  188. package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
  189. package/dist/inspector/ui/PopoverListbox.js +8 -7
  190. package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
  191. package/dist/inspector/ui/SegmentedControl.js +2 -2
  192. package/dist/inspector/ui/Select.d.ts +6 -1
  193. package/dist/inspector/ui/Select.js +7 -11
  194. package/dist/inspector/ui/SideValuesField.js +1 -1
  195. package/dist/inspector/ui/Tooltip.d.ts +26 -0
  196. package/dist/inspector/ui/Tooltip.js +32 -0
  197. package/dist/inspector/ui/classNames.d.ts +2 -0
  198. package/dist/inspector/ui/classNames.js +4 -0
  199. package/dist/inspector/ui/customCursors.d.ts +23 -0
  200. package/dist/inspector/ui/customCursors.js +122 -0
  201. package/dist/inspector/ui/dragCursor.d.ts +0 -2
  202. package/dist/inspector/ui/dragCursor.js +2 -10
  203. package/dist/inspector/ui/portalContainer.d.ts +2 -0
  204. package/dist/inspector/ui/portalContainer.js +6 -0
  205. package/dist/inspector/ui/styles.js +29 -21
  206. package/dist/inspector/workspace/commands.d.ts +9 -0
  207. package/dist/inspector/workspace/commands.js +82 -0
  208. package/dist/inspector/workspace/controller.d.ts +4 -0
  209. package/dist/inspector/workspace/controller.js +66 -0
  210. package/dist/inspector/workspace/handoff.d.ts +42 -0
  211. package/dist/inspector/workspace/handoff.js +115 -0
  212. package/dist/inspector/workspace/timeline.d.ts +30 -0
  213. package/dist/inspector/workspace/timeline.js +108 -0
  214. package/dist/internal/agent-protocol.d.ts +19 -2
  215. package/dist/internal/agent-protocol.js +13 -0
  216. package/dist/nudge-ui.mjs +558 -311
  217. package/dist/project/artifacts.d.ts +13 -0
  218. package/dist/project/artifacts.js +253 -0
  219. package/dist/transport/artifacts.d.ts +8 -0
  220. package/dist/transport/artifacts.js +9 -0
  221. package/dist/transport/editor.js +3 -1
  222. package/dist/transport/routes.d.ts +2 -0
  223. package/dist/transport/routes.js +2 -0
  224. package/package.json +6 -2
  225. package/dist/inspector/changes/workspaceChanges.d.ts +0 -47
  226. package/dist/inspector/changes/workspaceChanges.js +0 -265
@@ -9,7 +9,8 @@ import { resolveSelectionTarget, selectionTargetMode } from "../selection/select
9
9
  import { escapeCssString } from "../projection/cssEscapes.js";
10
10
  import { APPLICATION_GESTURE_EVENTS, blockApplicationClick, blockApplicationGesture, isInlineEditorTarget, isUserClick, } from "../overlay/clickPolicy.js";
11
11
  import { EMPTY_TEXT_PROJECTION_ATTR } from "../projection/textProjection.js";
12
- import { getSpacingAffordanceAtPoint, toSpacingDescriptor, } from "../overlay/spacingGestures.js";
12
+ import { getSpacingAffordanceAtPoint, getSpacingHoverAtPoint, toSpacingDescriptor, } from "../overlay/spacingGestures.js";
13
+ import { COMMENT_CURSOR, INTERACTION_CURSOR_PROPERTY, rendererCursorValue } from "../ui/customCursors.js";
13
14
  const REACT_FIBER_KEY = /^__reactFiber\$/;
14
15
  const REACT_INTERNAL_KEY = /^__reactInternalInstance\$/;
15
16
  const HOVER_SCROLL_SETTLE_DELAY = 150;
@@ -100,8 +101,29 @@ export function installRendererElementSelector(acceptsClick = isUserClick) {
100
101
  const cidIndex = createCidIndex(document);
101
102
  const initialCursor = document.documentElement.style.getPropertyValue("cursor");
102
103
  const initialCursorPriority = document.documentElement.style.getPropertyPriority("cursor");
104
+ const initialInteractionCursor = document.documentElement.style.getPropertyValue(INTERACTION_CURSOR_PROPERTY);
105
+ const initialInteractionCursorPriority = document.documentElement.style.getPropertyPriority(INTERACTION_CURSOR_PROPERTY);
106
+ let commentMode = false;
107
+ let interactionCursor = "design";
108
+ let commentCursor = COMMENT_CURSOR;
109
+ let appliedInteractionCursor = rendererCursorValue("design");
103
110
  let appliedRootCursor = null;
104
111
  let spacingCursor = null;
112
+ function setInteractionCursor(cursor) {
113
+ appliedInteractionCursor = rendererCursorValue(cursor, commentCursor);
114
+ document.documentElement.style.setProperty(INTERACTION_CURSOR_PROPERTY, appliedInteractionCursor);
115
+ }
116
+ function restoreInteractionCursor() {
117
+ if (document.documentElement.style.getPropertyValue(INTERACTION_CURSOR_PROPERTY) !== appliedInteractionCursor)
118
+ return;
119
+ if (initialInteractionCursor) {
120
+ document.documentElement.style.setProperty(INTERACTION_CURSOR_PROPERTY, initialInteractionCursor, initialInteractionCursorPriority);
121
+ }
122
+ else {
123
+ document.documentElement.style.removeProperty(INTERACTION_CURSOR_PROPERTY);
124
+ }
125
+ }
126
+ setInteractionCursor("design");
105
127
  function clearSpacingCursor() {
106
128
  const active = spacingCursor;
107
129
  spacingCursor = null;
@@ -140,7 +162,7 @@ export function installRendererElementSelector(acceptsClick = isUserClick) {
140
162
  spacingCursor.appliedValue = cursor;
141
163
  }
142
164
  element.style.setProperty("cursor", cursor, "important");
143
- document.documentElement.style.setProperty("cursor", cursor);
165
+ document.documentElement.style.setProperty("cursor", cursor, "important");
144
166
  appliedRootCursor = cursor;
145
167
  }
146
168
  function reportHover(pending) {
@@ -152,10 +174,16 @@ export function installRendererElementSelector(acceptsClick = isUserClick) {
152
174
  const selector = buildSelector(element);
153
175
  const src = element.getAttribute("data-src") ?? "";
154
176
  const rect = clear ? null : element.getBoundingClientRect();
155
- const rawSpacing = !clear && pending.point
177
+ // The handle hit is the only target that starts a drag, so it is the only
178
+ // one that changes the cursor. The area-wide match is purely visual.
179
+ const rawSpacing = !commentMode && !clear && pending.point
156
180
  ? getSpacingAffordanceAtPoint(document, pending.point.x, pending.point.y)
157
181
  : null;
158
182
  const spacing = rawSpacing && rawSpacing.element === element ? rawSpacing : null;
183
+ const rawHoverSpacing = !commentMode && !clear && pending.point
184
+ ? getSpacingHoverAtPoint(document, pending.point.x, pending.point.y)
185
+ : null;
186
+ const hoverSpacing = rawHoverSpacing && rawHoverSpacing.element === element ? rawHoverSpacing : null;
159
187
  if (spacing) {
160
188
  setSpacingCursor(spacing.element, spacing.cursor);
161
189
  }
@@ -182,6 +210,7 @@ export function installRendererElementSelector(acceptsClick = isUserClick) {
182
210
  borders: rect ? readBorderWidths(element) : null,
183
211
  point: pending.point,
184
212
  spacing: spacing ? toSpacingDescriptor(spacing) : null,
213
+ hoverSpacing: hoverSpacing ? toSpacingDescriptor(hoverSpacing) : null,
185
214
  ...identity,
186
215
  };
187
216
  sendToParent(msg);
@@ -292,8 +321,19 @@ export function installRendererElementSelector(acceptsClick = isUserClick) {
292
321
  updateMeasureState(message.altKey, measurePointerOverPage);
293
322
  return;
294
323
  }
324
+ if (message.type === "pan-modifier" && typeof message.spaceHeld === "boolean") {
325
+ setInteractionCursor(message.spaceHeld ? "drag" : interactionCursor);
326
+ return;
327
+ }
295
328
  if (message.type !== "inspector-interaction-state" || typeof message.open !== "boolean")
296
329
  return;
330
+ if (typeof message.commentCursor === "string")
331
+ commentCursor = message.commentCursor;
332
+ interactionCursor = message.cursor === "comment" ? "comment" : message.cursor === "drag" ? "drag" : "design";
333
+ commentMode = interactionCursor === "comment";
334
+ cancelActiveDrag();
335
+ clearSpacingCursor();
336
+ setInteractionCursor(interactionCursor);
297
337
  const interactionsEnabled = message.interactionsEnabled === undefined
298
338
  ? message.open
299
339
  : message.interactionsEnabled === true;
@@ -360,7 +400,7 @@ export function installRendererElementSelector(acceptsClick = isUserClick) {
360
400
  trackListener(document, "mousedown", (event) => {
361
401
  if (interactionsSuspended)
362
402
  return;
363
- if (event.button !== 0)
403
+ if (event.button !== 0 || commentMode)
364
404
  return;
365
405
  const element = resolveSelectionTarget(event.target, selectionTargetMode(event));
366
406
  if (!element)
@@ -559,7 +599,7 @@ export function installRendererElementSelector(acceptsClick = isUserClick) {
559
599
  sendToParent(msg);
560
600
  return;
561
601
  }
562
- if (!lastSelected)
602
+ if (!lastSelected || commentMode)
563
603
  return;
564
604
  const identity = getRendererIdentity();
565
605
  if (!identity)
@@ -619,7 +659,7 @@ export function installRendererElementSelector(acceptsClick = isUserClick) {
619
659
  hoverUpdate.schedule({ element: el, clear: true, point: null });
620
660
  }, true);
621
661
  trackListener(document, "dblclick", (event) => {
622
- if (interactionsSuspended)
662
+ if (interactionsSuspended || commentMode)
623
663
  return;
624
664
  const element = resolveSelectionTarget(event.target, selectionTargetMode(event));
625
665
  const identity = getRendererIdentity();
@@ -701,5 +741,6 @@ export function installRendererElementSelector(acceptsClick = isUserClick) {
701
741
  measureAltKey = false;
702
742
  document.documentElement.removeAttribute("data-nudge-ui-panel");
703
743
  clearSpacingCursor();
744
+ restoreInteractionCursor();
704
745
  };
705
746
  }
@@ -8,7 +8,7 @@ import { applyRenderedInstanceProjection, getRenderedInstanceProjectionReports,
8
8
  import { applyStructuralProjection, getStructuralProjectionReports, isStructuralChange, subscribeStructuralDiagnostics, } from "../projection/structuralProjection.js";
9
9
  import { applyTextContentProjection, getTextProjectionReports, subscribeTextProjectionDiagnostics, } from "../projection/textProjection.js";
10
10
  import { isTextContentChangeListValue } from "../changes/types.js";
11
- import { applyWorkspaceProjection, } from "../projection/workspaceProjection.js";
11
+ import { applyDraftProjection, } from "../projection/draftProjection.js";
12
12
  const SHEET_ID = "nudge-ui-styles";
13
13
  function replaceRendererCss(css) {
14
14
  let sheet = document.getElementById(SHEET_ID);
@@ -208,7 +208,7 @@ export function handleReplaceStyles(msg, projectId, workspaceId, cardId) {
208
208
  return false;
209
209
  if (msg.revision <= lastAppliedRevision)
210
210
  return false;
211
- applyWorkspaceProjection(rendererDocumentProjectionAdapter, projectionFromReplaceStylesMessage(msg));
211
+ applyDraftProjection(rendererDocumentProjectionAdapter, projectionFromReplaceStylesMessage(msg));
212
212
  lastStructuralReportRevision = msg.revision;
213
213
  lastRenderedInstanceReportRevision = msg.revision;
214
214
  lastTextProjectionReportRevision = msg.revision;
@@ -1,4 +1,4 @@
1
- import { MAX_CAMERA_ZOOM } from "./canvasStore.js";
1
+ import { MAX_CAMERA_ZOOM } from "./viewStore.js";
2
2
  import { CANVAS_TOOLBAR_MAX_SCALE } from "./toolbarScale.js";
3
3
  /**
4
4
  * Keep the resize handle usable while the board is zoomed without allowing
@@ -1,46 +1,33 @@
1
- import { type SerializableChange } from "../changes/codecs.js";
2
- import { type CanvasMode } from "./canvasStore.js";
3
- import { type StructuralChange } from "../projection/structuralProjection.js";
1
+ import { type FrameContent } from "./frameContent.js";
2
+ import { type FrameGroup } from "./canvasStore.js";
3
+ import { type CanvasPresentation, type CanvasMode } from "./viewStore.js";
4
4
  import { type ClipboardHandoffSnapshot } from "../prompt/clipboardHandoff.js";
5
- declare const SCHEMA_VERSION = 12;
5
+ declare const SCHEMA_VERSION = 17;
6
6
  declare function storageKey(projectId: string): string;
7
7
  export interface SerializableCard {
8
8
  id: string;
9
- url: string;
9
+ content: FrameContent;
10
10
  title: string | null;
11
+ groupId?: string;
11
12
  x: number;
12
13
  y: number;
13
14
  width: number;
14
15
  height: number;
15
- comparisonGroupId?: string;
16
- }
17
- export interface SerializableComparisonGroup {
18
- id: string;
19
- label: string;
20
- owner: "agent";
21
- agentId: string;
22
- cardIds: string[];
23
- routes: Array<{
24
- url: string;
25
- title?: string | null;
26
- label?: string;
27
- }>;
28
16
  }
29
17
  export interface DurableSession {
30
18
  schemaVersion: typeof SCHEMA_VERSION;
31
19
  projectId: string;
32
20
  mode: CanvasMode;
21
+ presentation: CanvasPresentation;
22
+ groups: readonly FrameGroup[];
33
23
  inspectUrl: string;
34
24
  cards: SerializableCard[];
35
- comparisonGroups: SerializableComparisonGroup[];
36
25
  camera: {
37
26
  x: number;
38
27
  y: number;
39
28
  zoom: number;
40
29
  };
41
30
  focusedCardId?: string | null;
42
- changes: SerializableChange[];
43
- structuralChanges: StructuralChange[];
44
31
  clipboardHandoff: ClipboardHandoffSnapshot | null;
45
32
  }
46
33
  export type { SerializableChange, SerializableComponentChange, SerializableElementChange, SerializableTextContentChange, SerializableTokenChange, SerializableTokenRef, } from "../changes/codecs.js";
@@ -51,6 +38,8 @@ export interface HydrationResult {
51
38
  export declare function serializeSession(): DurableSession;
52
39
  export declare function persistSession(): boolean;
53
40
  export declare function hydrateSession(): HydrationResult;
41
+ /** Clears the active frame's edit draft while preserving canvas geometry and other drafts. */
42
+ export declare function clearSelectedFrameChanges(): void;
54
43
  export declare function clearSession(): void;
55
44
  export declare function enableAutoSave(): void;
56
45
  /**
@@ -59,12 +48,8 @@ export declare function enableAutoSave(): void;
59
48
  * refresh or close inside the debounce window still flushes on `beforeunload`.
60
49
  */
61
50
  export declare function scheduleAutoSave(): void;
62
- /**
63
- * Canvas mode/card/camera changes persist immediately (synchronous) so a
64
- * refresh always restores the workspace, even inside the edit-autosave
65
- * debounce window. Edit autosave stays coalesced behind the trailing timer.
66
- */
67
51
  export declare function scheduleCanvasSave(): void;
52
+ export declare function flushAutoSave(): void;
68
53
  export declare function getRestoreCount(): number;
69
54
  export declare function setRestoreCount(count: number): void;
70
55
  export declare function clearRestoreCount(): void;
@@ -1,19 +1,20 @@
1
- import { getWorkspaceChanges } from "../changes/workspaceChanges.js";
2
- import { loadWorkspaceChanges } from "../changes/changesLog.js";
3
- import { deserializeChange, isSerializableChange, serializeChange, } from "../changes/codecs.js";
4
- import { getCanvasMode, getCanvasCards, getCanvasComparisonGroups, getFocusedCardId, getBoardCamera, setBoardCamera, hydrateCanvasStore, removeCanvasCard, } from "./canvasStore.js";
1
+ import { isFrameContent } from "./frameContent.js";
2
+ import { clearComments } from "../comments/store.js";
3
+ import { documentTarget, targetFromKey } from "../drafts/model.js";
4
+ import { canEditDraft, getActiveDraftChanges, isDraftLocked } from "../changes/draftChanges.js";
5
+ import { getFrameGroups, isFrameGroup, getCanvasCards, getFocusedCardId, getSelectedCardId, hydrateCanvasStore, removeCanvasCard, } from "./canvasStore.js";
6
+ import { getCanvasMode, getCanvasPresentation, getBoardCamera, setBoardCamera, } from "./viewStore.js";
5
7
  import { applyRules } from "../projection/managedStylesheet.js";
6
- import { clearWorkspace as clearWorkspaceLog } from "../changes/changesLog.js";
8
+ import { clearActiveDraft } from "../changes/changesLog.js";
7
9
  import { removeManagedSheet } from "../projection/managedStylesheet.js";
8
10
  import { setSelectedElement } from "../selection/selectionStore.js";
9
11
  import { canWriteWorkspace } from "./workspaceLease.js";
10
- import { isStructuralChange, resetStructuralDeleteProjection, } from "../projection/structuralProjection.js";
12
+ import { resetStructuralDeleteProjection, } from "../projection/structuralProjection.js";
11
13
  import { projectToAllReadyCards } from "./projection.js";
12
14
  import { getNudgeUiRuntimeConfig } from "../runtime/runtimeConfig.js";
13
15
  import { clearClipboardHandoff, getClipboardHandoffSnapshot, hydrateClipboardHandoff, isClipboardHandoffSnapshot, } from "../prompt/clipboardHandoff.js";
14
- // v12 is the first durable-session schema after the edit model and preview
15
- // diagnostic split. Previous session shapes are intentionally incompatible.
16
- const SCHEMA_VERSION = 12;
16
+ import { activateDraftForCard, clearDrafts, persistDrafts } from "../drafts/store.js";
17
+ const SCHEMA_VERSION = 17;
17
18
  const STORAGE_PREFIX = "nudge-ui";
18
19
  function projectId() {
19
20
  return getNudgeUiRuntimeConfig().projectId;
@@ -21,9 +22,6 @@ function projectId() {
21
22
  function isFiniteNumber(value) {
22
23
  return typeof value === "number" && Number.isFinite(value);
23
24
  }
24
- function isRecord(value) {
25
- return value !== null && typeof value === "object" && !Array.isArray(value);
26
- }
27
25
  function isSameOriginUrl(value) {
28
26
  if (typeof value !== "string")
29
27
  return false;
@@ -34,95 +32,32 @@ function isSameOriginUrl(value) {
34
32
  return false;
35
33
  }
36
34
  }
37
- function hasOnlyKeys(value, allowed) {
38
- return Object.keys(value).every((key) => allowed.includes(key));
39
- }
40
- function isSerializableComparisonGroup(value) {
41
- if (!isRecord(value)
42
- || !hasOnlyKeys(value, ["id", "label", "owner", "agentId", "cardIds", "routes"])
43
- || typeof value.id !== "string"
44
- || value.id.length === 0
45
- || value.id.length > 256
46
- || typeof value.label !== "string"
47
- || value.label.length === 0
48
- || value.label.length > 160
49
- || value.owner !== "agent"
50
- || typeof value.agentId !== "string"
51
- || value.agentId.length === 0
52
- || value.agentId.length > 256
53
- || !Array.isArray(value.cardIds)
54
- || value.cardIds.length === 0
55
- || value.cardIds.length > 64
56
- || !Array.isArray(value.routes)
57
- || value.routes.length === 0
58
- || value.routes.length > 64) {
59
- return false;
60
- }
61
- if (!value.cardIds.every((cardId) => typeof cardId === "string" && cardId.length > 0 && cardId.length <= 256)) {
62
- return false;
63
- }
64
- return value.routes.every((route) => {
65
- if (!isRecord(route)
66
- || !hasOnlyKeys(route, ["url", "title", "label"])
67
- || !isSameOriginUrl(route.url)
68
- || (route.title !== undefined && route.title !== null
69
- && (typeof route.title !== "string" || route.title.length > 512))
70
- || (route.label !== undefined
71
- && (typeof route.label !== "string" || route.label.length > 160))) {
72
- return false;
73
- }
74
- return true;
75
- });
76
- }
77
35
  function storageKey(projectId) {
78
36
  return `${STORAGE_PREFIX}:${projectId}:v${SCHEMA_VERSION}`;
79
37
  }
80
38
  function buildSession() {
81
- const workspace = getWorkspaceChanges();
82
- const changes = workspace.changes;
83
- const serializableChanges = [];
84
- for (const change of changes) {
85
- const serialized = serializeChange(change);
86
- // serializeChange already fails closed, but re-validate here so a future
87
- // serializer skew can never poison the whole durable session.
88
- if (serialized && isSerializableChange(serialized))
89
- serializableChanges.push(serialized);
90
- }
91
39
  const cards = getCanvasCards();
92
- const comparisonGroups = getCanvasComparisonGroups();
93
40
  const camera = getBoardCamera();
94
41
  const mode = getCanvasMode();
95
42
  return {
96
43
  schemaVersion: SCHEMA_VERSION,
97
44
  projectId: projectId(),
98
45
  mode,
46
+ presentation: getCanvasPresentation(),
47
+ groups: getFrameGroups(),
99
48
  inspectUrl: window.location.href,
100
49
  cards: cards.map((c) => ({
101
50
  id: c.id,
102
- url: c.url,
51
+ content: c.content.kind === "route" ? { kind: "route", url: c.content.url } : { ...c.content },
103
52
  title: c.title,
53
+ ...(c.groupId ? { groupId: c.groupId } : {}),
104
54
  x: c.x,
105
55
  y: c.y,
106
56
  width: c.width,
107
57
  height: c.height,
108
- ...(c.comparisonGroupId ? { comparisonGroupId: c.comparisonGroupId } : {}),
109
- })),
110
- comparisonGroups: comparisonGroups.map((group) => ({
111
- id: group.id,
112
- label: group.label,
113
- owner: "agent",
114
- agentId: group.agentId,
115
- cardIds: [...group.cardIds],
116
- routes: group.routes.map((route) => ({
117
- url: route.url,
118
- ...(route.title === undefined ? {} : { title: route.title }),
119
- ...(route.label === undefined ? {} : { label: route.label }),
120
- })),
121
58
  })),
122
59
  camera: { x: camera.x, y: camera.y, zoom: camera.zoom },
123
60
  focusedCardId: getFocusedCardId(),
124
- changes: serializableChanges,
125
- structuralChanges: workspace.structuralChanges.map((change) => ({ ...change })),
126
61
  clipboardHandoff: getClipboardHandoffSnapshot(),
127
62
  };
128
63
  }
@@ -142,6 +77,7 @@ function persistSessionUnchecked() {
142
77
  if (!projectId())
143
78
  return false;
144
79
  try {
80
+ persistDrafts();
145
81
  const session = buildSession();
146
82
  localStorage.setItem(storageKey(projectId()), JSON.stringify(session));
147
83
  return true;
@@ -203,7 +139,7 @@ export function hydrateSession() {
203
139
  const candidate = card;
204
140
  if (!card || typeof card !== "object" ||
205
141
  typeof candidate.id !== "string" ||
206
- !isSameOriginUrl(candidate.url) ||
142
+ !isFrameContent(candidate.content, window.location.origin) ||
207
143
  !isFiniteNumber(candidate.x) ||
208
144
  !isFiniteNumber(candidate.y) ||
209
145
  !isFiniteNumber(candidate.width) ||
@@ -216,70 +152,20 @@ export function hydrateSession() {
216
152
  const c = card;
217
153
  serializableCards.push({
218
154
  id: c.id,
219
- url: c.url,
155
+ content: c.content,
220
156
  title: typeof c.title === "string" ? c.title : null,
157
+ ...(typeof c.groupId === "string" && c.groupId.length <= 256 ? { groupId: c.groupId } : {}),
221
158
  x: c.x,
222
159
  y: c.y,
223
160
  width: c.width,
224
161
  height: c.height,
225
- ...(typeof c.comparisonGroupId === "string"
226
- ? { comparisonGroupId: c.comparisonGroupId }
227
- : {}),
228
162
  });
229
163
  }
230
164
  if (new Set(serializableCards.map((card) => card.id)).size !== serializableCards.length) {
231
165
  safeDiscard();
232
166
  return { restored: false, changeCount: 0 };
233
167
  }
234
- const comparisonGroupsRaw = s.comparisonGroups;
235
- if (!Array.isArray(comparisonGroupsRaw) || comparisonGroupsRaw.length > 128) {
236
- safeDiscard();
237
- return { restored: false, changeCount: 0 };
238
- }
239
- const serializableComparisonGroups = [];
240
- for (const group of comparisonGroupsRaw) {
241
- if (!isSerializableComparisonGroup(group)) {
242
- safeDiscard();
243
- return { restored: false, changeCount: 0 };
244
- }
245
- serializableComparisonGroups.push({
246
- id: group.id,
247
- label: group.label,
248
- owner: "agent",
249
- agentId: group.agentId,
250
- cardIds: [...group.cardIds],
251
- routes: group.routes.map((route) => ({ ...route })),
252
- });
253
- }
254
- if (new Set(serializableComparisonGroups.map((group) => group.id)).size
255
- !== serializableComparisonGroups.length) {
256
- safeDiscard();
257
- return { restored: false, changeCount: 0 };
258
- }
259
168
  const cardsById = new Map(serializableCards.map((card) => [card.id, card]));
260
- const groupedCardIds = new Set();
261
- for (const group of serializableComparisonGroups) {
262
- if (group.cardIds.length !== group.routes.length) {
263
- safeDiscard();
264
- return { restored: false, changeCount: 0 };
265
- }
266
- for (const [routeIndex, cardId] of group.cardIds.entries()) {
267
- const card = cardsById.get(cardId);
268
- const route = group.routes[routeIndex];
269
- if (!card || !route || card.comparisonGroupId !== group.id || groupedCardIds.has(cardId)
270
- || card.url !== route.url) {
271
- safeDiscard();
272
- return { restored: false, changeCount: 0 };
273
- }
274
- groupedCardIds.add(cardId);
275
- }
276
- }
277
- for (const card of serializableCards) {
278
- if (card.comparisonGroupId && !serializableComparisonGroups.some((group) => group.id === card.comparisonGroupId)) {
279
- safeDiscard();
280
- return { restored: false, changeCount: 0 };
281
- }
282
- }
283
169
  const cameraRaw = s.camera;
284
170
  if (!cameraRaw || typeof cameraRaw !== "object" ||
285
171
  !isFiniteNumber(cameraRaw.x) ||
@@ -288,35 +174,9 @@ export function hydrateSession() {
288
174
  safeDiscard();
289
175
  return { restored: false, changeCount: 0 };
290
176
  }
291
- const changesRaw = s.changes;
292
- if (!Array.isArray(changesRaw)) {
293
- safeDiscard();
294
- return { restored: false, changeCount: 0 };
295
- }
296
- const deserializedChanges = [];
297
- const textChangeIds = new Set();
298
- for (const c of changesRaw) {
299
- if (!isSerializableChange(c)) {
300
- safeDiscard();
301
- return { restored: false, changeCount: 0 };
302
- }
303
- if (c.kind === "text-content") {
304
- if (textChangeIds.has(c.id)) {
305
- safeDiscard();
306
- return { restored: false, changeCount: 0 };
307
- }
308
- textChangeIds.add(c.id);
309
- }
310
- try {
311
- deserializedChanges.push(deserializeChange(c));
312
- }
313
- catch {
314
- safeDiscard();
315
- return { restored: false, changeCount: 0 };
316
- }
317
- }
318
- const structuralChanges = s.structuralChanges;
319
- if (!Array.isArray(structuralChanges) || !structuralChanges.every(isStructuralChange)) {
177
+ const presentation = s.presentation ?? "focus";
178
+ const groups = Array.isArray(s.groups) ? s.groups.filter((group) => group?.kind !== "linked") : s.groups ?? [];
179
+ if ((presentation !== "focus" && presentation !== "canvas") || !Array.isArray(groups) || !groups.every(isFrameGroup)) {
320
180
  safeDiscard();
321
181
  return { restored: false, changeCount: 0 };
322
182
  }
@@ -339,10 +199,7 @@ export function hydrateSession() {
339
199
  safeDiscard();
340
200
  return { restored: false, changeCount: 0 };
341
201
  }
342
- hydrateCanvasStore(s.mode, serializableCards, camera, serializableComparisonGroups, focusedCardId);
343
- // Structural intent and instance evidence become visible atomically. The
344
- // projection layer preserves structural-first document application order.
345
- loadWorkspaceChanges(deserializedChanges, structuralChanges);
202
+ hydrateCanvasStore(s.mode, serializableCards, camera, focusedCardId, presentation, groups);
346
203
  hydrateClipboardHandoff(clipboardHandoff);
347
204
  // A different URL means the user intentionally navigated while Inspect was
348
205
  // active. Keep the durable edits, but adopt the new route instead of
@@ -350,8 +207,7 @@ export function hydrateSession() {
350
207
  // match and restoration remains unchanged.
351
208
  if (inspectRouteChanged)
352
209
  persistSessionUnchecked();
353
- projectToAllReadyCards();
354
- return { restored: true, changeCount: deserializedChanges.length + structuralChanges.length };
210
+ return { restored: true, changeCount: 0 };
355
211
  }
356
212
  function safeDiscard() {
357
213
  try {
@@ -361,7 +217,22 @@ function safeDiscard() {
361
217
  // ignore
362
218
  }
363
219
  }
220
+ /** Clears the active frame's edit draft while preserving canvas geometry and other drafts. */
221
+ export function clearSelectedFrameChanges() {
222
+ if (!canEditDraft())
223
+ return;
224
+ const cardId = getSelectedCardId() ?? getFocusedCardId();
225
+ if (cardId)
226
+ activateDraftForCard(cardId);
227
+ clearComments(targetFromKey(getActiveDraftChanges().draftId) ?? documentTarget(window.location.href));
228
+ clearActiveDraft();
229
+ setSelectedElement(null);
230
+ projectToAllReadyCards();
231
+ scheduleAutoSave();
232
+ }
364
233
  export function clearSession() {
234
+ if (isDraftLocked())
235
+ return;
365
236
  if (!canWriteWorkspace())
366
237
  return;
367
238
  try {
@@ -370,8 +241,10 @@ export function clearSession() {
370
241
  catch {
371
242
  // ignore
372
243
  }
373
- clearWorkspaceLog();
244
+ clearComments();
245
+ clearActiveDraft();
374
246
  clearClipboardHandoff();
247
+ clearDrafts();
375
248
  resetStructuralDeleteProjection();
376
249
  removeManagedSheet();
377
250
  setSelectedElement(null);
@@ -415,27 +288,14 @@ export function scheduleAutoSave() {
415
288
  autoSaveDirty = false;
416
289
  }, AUTOSAVE_DEBOUNCE_MS);
417
290
  }
418
- /**
419
- * Canvas mode/card/camera changes persist immediately (synchronous) so a
420
- * refresh always restores the workspace, even inside the edit-autosave
421
- * debounce window. Edit autosave stays coalesced behind the trailing timer.
422
- */
423
- export function scheduleCanvasSave() {
424
- if (!autoSaveEnabled)
425
- return;
426
- if (persistSession())
427
- autoSaveDirty = false;
428
- }
429
- function flushAutoSave() {
291
+ export function scheduleCanvasSave() { scheduleAutoSave(); }
292
+ export function flushAutoSave() {
430
293
  if (autosaveTimer !== null) {
431
294
  clearTimeout(autosaveTimer);
432
295
  autosaveTimer = null;
433
296
  }
434
297
  if (!autoSaveDirty)
435
298
  return;
436
- // Keep the normal lease gate on the unload path. The controller registers
437
- // this listener before releaseLease, so a current owner can flush a first
438
- // write without allowing a stale tab to overwrite the new owner's session.
439
299
  if (persistSession())
440
300
  autoSaveDirty = false;
441
301
  }
@@ -1,10 +1,12 @@
1
+ import { draftIdForCard } from "../drafts/store.js";
1
2
  import { isPreviewableChange, isTokenChange, } from "../changes/changesLog.js";
2
3
  import { changeKey } from "../changes/model.js";
3
4
  import { beginPreviewAttempt, clearPreviewDiagnostics, getHostPreviewDocument, notifyPreviewDiagnostics, publishPreviewDiagnostic, } from "../changes/previewDiagnostics.js";
4
- import { getWorkspaceChanges } from "../changes/workspaceChanges.js";
5
+ import { getActiveDraftChanges } from "../changes/draftChanges.js";
5
6
  import { getCanvasPreviewDocument, getRegisteredFrames } from "./projection.js";
6
7
  import { findCanvasFrameBySource, PROJECT_ID, WORKSPACE_ID } from "./projection.js";
7
- import { getCanvasCards, getCanvasMode } from "./canvasStore.js";
8
+ import { getCanvasCards } from "./canvasStore.js";
9
+ import { getCanvasMode } from "./viewStore.js";
8
10
  import { isRendererMessageFor } from "./frameProtocol.js";
9
11
  import { getNudgeUiRuntimeConfig } from "../runtime/runtimeConfig.js";
10
12
  const VERIFICATION_TIMEOUT_MS = 5000;
@@ -82,6 +84,8 @@ function getPreviewDocuments() {
82
84
  const documents = getCanvasMode() === "canvas" ? [] : [getHostPreviewDocument()];
83
85
  const frames = getRegisteredFrames();
84
86
  for (const card of getCanvasCards()) {
87
+ if (draftIdForCard(card.id) && draftIdForCard(card.id) !== getActiveDraftChanges().draftId)
88
+ continue;
85
89
  if (frames.has(card.id))
86
90
  documents.push(getCanvasPreviewDocument(card.id));
87
91
  }
@@ -89,7 +93,7 @@ function getPreviewDocuments() {
89
93
  }
90
94
  function beginPreviewAttempts() {
91
95
  const attempts = new Map();
92
- const revision = getWorkspaceChanges().revision;
96
+ const revision = getActiveDraftChanges().revision;
93
97
  for (const document of getPreviewDocuments()) {
94
98
  const attempt = beginPreviewAttempt(document, revision);
95
99
  if (attempt)
@@ -115,6 +119,8 @@ function gatherMatchEvidence(changes) {
115
119
  const frames = getRegisteredFrames();
116
120
  const cards = getCanvasCards();
117
121
  for (const card of cards) {
122
+ if (draftIdForCard(card.id) && draftIdForCard(card.id) !== getActiveDraftChanges().draftId)
123
+ continue;
118
124
  const frame = frames.get(card.id);
119
125
  if (!frame || !frame.contentWindow)
120
126
  continue;
@@ -0,0 +1,5 @@
1
+ import { type RefObject } from "react";
2
+ import { type CanvasCard } from "./canvasStore.js";
3
+ import { type CanvasCamera, type CanvasPresentation } from "./viewStore.js";
4
+ /** Animates frame creation and layout changes without delaying direct manipulation. */
5
+ export declare function useCanvasLayoutAnimation(boardRef: RefObject<HTMLDivElement | null>, cards: readonly CanvasCard[], camera: CanvasCamera, presentation: CanvasPresentation): void;