nudge-ui 0.2.2 → 0.2.4

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 (244) hide show
  1. package/README.md +164 -46
  2. package/dist/client.mjs +260 -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 +15 -7
  26. package/dist/inspector/agent/verification.js +113 -41
  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 +135 -38
  31. package/dist/inspector/canvas/CanvasElementOverlay.js +166 -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 +270 -122
  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/frameScrollbarStyles.d.ts +3 -0
  48. package/dist/inspector/canvas/frameScrollbarStyles.js +21 -0
  49. package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -1
  50. package/dist/inspector/canvas/keyboardShortcuts.js +4 -4
  51. package/dist/inspector/canvas/layout.d.ts +52 -0
  52. package/dist/inspector/canvas/layout.js +106 -0
  53. package/dist/inspector/canvas/projection.d.ts +15 -5
  54. package/dist/inspector/canvas/projection.js +69 -13
  55. package/dist/inspector/canvas/rendererBootstrap.js +32 -4
  56. package/dist/inspector/canvas/rendererElementSelector.js +47 -6
  57. package/dist/inspector/canvas/rendererStylesheet.js +2 -2
  58. package/dist/inspector/canvas/resizeHandleScale.js +1 -1
  59. package/dist/inspector/canvas/sessionStore.d.ts +11 -26
  60. package/dist/inspector/canvas/sessionStore.js +43 -183
  61. package/dist/inspector/canvas/staleChangeDetector.js +9 -3
  62. package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
  63. package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
  64. package/dist/inspector/canvas/viewStore.d.ts +38 -0
  65. package/dist/inspector/canvas/viewStore.js +104 -0
  66. package/dist/inspector/canvas/wheelPan.d.ts +8 -0
  67. package/dist/inspector/canvas/wheelPan.js +8 -0
  68. package/dist/inspector/changes/changesLog.d.ts +9 -6
  69. package/dist/inspector/changes/changesLog.js +48 -32
  70. package/dist/inspector/changes/codecs.d.ts +1 -0
  71. package/dist/inspector/changes/codecs.js +10 -1
  72. package/dist/inspector/changes/draftChanges.d.ts +58 -0
  73. package/dist/inspector/changes/draftChanges.js +304 -0
  74. package/dist/inspector/changes/editModel.d.ts +10 -0
  75. package/dist/inspector/changes/editModel.js +9 -2
  76. package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
  77. package/dist/inspector/changes/previewDiagnostics.js +11 -25
  78. package/dist/inspector/changes/types.d.ts +7 -0
  79. package/dist/inspector/clientManifest.d.ts +1 -1
  80. package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
  81. package/dist/inspector/comments/CommentsOverlay.js +250 -0
  82. package/dist/inspector/comments/element.d.ts +6 -0
  83. package/dist/inspector/comments/element.js +54 -0
  84. package/dist/inspector/comments/store.d.ts +29 -0
  85. package/dist/inspector/comments/store.js +121 -0
  86. package/dist/inspector/comments/verification.d.ts +5 -0
  87. package/dist/inspector/comments/verification.js +52 -0
  88. package/dist/inspector/componentSemantics/adapterRegistry.d.ts +6 -1
  89. package/dist/inspector/componentSemantics/adapterRegistry.js +23 -1
  90. package/dist/inspector/componentSemantics/reactRuntime.d.ts +2 -1
  91. package/dist/inspector/componentSemantics/reactRuntime.js +38 -0
  92. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +4 -2
  93. package/dist/inspector/componentSemantics/runtimeBridge.js +17 -0
  94. package/dist/inspector/componentSemantics/types.d.ts +11 -0
  95. package/dist/inspector/drafts/model.d.ts +35 -0
  96. package/dist/inspector/drafts/model.js +45 -0
  97. package/dist/inspector/drafts/store.d.ts +28 -0
  98. package/dist/inspector/drafts/store.js +231 -0
  99. package/dist/inspector/index.js +20 -27
  100. package/dist/inspector/inline-text/inlineTextEditor.js +7 -0
  101. package/dist/inspector/inspection/useBrowserCssInspection.js +15 -1
  102. package/dist/inspector/iterations/capture.d.ts +2 -0
  103. package/dist/inspector/iterations/capture.js +174 -0
  104. package/dist/inspector/iterations/client.d.ts +7 -0
  105. package/dist/inspector/iterations/client.js +52 -0
  106. package/dist/inspector/iterations/coordinator.d.ts +15 -0
  107. package/dist/inspector/iterations/coordinator.js +130 -0
  108. package/dist/inspector/iterations/prompt.d.ts +3 -0
  109. package/dist/inspector/iterations/prompt.js +26 -0
  110. package/dist/inspector/overlay/DomNavigator.js +21 -15
  111. package/dist/inspector/overlay/interactionStyles.js +3 -1
  112. package/dist/inspector/overlay/spacingGestures.d.ts +13 -1
  113. package/dist/inspector/overlay/spacingGestures.js +82 -5
  114. package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
  115. package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
  116. package/dist/inspector/projection/renderedInstance.d.ts +17 -3
  117. package/dist/inspector/projection/renderedInstance.js +62 -11
  118. package/dist/inspector/projection/structuralProjection.d.ts +2 -1
  119. package/dist/inspector/projection/structuralProjection.js +21 -17
  120. package/dist/inspector/projection/textProjection.d.ts +2 -1
  121. package/dist/inspector/projection/textProjection.js +64 -15
  122. package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
  123. package/dist/inspector/prompt/clipboardHandoff.d.ts +15 -18
  124. package/dist/inspector/prompt/clipboardHandoff.js +97 -143
  125. package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
  126. package/dist/inspector/prompt/generatePrompt.js +22 -17
  127. package/dist/inspector/runtime/runtimeConfig.d.ts +9 -0
  128. package/dist/inspector/runtime/runtimeConfig.js +24 -0
  129. package/dist/inspector/selection/sourceSite.d.ts +12 -0
  130. package/dist/inspector/selection/sourceSite.js +28 -0
  131. package/dist/inspector/settings/SettingsDialog.js +21 -8
  132. package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
  133. package/dist/inspector/shell/AgentStatusButton.js +36 -0
  134. package/dist/inspector/shell/AppShell.js +4 -2
  135. package/dist/inspector/shell/ChangesLog.js +46 -26
  136. package/dist/inspector/shell/CopyPromptButton.js +184 -93
  137. package/dist/inspector/shell/EmptyState.js +9 -8
  138. package/dist/inspector/shell/InspectorShell.js +13 -21
  139. package/dist/inspector/shell/PeekOriginalButton.js +24 -23
  140. package/dist/inspector/shell/originalPreview.js +5 -5
  141. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  142. package/dist/inspector/shell/shortcuts.js +11 -0
  143. package/dist/inspector/sketch/SketchEditor.js +14 -14
  144. package/dist/inspector/sketch/SketchEntryButton.js +1 -1
  145. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
  146. package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
  147. package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
  148. package/dist/inspector/sketch/SketchOverlay.js +7 -7
  149. package/dist/inspector/sketch/SketchWorkspace.js +18 -12
  150. package/dist/inspector/sketch/interaction.d.ts +1 -0
  151. package/dist/inspector/sketch/interaction.js +8 -4
  152. package/dist/inspector/sketch/model.d.ts +5 -0
  153. package/dist/inspector/sketch/model.js +6 -2
  154. package/dist/inspector/sketch/raster.d.ts +1 -2
  155. package/dist/inspector/sketch/raster.js +68 -4
  156. package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
  157. package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
  158. package/dist/inspector/styleEditors/BorderEditor.js +49 -78
  159. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
  160. package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
  161. package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
  162. package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
  163. package/dist/inspector/styleEditors/ColorPicker.js +10 -4
  164. package/dist/inspector/styleEditors/GapField.js +3 -5
  165. package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
  166. package/dist/inspector/styleEditors/GridChildSection.js +18 -9
  167. package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
  168. package/dist/inspector/styleEditors/GridPicker.js +8 -2
  169. package/dist/inspector/styleEditors/GridSection.js +1 -1
  170. package/dist/inspector/styleEditors/GridValueField.js +19 -14
  171. package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
  172. package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
  173. package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
  174. package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
  175. package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
  176. package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
  177. package/dist/inspector/styleEditors/LayoutSection.js +32 -11
  178. package/dist/inspector/styleEditors/SpacingBox.js +7 -5
  179. package/dist/inspector/styleEditors/Typography.js +11 -3
  180. package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
  181. package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
  182. package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
  183. package/dist/inspector/styleEditors/gradientValue.js +114 -0
  184. package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
  185. package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
  186. package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
  187. package/dist/inspector/styleEditors/layoutValue.js +0 -42
  188. package/dist/inspector/styleEditors/nudgeValue.d.ts +2 -2
  189. package/dist/inspector/styleEditors/nudgeValue.js +36 -8
  190. package/dist/inspector/tokens/TokenField.d.ts +7 -0
  191. package/dist/inspector/tokens/TokenField.js +35 -25
  192. package/dist/inspector/tokens/TokenLabel.js +5 -14
  193. package/dist/inspector/tokens/editActions.d.ts +1 -1
  194. package/dist/inspector/tokens/editActions.js +7 -1
  195. package/dist/inspector/ui/AtRuleContext.js +4 -6
  196. package/dist/inspector/ui/Button.d.ts +6 -2
  197. package/dist/inspector/ui/Button.js +4 -3
  198. package/dist/inspector/ui/ControlSurface.d.ts +2 -2
  199. package/dist/inspector/ui/ControlSurface.js +4 -3
  200. package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
  201. package/dist/inspector/ui/InlineStyleWarning.js +13 -0
  202. package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
  203. package/dist/inspector/ui/InspectorPopover.js +4 -5
  204. package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
  205. package/dist/inspector/ui/PopoverListbox.js +8 -7
  206. package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
  207. package/dist/inspector/ui/SegmentedControl.js +2 -2
  208. package/dist/inspector/ui/Select.d.ts +6 -1
  209. package/dist/inspector/ui/Select.js +7 -11
  210. package/dist/inspector/ui/SideValuesField.js +1 -1
  211. package/dist/inspector/ui/Tooltip.d.ts +26 -0
  212. package/dist/inspector/ui/Tooltip.js +32 -0
  213. package/dist/inspector/ui/classNames.d.ts +2 -0
  214. package/dist/inspector/ui/classNames.js +4 -0
  215. package/dist/inspector/ui/customCursors.d.ts +23 -0
  216. package/dist/inspector/ui/customCursors.js +122 -0
  217. package/dist/inspector/ui/dragCursor.d.ts +0 -2
  218. package/dist/inspector/ui/dragCursor.js +2 -10
  219. package/dist/inspector/ui/portalContainer.d.ts +2 -0
  220. package/dist/inspector/ui/portalContainer.js +6 -0
  221. package/dist/inspector/ui/styles.js +29 -21
  222. package/dist/inspector/workspace/commands.d.ts +9 -0
  223. package/dist/inspector/workspace/commands.js +82 -0
  224. package/dist/inspector/workspace/controller.d.ts +4 -0
  225. package/dist/inspector/workspace/controller.js +66 -0
  226. package/dist/inspector/workspace/handoff.d.ts +42 -0
  227. package/dist/inspector/workspace/handoff.js +115 -0
  228. package/dist/inspector/workspace/sentItems.d.ts +19 -0
  229. package/dist/inspector/workspace/sentItems.js +51 -0
  230. package/dist/inspector/workspace/timeline.d.ts +30 -0
  231. package/dist/inspector/workspace/timeline.js +108 -0
  232. package/dist/internal/agent-protocol.d.ts +19 -2
  233. package/dist/internal/agent-protocol.js +13 -0
  234. package/dist/nudge-ui.mjs +558 -311
  235. package/dist/project/artifacts.d.ts +13 -0
  236. package/dist/project/artifacts.js +253 -0
  237. package/dist/transport/artifacts.d.ts +8 -0
  238. package/dist/transport/artifacts.js +9 -0
  239. package/dist/transport/editor.js +3 -1
  240. package/dist/transport/routes.d.ts +2 -0
  241. package/dist/transport/routes.js +2 -0
  242. package/package.json +7 -3
  243. package/dist/inspector/changes/workspaceChanges.d.ts +0 -47
  244. package/dist/inspector/changes/workspaceChanges.js +0 -265
@@ -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;
@@ -0,0 +1,107 @@
1
+ import { iterationId } from "./frameContent.js";
2
+ import { useEffect, useLayoutEffect, useRef } from "react";
3
+ import {} from "./canvasStore.js";
4
+ import { setCanvasLayoutTransitioning } from "./viewStore.js";
5
+ const SLIDE_OPTIONS = { duration: 300, easing: "ease-out" };
6
+ const CAMERA_OPTIONS = { duration: 150, easing: "ease-out" };
7
+ /** Animates frame creation and layout changes without delaying direct manipulation. */
8
+ export function useCanvasLayoutAnimation(boardRef, cards, camera, presentation) {
9
+ const previous = useRef({ cards, camera, presentation });
10
+ const animations = useRef([]);
11
+ const generation = useRef(0);
12
+ useEffect(() => {
13
+ const board = boardRef.current;
14
+ const cancel = () => {
15
+ generation.current += 1;
16
+ setCanvasLayoutTransitioning(false);
17
+ for (const animation of animations.current)
18
+ animation.cancel();
19
+ animations.current = [];
20
+ };
21
+ const cancelWheel = (event) => {
22
+ if (board && event.composedPath().includes(board))
23
+ cancel();
24
+ };
25
+ board?.addEventListener("pointerdown", cancel, true);
26
+ window.addEventListener("wheel", cancelWheel, true);
27
+ return () => {
28
+ cancel();
29
+ board?.removeEventListener("pointerdown", cancel, true);
30
+ window.removeEventListener("wheel", cancelWheel, true);
31
+ };
32
+ }, [boardRef]);
33
+ useLayoutEffect(() => {
34
+ const before = previous.current;
35
+ previous.current = { cards, camera, presentation };
36
+ const oldCards = new Map(before.cards.map((card) => [card.id, card]));
37
+ const layoutChanged = cards.some((card) => {
38
+ const old = oldCards.get(card.id);
39
+ return (!old && card.animateEntrance !== false && (card.entrance === "linked" || iterationId(card.content))) || (old && iterationId(card.content) && iterationId(old.content) !== iterationId(card.content));
40
+ });
41
+ const cameraChanged = camera.x !== before.camera.x || camera.y !== before.camera.y || camera.zoom !== before.camera.zoom;
42
+ const shouldAnimate = (layoutChanged || (cameraChanged && animations.current.length > 0))
43
+ && presentation === "canvas" && before.presentation === "canvas"
44
+ && !window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
45
+ const board = boardRef.current;
46
+ const animate = (element, frames, options = SLIDE_OPTIONS) => {
47
+ if (shouldAnimate && typeof element.animate === "function") {
48
+ animations.current.push(element.animate(frames, options));
49
+ }
50
+ };
51
+ if (shouldAnimate) {
52
+ generation.current += 1;
53
+ setCanvasLayoutTransitioning(true);
54
+ if (layoutChanged) {
55
+ for (const animation of animations.current)
56
+ animation.cancel();
57
+ animations.current = [];
58
+ }
59
+ for (const element of board?.querySelectorAll(".canvas-card") ?? []) {
60
+ const card = cards.find((item) => item.id === element.dataset.cardId);
61
+ if (!card)
62
+ continue;
63
+ const old = oldCards.get(card.id);
64
+ if (!old) {
65
+ if (card.animateEntrance === false)
66
+ continue;
67
+ const slideRight = Boolean(card.entrance === "linked");
68
+ animate(element, [
69
+ { left: `${card.x - (slideRight ? 200 : 0)}px`, top: `${card.y - (slideRight ? 0 : 200)}px`, opacity: 0 },
70
+ { left: `${card.x}px`, top: `${card.y}px`, opacity: 1 },
71
+ ]);
72
+ }
73
+ else if (old.x !== card.x || old.y !== card.y) {
74
+ animate(element, [{ left: `${old.x}px`, top: `${old.y}px` }, { left: `${card.x}px`, top: `${card.y}px` }]);
75
+ }
76
+ }
77
+ const content = board?.querySelector(".canvas-workspace__board-content");
78
+ if (content && cameraChanged) {
79
+ const current = animations.current.find((animation) => animation.playState === "running"
80
+ && animation.effect?.target === content);
81
+ const from = current ? getComputedStyle(content).transform
82
+ : `translate(${before.camera.x}px, ${before.camera.y}px) scale(${before.camera.zoom})`;
83
+ current?.cancel();
84
+ animate(content, [
85
+ { transform: from },
86
+ { transform: `translate(${camera.x}px, ${camera.y}px) scale(${camera.zoom})` },
87
+ ], CAMERA_OPTIONS);
88
+ }
89
+ }
90
+ if (shouldAnimate) {
91
+ const current = generation.current;
92
+ void Promise.all(animations.current.map((animation) => animation.finished.catch(() => undefined))).then(() => {
93
+ if (generation.current !== current)
94
+ return;
95
+ animations.current = [];
96
+ setCanvasLayoutTransitioning(false);
97
+ });
98
+ }
99
+ else if (presentation !== before.presentation) {
100
+ generation.current += 1;
101
+ for (const animation of animations.current)
102
+ animation.cancel();
103
+ animations.current = [];
104
+ setCanvasLayoutTransitioning(false);
105
+ }
106
+ }, [boardRef, cards, camera, presentation]);
107
+ }
@@ -0,0 +1,38 @@
1
+ export type CanvasMode = "inspect" | "canvas";
2
+ export type CanvasPresentation = "focus" | "canvas";
3
+ export interface CanvasCamera {
4
+ x: number;
5
+ y: number;
6
+ zoom: number;
7
+ }
8
+ export declare const MIN_CAMERA_ZOOM = 0.25;
9
+ export declare const MAX_CAMERA_ZOOM = 3;
10
+ export declare const DEFAULT_CAMERA: CanvasCamera;
11
+ export declare function subscribeCanvasView(listener: () => void): () => void;
12
+ export declare function getCanvasMode(): CanvasMode;
13
+ export declare function setCanvasMode(next: CanvasMode): void;
14
+ export declare function getCanvasPresentation(): CanvasPresentation;
15
+ export declare function setCanvasPresentation(next: CanvasPresentation): void;
16
+ /** Holding A temporarily enables app interaction without changing the selected tool. */
17
+ export declare function setTemporaryAppInteraction(value: boolean): void;
18
+ /** Element interactions pause while frame and camera geometry are animated. */
19
+ export declare function setCanvasLayoutTransitioning(value: boolean): void;
20
+ export declare function getCanvasLayoutTransitioning(): boolean;
21
+ export declare function getBoardCamera(): CanvasCamera;
22
+ export declare function setBoardCamera(next: CanvasCamera): void;
23
+ export declare function updateBoardCamera(partial: Partial<CanvasCamera>): void;
24
+ /** Moves the camera to frame content; the initial fit-all then no longer runs. */
25
+ export declare function placeBoardCamera(next: CanvasCamera): void;
26
+ export declare function hasFitAllRan(): boolean;
27
+ export declare function resetFitAllFlag(): void;
28
+ export declare function restoreCanvasView(next: {
29
+ mode: CanvasMode;
30
+ presentation: CanvasPresentation;
31
+ camera: CanvasCamera;
32
+ }): void;
33
+ export declare function useCanvasMode(): CanvasMode;
34
+ export declare function useCanvasPresentation(): CanvasPresentation;
35
+ export declare function useCanvasPresentationTransitioning(): boolean;
36
+ export declare function useTemporaryAppInteraction(): boolean;
37
+ export declare function useCanvasLayoutTransitioning(): boolean;
38
+ export declare function useBoardCamera(): CanvasCamera;
@@ -0,0 +1,104 @@
1
+ import { useSyncExternalStore } from "react";
2
+ export const MIN_CAMERA_ZOOM = 0.25;
3
+ export const MAX_CAMERA_ZOOM = 3;
4
+ export const DEFAULT_CAMERA = { x: 0, y: 0, zoom: 1 };
5
+ /** How the canvas is viewed. Frames, groups, and selection live in the canvas store. */
6
+ let mode = "inspect";
7
+ let presentation = "focus";
8
+ let presentationTransitioning = false;
9
+ let presentationTransitionTimer = null;
10
+ let layoutTransitioning = false;
11
+ let temporaryAppInteraction = false;
12
+ let camera = { ...DEFAULT_CAMERA };
13
+ let cameraPlaced = false;
14
+ const listeners = new Set();
15
+ function notify() {
16
+ for (const listener of listeners)
17
+ listener();
18
+ }
19
+ export function subscribeCanvasView(listener) {
20
+ listeners.add(listener);
21
+ return () => listeners.delete(listener);
22
+ }
23
+ function clampZoom(zoom) {
24
+ return Math.max(MIN_CAMERA_ZOOM, Math.min(MAX_CAMERA_ZOOM, zoom));
25
+ }
26
+ export function getCanvasMode() { return mode; }
27
+ export function setCanvasMode(next) {
28
+ if (mode === next)
29
+ return;
30
+ mode = next;
31
+ notify();
32
+ }
33
+ export function getCanvasPresentation() { return presentation; }
34
+ export function setCanvasPresentation(next) {
35
+ if (presentation === next)
36
+ return;
37
+ presentation = next;
38
+ presentationTransitioning = !window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
39
+ if (presentationTransitionTimer !== null)
40
+ window.clearTimeout(presentationTransitionTimer);
41
+ presentationTransitionTimer = presentationTransitioning
42
+ ? window.setTimeout(() => {
43
+ presentationTransitionTimer = null;
44
+ presentationTransitioning = false;
45
+ notify();
46
+ }, 220)
47
+ : null;
48
+ notify();
49
+ }
50
+ /** Holding A temporarily enables app interaction without changing the selected tool. */
51
+ export function setTemporaryAppInteraction(value) {
52
+ if (temporaryAppInteraction === value)
53
+ return;
54
+ temporaryAppInteraction = value;
55
+ notify();
56
+ }
57
+ /** Element interactions pause while frame and camera geometry are animated. */
58
+ export function setCanvasLayoutTransitioning(value) {
59
+ if (layoutTransitioning === value)
60
+ return;
61
+ layoutTransitioning = value;
62
+ notify();
63
+ }
64
+ export function getCanvasLayoutTransitioning() { return layoutTransitioning; }
65
+ export function getBoardCamera() { return camera; }
66
+ export function setBoardCamera(next) {
67
+ camera = { x: next.x, y: next.y, zoom: clampZoom(next.zoom) };
68
+ notify();
69
+ }
70
+ export function updateBoardCamera(partial) {
71
+ setBoardCamera({ ...camera, ...partial });
72
+ }
73
+ /** Moves the camera to frame content; the initial fit-all then no longer runs. */
74
+ export function placeBoardCamera(next) {
75
+ cameraPlaced = true;
76
+ setBoardCamera(next);
77
+ }
78
+ export function hasFitAllRan() { return cameraPlaced; }
79
+ export function resetFitAllFlag() { cameraPlaced = false; }
80
+ export function restoreCanvasView(next) {
81
+ mode = next.mode;
82
+ presentation = next.presentation;
83
+ camera = { ...next.camera };
84
+ cameraPlaced = true;
85
+ notify();
86
+ }
87
+ export function useCanvasMode() {
88
+ return useSyncExternalStore(subscribeCanvasView, getCanvasMode, getCanvasMode);
89
+ }
90
+ export function useCanvasPresentation() {
91
+ return useSyncExternalStore(subscribeCanvasView, getCanvasPresentation, getCanvasPresentation);
92
+ }
93
+ export function useCanvasPresentationTransitioning() {
94
+ return useSyncExternalStore(subscribeCanvasView, () => presentationTransitioning, () => presentationTransitioning);
95
+ }
96
+ export function useTemporaryAppInteraction() {
97
+ return useSyncExternalStore(subscribeCanvasView, () => temporaryAppInteraction, () => temporaryAppInteraction);
98
+ }
99
+ export function useCanvasLayoutTransitioning() {
100
+ return useSyncExternalStore(subscribeCanvasView, getCanvasLayoutTransitioning, getCanvasLayoutTransitioning);
101
+ }
102
+ export function useBoardCamera() {
103
+ return useSyncExternalStore(subscribeCanvasView, getBoardCamera, getBoardCamera);
104
+ }
@@ -0,0 +1,8 @@
1
+ /** Converts wheel units to pixel distances for consistent canvas panning. */
2
+ export declare function wheelPanDelta(event: Pick<WheelEvent, "deltaX" | "deltaY" | "deltaMode" | "shiftKey">, viewport: {
3
+ width: number;
4
+ height: number;
5
+ }): {
6
+ x: number;
7
+ y: number;
8
+ };
@@ -0,0 +1,8 @@
1
+ /** Converts wheel units to pixel distances for consistent canvas panning. */
2
+ export function wheelPanDelta(event, viewport) {
3
+ const xUnit = event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? viewport.width : 1;
4
+ const yUnit = event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? viewport.height : 1;
5
+ const x = event.deltaX * xUnit;
6
+ const y = event.deltaY * yUnit;
7
+ return event.shiftKey && x === 0 ? { x: y, y: 0 } : { x, y };
8
+ }