nudge-ui 0.2.1 → 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 (258) hide show
  1. package/README.md +164 -46
  2. package/dist/client.mjs +252 -54
  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 +41 -32
  7. package/dist/hosts/astro/integration.d.ts +2 -2
  8. package/dist/hosts/astro/integration.js +9 -4
  9. package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
  10. package/dist/hosts/astro/markdownIdentity.js +81 -0
  11. package/dist/hosts/environment.d.ts +15 -0
  12. package/dist/hosts/environment.js +16 -0
  13. package/dist/hosts/next/manifest.js +1 -1
  14. package/dist/hosts/next/sidecar.js +12 -2
  15. package/dist/hosts/next/wrapper.d.ts +2 -0
  16. package/dist/hosts/next/wrapper.js +2 -1
  17. package/dist/hosts/projectBridge.d.ts +1 -0
  18. package/dist/hosts/projectBridge.js +1 -0
  19. package/dist/hosts/static/manifest.js +3 -1
  20. package/dist/hosts/static/server.js +4 -0
  21. package/dist/hosts/vite/vite.d.ts +4 -0
  22. package/dist/hosts/vite/vite.js +28 -5
  23. package/dist/inspector/agent/McpConnectionDialog.d.ts +3 -2
  24. package/dist/inspector/agent/McpConnectionDialog.js +23 -22
  25. package/dist/inspector/agent/activity.d.ts +18 -0
  26. package/dist/inspector/agent/activity.js +70 -0
  27. package/dist/inspector/agent/client.d.ts +8 -0
  28. package/dist/inspector/agent/client.js +65 -5
  29. package/dist/inspector/agent/connectionStatus.d.ts +3 -3
  30. package/dist/inspector/agent/connectionStatus.js +9 -2
  31. package/dist/inspector/agent/httpTransport.d.ts +6 -0
  32. package/dist/inspector/agent/httpTransport.js +50 -3
  33. package/dist/inspector/agent/protocol.d.ts +9 -3
  34. package/dist/inspector/agent/protocol.js +1 -1
  35. package/dist/inspector/agent/verification.d.ts +11 -1
  36. package/dist/inspector/agent/verification.js +53 -25
  37. package/dist/inspector/canvas/AgentActivityOverlay.d.ts +13 -0
  38. package/dist/inspector/canvas/AgentActivityOverlay.js +111 -0
  39. package/dist/inspector/canvas/CanvasCard.d.ts +9 -2
  40. package/dist/inspector/canvas/CanvasCard.js +133 -38
  41. package/dist/inspector/canvas/CanvasElementOverlay.js +200 -22
  42. package/dist/inspector/canvas/CanvasToolIcons.d.ts +10 -0
  43. package/dist/inspector/canvas/CanvasToolIcons.js +20 -0
  44. package/dist/inspector/canvas/CanvasToolbar.js +8 -4
  45. package/dist/inspector/canvas/CanvasWorkspace.js +243 -56
  46. package/dist/inspector/canvas/StaleChangeIndicator.js +3 -13
  47. package/dist/inspector/canvas/agentPresentation.d.ts +8 -8
  48. package/dist/inspector/canvas/agentPresentation.js +64 -52
  49. package/dist/inspector/canvas/canvasGestures.d.ts +1 -1
  50. package/dist/inspector/canvas/canvasGestures.js +1 -1
  51. package/dist/inspector/canvas/canvasStore.d.ts +46 -82
  52. package/dist/inspector/canvas/canvasStore.js +251 -284
  53. package/dist/inspector/canvas/frameContent.d.ts +21 -0
  54. package/dist/inspector/canvas/frameContent.js +44 -0
  55. package/dist/inspector/canvas/frameProtocol.d.ts +34 -4
  56. package/dist/inspector/canvas/frameProtocol.js +9 -2
  57. package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -1
  58. package/dist/inspector/canvas/keyboardShortcuts.js +5 -3
  59. package/dist/inspector/canvas/layout.d.ts +52 -0
  60. package/dist/inspector/canvas/layout.js +106 -0
  61. package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
  62. package/dist/inspector/canvas/linkEligibility.js +0 -29
  63. package/dist/inspector/canvas/projection.d.ts +15 -5
  64. package/dist/inspector/canvas/projection.js +69 -13
  65. package/dist/inspector/canvas/rendererBootstrap.js +45 -4
  66. package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
  67. package/dist/inspector/canvas/rendererElementSelector.js +102 -45
  68. package/dist/inspector/canvas/rendererStylesheet.js +2 -2
  69. package/dist/inspector/canvas/resizeHandleScale.js +1 -1
  70. package/dist/inspector/canvas/sessionStore.d.ts +11 -26
  71. package/dist/inspector/canvas/sessionStore.js +43 -183
  72. package/dist/inspector/canvas/staleChangeDetector.js +9 -3
  73. package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
  74. package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
  75. package/dist/inspector/canvas/viewStore.d.ts +38 -0
  76. package/dist/inspector/canvas/viewStore.js +104 -0
  77. package/dist/inspector/canvas/wheelPan.d.ts +8 -0
  78. package/dist/inspector/canvas/wheelPan.js +8 -0
  79. package/dist/inspector/changes/changesLog.d.ts +6 -5
  80. package/dist/inspector/changes/changesLog.js +40 -32
  81. package/dist/inspector/changes/draftChanges.d.ts +56 -0
  82. package/dist/inspector/changes/draftChanges.js +300 -0
  83. package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
  84. package/dist/inspector/changes/previewDiagnostics.js +11 -25
  85. package/dist/inspector/clientManifest.d.ts +3 -1
  86. package/dist/inspector/clientManifest.js +1 -0
  87. package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
  88. package/dist/inspector/comments/CommentsOverlay.js +250 -0
  89. package/dist/inspector/comments/element.d.ts +6 -0
  90. package/dist/inspector/comments/element.js +47 -0
  91. package/dist/inspector/comments/store.d.ts +29 -0
  92. package/dist/inspector/comments/store.js +121 -0
  93. package/dist/inspector/comments/verification.d.ts +5 -0
  94. package/dist/inspector/comments/verification.js +52 -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 +26 -35
  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 +173 -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.d.ts +10 -0
  111. package/dist/inspector/overlay/DomNavigator.js +46 -0
  112. package/dist/inspector/overlay/InspectorOverlay.js +6 -2
  113. package/dist/inspector/overlay/clickPolicy.d.ts +18 -2
  114. package/dist/inspector/overlay/clickPolicy.js +34 -3
  115. package/dist/inspector/overlay/elementSelector.d.ts +1 -1
  116. package/dist/inspector/overlay/elementSelector.js +18 -7
  117. package/dist/inspector/overlay/interactionStyles.js +3 -1
  118. package/dist/inspector/overlay/spacingGestures.d.ts +15 -3
  119. package/dist/inspector/overlay/spacingGestures.js +88 -13
  120. package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
  121. package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
  122. package/dist/inspector/projection/renderedInstance.d.ts +2 -1
  123. package/dist/inspector/projection/renderedInstance.js +9 -5
  124. package/dist/inspector/projection/structuralProjection.d.ts +2 -1
  125. package/dist/inspector/projection/structuralProjection.js +19 -15
  126. package/dist/inspector/projection/textProjection.d.ts +2 -1
  127. package/dist/inspector/projection/textProjection.js +8 -4
  128. package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
  129. package/dist/inspector/prompt/clipboardHandoff.d.ts +10 -18
  130. package/dist/inspector/prompt/clipboardHandoff.js +92 -143
  131. package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
  132. package/dist/inspector/prompt/generatePrompt.js +17 -16
  133. package/dist/inspector/runtime/runtimeConfig.d.ts +10 -1
  134. package/dist/inspector/runtime/runtimeConfig.js +24 -0
  135. package/dist/inspector/selection/hierarchy.d.ts +6 -0
  136. package/dist/inspector/selection/hierarchy.js +8 -0
  137. package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
  138. package/dist/inspector/settings/SettingsDialog.js +22 -9
  139. package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
  140. package/dist/inspector/shell/AgentStatusButton.js +36 -0
  141. package/dist/inspector/shell/AppShell.js +4 -2
  142. package/dist/inspector/shell/ChangesLog.js +47 -27
  143. package/dist/inspector/shell/CopyPromptButton.js +146 -90
  144. package/dist/inspector/shell/EmptyState.js +9 -8
  145. package/dist/inspector/shell/InspectorShell.js +14 -25
  146. package/dist/inspector/shell/PeekOriginalButton.js +24 -23
  147. package/dist/inspector/shell/originalPreview.js +5 -5
  148. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  149. package/dist/inspector/shell/shortcuts.js +11 -0
  150. package/dist/inspector/sketch/SketchEditor.js +14 -14
  151. package/dist/inspector/sketch/SketchEntryButton.js +1 -1
  152. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
  153. package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
  154. package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
  155. package/dist/inspector/sketch/SketchOverlay.js +7 -7
  156. package/dist/inspector/sketch/SketchWorkspace.js +18 -12
  157. package/dist/inspector/sketch/interaction.d.ts +1 -0
  158. package/dist/inspector/sketch/interaction.js +8 -4
  159. package/dist/inspector/sketch/model.d.ts +5 -0
  160. package/dist/inspector/sketch/model.js +6 -2
  161. package/dist/inspector/sketch/raster.d.ts +1 -2
  162. package/dist/inspector/sketch/raster.js +68 -4
  163. package/dist/inspector/sketch/store.js +48 -23
  164. package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
  165. package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
  166. package/dist/inspector/styleEditors/BorderEditor.js +49 -78
  167. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
  168. package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
  169. package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
  170. package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
  171. package/dist/inspector/styleEditors/ColorPicker.js +10 -4
  172. package/dist/inspector/styleEditors/GapField.js +3 -5
  173. package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
  174. package/dist/inspector/styleEditors/GridChildSection.js +18 -9
  175. package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
  176. package/dist/inspector/styleEditors/GridPicker.js +8 -2
  177. package/dist/inspector/styleEditors/GridSection.js +1 -1
  178. package/dist/inspector/styleEditors/GridValueField.js +19 -14
  179. package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
  180. package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
  181. package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
  182. package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
  183. package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
  184. package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
  185. package/dist/inspector/styleEditors/LayoutSection.js +32 -11
  186. package/dist/inspector/styleEditors/SpacingBox.js +16 -9
  187. package/dist/inspector/styleEditors/Typography.js +11 -3
  188. package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
  189. package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
  190. package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
  191. package/dist/inspector/styleEditors/gradientValue.js +114 -0
  192. package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
  193. package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
  194. package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
  195. package/dist/inspector/styleEditors/layoutValue.js +0 -42
  196. package/dist/inspector/styleEditors/nudgeValue.d.ts +4 -4
  197. package/dist/inspector/styleEditors/nudgeValue.js +41 -11
  198. package/dist/inspector/tokens/TokenField.d.ts +7 -0
  199. package/dist/inspector/tokens/TokenField.js +49 -22
  200. package/dist/inspector/tokens/TokenLabel.js +5 -14
  201. package/dist/inspector/tokens/TokensPanel.js +2 -1
  202. package/dist/inspector/tokens/dragPointerLock.d.ts +8 -0
  203. package/dist/inspector/tokens/dragPointerLock.js +70 -0
  204. package/dist/inspector/tokens/editActions.d.ts +1 -1
  205. package/dist/inspector/tokens/editActions.js +1 -1
  206. package/dist/inspector/ui/AtRuleContext.js +4 -6
  207. package/dist/inspector/ui/Button.d.ts +6 -2
  208. package/dist/inspector/ui/Button.js +4 -3
  209. package/dist/inspector/ui/ControlSurface.d.ts +2 -2
  210. package/dist/inspector/ui/ControlSurface.js +4 -3
  211. package/dist/inspector/ui/Disclosure.d.ts +22 -0
  212. package/dist/inspector/ui/Disclosure.js +13 -0
  213. package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
  214. package/dist/inspector/ui/InlineStyleWarning.js +13 -0
  215. package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
  216. package/dist/inspector/ui/InspectorPopover.js +4 -5
  217. package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
  218. package/dist/inspector/ui/PopoverListbox.js +8 -7
  219. package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
  220. package/dist/inspector/ui/SegmentedControl.js +2 -2
  221. package/dist/inspector/ui/Select.d.ts +6 -1
  222. package/dist/inspector/ui/Select.js +7 -11
  223. package/dist/inspector/ui/SideValuesField.js +1 -1
  224. package/dist/inspector/ui/Tooltip.d.ts +26 -0
  225. package/dist/inspector/ui/Tooltip.js +32 -0
  226. package/dist/inspector/ui/classNames.d.ts +2 -0
  227. package/dist/inspector/ui/classNames.js +4 -0
  228. package/dist/inspector/ui/customCursors.d.ts +23 -0
  229. package/dist/inspector/ui/customCursors.js +122 -0
  230. package/dist/inspector/ui/dragCursor.d.ts +6 -0
  231. package/dist/inspector/ui/dragCursor.js +13 -0
  232. package/dist/inspector/ui/portalContainer.d.ts +2 -0
  233. package/dist/inspector/ui/portalContainer.js +6 -0
  234. package/dist/inspector/ui/styles.js +34 -24
  235. package/dist/inspector/workspace/commands.d.ts +9 -0
  236. package/dist/inspector/workspace/commands.js +82 -0
  237. package/dist/inspector/workspace/controller.d.ts +4 -0
  238. package/dist/inspector/workspace/controller.js +66 -0
  239. package/dist/inspector/workspace/handoff.d.ts +42 -0
  240. package/dist/inspector/workspace/handoff.js +115 -0
  241. package/dist/inspector/workspace/timeline.d.ts +30 -0
  242. package/dist/inspector/workspace/timeline.js +108 -0
  243. package/dist/internal/agent-protocol.d.ts +19 -2
  244. package/dist/internal/agent-protocol.js +13 -0
  245. package/dist/nudge-ui.mjs +560 -299
  246. package/dist/project/artifacts.d.ts +13 -0
  247. package/dist/project/artifacts.js +253 -0
  248. package/dist/transport/artifacts.d.ts +8 -0
  249. package/dist/transport/artifacts.js +9 -0
  250. package/dist/transport/editor.d.ts +20 -1
  251. package/dist/transport/editor.js +44 -11
  252. package/dist/transport/routes.d.ts +6 -1
  253. package/dist/transport/routes.js +6 -1
  254. package/package.json +6 -2
  255. package/dist/inspector/changes/workspaceChanges.d.ts +0 -43
  256. package/dist/inspector/changes/workspaceChanges.js +0 -239
  257. package/dist/inspector/shell/DomNavigation.d.ts +0 -8
  258. package/dist/inspector/shell/DomNavigation.js +0 -30
@@ -0,0 +1,21 @@
1
+ import { type DraftTarget } from "../drafts/model.js";
2
+ /** Content identity is independent of frame geometry and its editable draft. */
3
+ export type FrameContent = {
4
+ kind: "route";
5
+ url: string;
6
+ navigationUrl?: string;
7
+ } | {
8
+ kind: "iteration";
9
+ artifactId: string;
10
+ sourceUrl: string;
11
+ };
12
+ export declare function contentSourceUrl(content: FrameContent | undefined): string;
13
+ export declare function iterationId(content: FrameContent | undefined): string | undefined;
14
+ export declare function contentNavigationUrl(content: FrameContent): string | undefined;
15
+ export declare function contentDocumentUrl(content: FrameContent): string;
16
+ export declare function contentEditTarget(content: FrameContent): DraftTarget;
17
+ export declare function getLinkedFrameIds(frames: readonly {
18
+ id: string;
19
+ content: FrameContent;
20
+ }[], activeId: string | null): readonly string[];
21
+ export declare function isFrameContent(value: unknown, origin: string): value is FrameContent;
@@ -0,0 +1,44 @@
1
+ import { applicationTarget, htmlTarget } from "../drafts/model.js";
2
+ import { artifactDocumentUrl } from "../iterations/client.js";
3
+ export function contentSourceUrl(content) {
4
+ return content ? content.kind === "route" ? content.url : content.sourceUrl : "";
5
+ }
6
+ export function iterationId(content) {
7
+ return content?.kind === "iteration" ? content.artifactId : undefined;
8
+ }
9
+ export function contentNavigationUrl(content) {
10
+ return content.kind === "route" ? content.navigationUrl : undefined;
11
+ }
12
+ export function contentDocumentUrl(content) {
13
+ return content.kind === "route" ? content.navigationUrl ?? content.url : artifactDocumentUrl(content.artifactId);
14
+ }
15
+ export function contentEditTarget(content) {
16
+ return content.kind === "route" ? applicationTarget(content.url) : htmlTarget(content.artifactId);
17
+ }
18
+ export function getLinkedFrameIds(frames, activeId) {
19
+ const active = frames.find((frame) => frame.id === activeId);
20
+ if (active?.content.kind !== "route")
21
+ return [];
22
+ const route = applicationTarget(active.content.url).route;
23
+ const matching = frames.filter((frame) => frame.content.kind === "route"
24
+ && applicationTarget(frame.content.url).route === route);
25
+ return matching.length > 1 ? matching.map((frame) => frame.id) : [];
26
+ }
27
+ export function isFrameContent(value, origin) {
28
+ if (!value || typeof value !== "object")
29
+ return false;
30
+ const content = value;
31
+ const url = content.kind === "route" ? content.url : content.sourceUrl;
32
+ if (typeof url !== "string")
33
+ return false;
34
+ try {
35
+ if (new URL(url).origin !== origin)
36
+ return false;
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ if (content.kind === "iteration")
42
+ return typeof content.artifactId === "string" && /^[a-f0-9-]{36}$/i.test(content.artifactId);
43
+ return content.kind === "route" && content.navigationUrl === undefined;
44
+ }
@@ -2,7 +2,7 @@ import type { ComponentOverride } from "../componentSemantics/types.js";
2
2
  import type { RenderedInstanceOverride } from "../changes/editModel.js";
3
3
  import type { NudgeUiRuntimeConfig } from "../runtime/runtimeConfig.js";
4
4
  import type { SpacingDescriptor } from "../overlay/spacingGestures.js";
5
- export declare const PROTOCOL_VERSION = 19;
5
+ export declare const PROTOCOL_VERSION = 21;
6
6
  export interface FrameMessage {
7
7
  type: string;
8
8
  protocolVersion: number;
@@ -93,6 +93,13 @@ export interface TextProjectionReportMessage extends RendererMessage {
93
93
  export interface NavigationIntentMessage extends RendererMessage {
94
94
  type: "navigation-intent";
95
95
  url: string;
96
+ /** The renderer kept its own route and asks the controller for a card instead. */
97
+ openInCard?: boolean;
98
+ }
99
+ /** Tells a renderer whether ordinary link clicks open a new canvas card. */
100
+ export interface LinkTargetStateMessage extends RendererMessage {
101
+ type: "link-target-state";
102
+ openInCard: boolean;
96
103
  }
97
104
  export interface ElementHoverMessage extends RendererMessage {
98
105
  type: "element-hover";
@@ -123,8 +130,12 @@ export interface ElementHoverMessage extends RendererMessage {
123
130
  x: number;
124
131
  y: number;
125
132
  } | null;
126
- /** The padding or layout gap under the pointer, when one is draggable. */
133
+ /** The spacing handle under the pointer. Drives the resize cursor and drag
134
+ * start; only present when the pointer is inside the handle hit area. */
127
135
  spacing?: SpacingDescriptor | null;
136
+ /** The padding bar or layout gap area under the pointer, regardless of the
137
+ * handle. Drives the hover fill overlay across the whole area. */
138
+ hoverSpacing?: SpacingDescriptor | null;
128
139
  }
129
140
  /** Modifier state stays inside one renderer frame; the parent never infers it
130
141
  * from its own keyboard events because focus lives in the iframe. */
@@ -179,6 +190,8 @@ export interface ElementDragStartMessage extends RendererMessage {
179
190
  };
180
191
  /** Spacing affordance captured at pointer-down, if this is a spacing drag. */
181
192
  spacing?: SpacingDescriptor | null;
193
+ /** True while Shift is held; spacing drags use this to snap by 8px steps. */
194
+ shiftKey?: boolean;
182
195
  }
183
196
  export interface ElementDragMoveMessage extends RendererMessage {
184
197
  type: "element-drag-move";
@@ -186,6 +199,8 @@ export interface ElementDragMoveMessage extends RendererMessage {
186
199
  x: number;
187
200
  y: number;
188
201
  };
202
+ /** True while Shift is held; spacing drags use this to snap by 8px steps. */
203
+ shiftKey?: boolean;
189
204
  }
190
205
  export interface ElementDragEndMessage extends RendererMessage {
191
206
  type: "element-drag-end";
@@ -193,6 +208,8 @@ export interface ElementDragEndMessage extends RendererMessage {
193
208
  x: number;
194
209
  y: number;
195
210
  };
211
+ /** True while Shift is held; spacing drags use this to snap by 8px steps. */
212
+ shiftKey?: boolean;
196
213
  /** True when the renderer lost the gesture before the pointer was released. */
197
214
  cancelled?: boolean;
198
215
  }
@@ -222,7 +239,7 @@ export interface InspectorToggleRequestMessage extends RendererMessage {
222
239
  export interface InspectorOpenRequestMessage extends RendererMessage {
223
240
  type: "inspector-open-request";
224
241
  }
225
- export type KeyboardShortcutCode = "Backslash" | "KeyV" | "KeyH" | "KeyP" | "KeyS";
242
+ export type KeyboardShortcutCode = "Backslash" | "KeyI" | "KeyV" | "KeyC" | "KeyP" | "KeyS";
226
243
  export type KeyboardShortcutPhase = "keydown" | "keyup";
227
244
  /** Forwards controller-owned keyboard shortcuts from an iframe document. */
228
245
  export interface KeyboardShortcutMessage extends RendererMessage {
@@ -261,6 +278,8 @@ export interface MeasureModifierMessage extends RendererMessage {
261
278
  export interface BoardGestureStateMessage extends RendererMessage {
262
279
  type: "board-gesture-state";
263
280
  enabled: boolean;
281
+ /** Ordinary wheel gestures pan the board instead of scrolling the app. */
282
+ panScroll?: boolean;
264
283
  }
265
284
  /** Proxies modified wheel gestures that originate inside an iframe card. */
266
285
  export interface ZoomMessage extends RendererMessage {
@@ -271,7 +290,18 @@ export interface ZoomMessage extends RendererMessage {
271
290
  y: number;
272
291
  };
273
292
  }
274
- export type FrameProtocolMessage = ParentReadyMessage | RendererHelloMessage | FrameReadyMessage | FrameRuntimeMessage | FrameMetadataMessage | FrameLoadError | ReplaceStylesMessage | ProjectionAppliedMessage | StructuralProjectionReportMessage | RenderedInstanceProjectionReportMessage | TextProjectionReportMessage | NavigationIntentMessage | ElementHoverMessage | ElementMeasureStateMessage | ElementClickMessage | InlineTextIntentMessage | ElementDeselectMessage | ElementDragStartMessage | ElementDragMoveMessage | ElementDragEndMessage | ElementDeleteMessage | ElementNudgeMessage | HistoryRequestMessage | InspectorToggleRequestMessage | InspectorOpenRequestMessage | KeyboardShortcutMessage | PanStartMessage | PanMoveMessage | PanEndMessage | PanModifierMessage | MeasureModifierMessage | BoardGestureStateMessage | ZoomMessage;
293
+ /** Proxies ordinary wheel gestures as normalized pixel distances. */
294
+ export interface WheelPanMessage extends RendererMessage {
295
+ type: "wheel-pan";
296
+ deltaX: number;
297
+ deltaY: number;
298
+ }
299
+ /** Keeps the A key state synchronized when keyboard focus moves into an iframe. */
300
+ export interface AppInteractionModifierMessage extends RendererMessage {
301
+ type: "app-interaction-modifier";
302
+ held: boolean;
303
+ }
304
+ export type FrameProtocolMessage = ParentReadyMessage | RendererHelloMessage | FrameReadyMessage | FrameRuntimeMessage | FrameMetadataMessage | FrameLoadError | ReplaceStylesMessage | ProjectionAppliedMessage | StructuralProjectionReportMessage | RenderedInstanceProjectionReportMessage | TextProjectionReportMessage | NavigationIntentMessage | LinkTargetStateMessage | ElementHoverMessage | ElementMeasureStateMessage | ElementClickMessage | InlineTextIntentMessage | ElementDeselectMessage | ElementDragStartMessage | ElementDragMoveMessage | ElementDragEndMessage | ElementDeleteMessage | ElementNudgeMessage | HistoryRequestMessage | InspectorToggleRequestMessage | InspectorOpenRequestMessage | KeyboardShortcutMessage | PanStartMessage | PanMoveMessage | PanEndMessage | PanModifierMessage | MeasureModifierMessage | BoardGestureStateMessage | ZoomMessage | WheelPanMessage | AppInteractionModifierMessage;
275
305
  export declare function setRendererIdentity(identity: FrameIdentity): void;
276
306
  export declare function clearRendererIdentity(expected?: FrameIdentity): void;
277
307
  export declare function getRendererIdentity(): FrameIdentity | null;
@@ -1,6 +1,9 @@
1
1
  import { isDocumentProjectionReport } from "../projection/renderedInstance.js";
2
2
  import { isTextProjectionReport } from "../projection/textProjection.js";
3
3
  import { isStructuralProjectionReport } from "../projection/structuralProjectionBoundary.js";
4
+ // v21 adds the Comment tool cursor and C shortcut.
5
+ // v20 adds Shift state to element drag messages so spacing handles can snap
6
+ // changes to 8px increments.
4
7
  // v19 adds spacing drag gestures (hover point, spacing descriptor, start
5
8
  // point, and cancelled drag-end) plus renderer-to-controller keyboard shortcut
6
9
  // forwarding for Canvas frames.
@@ -15,7 +18,7 @@ import { isStructuralProjectionReport } from "../projection/structuralProjection
15
18
  // rereading a Canvas iframe until the renderer has applied its revision. v11
16
19
  // added the renderer-hello handshake solicitation for runtimes whose boot
17
20
  // completes after the controller's load-time parent-ready.
18
- export const PROTOCOL_VERSION = 19;
21
+ export const PROTOCOL_VERSION = 21;
19
22
  let rendererIdentity = null;
20
23
  export function setRendererIdentity(identity) {
21
24
  rendererIdentity = identity;
@@ -37,7 +40,11 @@ export function isRendererMessageFor(value, identity) {
37
40
  && message.cardId === identity.cardId;
38
41
  }
39
42
  export function isKeyboardShortcutCode(value) {
40
- return value === "Backslash" || value === "KeyV" || value === "KeyH" || value === "KeyP" || value === "KeyS";
43
+ return value === "Backslash"
44
+ || value === "KeyV"
45
+ || value === "KeyC"
46
+ || value === "KeyP"
47
+ || value === "KeyS";
41
48
  }
42
49
  /** Strict JSON-only schema for renderer-originated global keyboard shortcuts. */
43
50
  export function isKeyboardShortcutMessage(value, identity) {
@@ -1,3 +1,6 @@
1
- export type CanvasInteractionTool = "move" | "pan" | "sketch";
1
+ /** `select` leaves the application interactive; `design` turns clicks into inspector selection. */
2
+ export type CanvasInteractionTool = "select" | "design" | "comment" | "sketch";
3
+ /** Physical key used to temporarily interact with the app in the canvas. */
4
+ export declare const APP_INTERACTION_KEY = "KeyA";
2
5
  export declare function canvasToolForCode(code: string): CanvasInteractionTool | null;
3
6
  export declare function canvasToolForEvent(event: KeyboardEvent): CanvasInteractionTool | null;
@@ -1,8 +1,10 @@
1
+ /** Physical key used to temporarily interact with the app in the canvas. */
2
+ export const APP_INTERACTION_KEY = "KeyA";
1
3
  export function canvasToolForCode(code) {
2
4
  if (code === "KeyV")
3
- return "move";
4
- if (code === "KeyH")
5
- return "pan";
5
+ return "design";
6
+ if (code === "KeyC")
7
+ return "comment";
6
8
  if (code === "KeyP")
7
9
  return "sketch";
8
10
  return null;
@@ -0,0 +1,52 @@
1
+ export interface LayoutCard {
2
+ readonly id: string;
3
+ readonly x: number;
4
+ readonly y: number;
5
+ readonly width: number;
6
+ readonly height: number;
7
+ }
8
+ export interface Bounds {
9
+ readonly minX: number;
10
+ readonly minY: number;
11
+ readonly maxX: number;
12
+ readonly maxY: number;
13
+ }
14
+ /** The layout fields a creation may change on existing frames. */
15
+ export interface LayoutShift {
16
+ readonly id: string;
17
+ readonly from: Pick<LayoutCard, "x" | "y">;
18
+ readonly to: Pick<LayoutCard, "x" | "y">;
19
+ }
20
+ export declare function boundsOf(cards: readonly LayoutCard[]): Bounds;
21
+ /** Places a new frame to the right of every frame, on the first frame's row. */
22
+ export declare function nextCardPosition(cards: readonly LayoutCard[], gap: number): {
23
+ x: number;
24
+ y: number;
25
+ };
26
+ export declare function pushOverlappingCards<T extends LayoutCard>(cards: readonly T[], protectedIds: ReadonlySet<string>, gap: number): T[];
27
+ export declare function iterationPosition(cards: readonly LayoutCard[], source: LayoutCard, gap: number): {
28
+ x: number;
29
+ y: number;
30
+ };
31
+ /** Zooms and centers content inside the padded viewport; null for empty content. */
32
+ export declare function fitCamera(bounds: Bounds, viewport: {
33
+ width: number;
34
+ height: number;
35
+ }, padding: number): {
36
+ x: number;
37
+ y: number;
38
+ zoom: number;
39
+ } | null;
40
+ export declare function centerCamera(bounds: Bounds, zoom: number, viewport: {
41
+ width: number;
42
+ height: number;
43
+ }): {
44
+ x: number;
45
+ y: number;
46
+ zoom: number;
47
+ };
48
+ export declare function layoutShifts(before: readonly LayoutCard[], after: readonly LayoutCard[]): LayoutShift[];
49
+ /** Replays recorded shifts as deltas, so frames moved since the creation keep their later moves. */
50
+ export declare function applyLayoutShifts<T extends LayoutCard>(cards: readonly T[], shifts: readonly LayoutShift[], forward: boolean): T[];
51
+ /** Reinserts recorded frames after their nearest recorded predecessor that still exists. */
52
+ export declare function reinsertInRecordedOrder<T extends LayoutCard>(cards: readonly T[], recorded: readonly T[], ids: ReadonlySet<string>): T[];
@@ -0,0 +1,106 @@
1
+ import { MAX_CAMERA_ZOOM, MIN_CAMERA_ZOOM } from "./viewStore.js";
2
+ export function boundsOf(cards) {
3
+ let minX = Infinity;
4
+ let minY = Infinity;
5
+ let maxX = -Infinity;
6
+ let maxY = -Infinity;
7
+ for (const card of cards) {
8
+ minX = Math.min(minX, card.x);
9
+ minY = Math.min(minY, card.y);
10
+ maxX = Math.max(maxX, card.x + card.width);
11
+ maxY = Math.max(maxY, card.y + card.height);
12
+ }
13
+ return { minX, minY, maxX, maxY };
14
+ }
15
+ /** Places a new frame to the right of every frame, on the first frame's row. */
16
+ export function nextCardPosition(cards, gap) {
17
+ if (cards.length === 0)
18
+ return { x: 0, y: 0 };
19
+ return { x: Math.max(...cards.map((card) => card.x + card.width + gap)), y: cards[0].y };
20
+ }
21
+ export function pushOverlappingCards(cards, protectedIds, gap) {
22
+ const fixed = cards.filter((card) => protectedIds.has(card.id)).map((card) => boundsOf([card]));
23
+ const moving = cards.filter((card) => !protectedIds.has(card.id)).sort((a, b) => a.x - b.x);
24
+ const shifts = new Map();
25
+ for (const card of moving) {
26
+ let bounds = boundsOf([card]);
27
+ let dx = 0;
28
+ let collision = true;
29
+ while (collision) {
30
+ collision = false;
31
+ for (const other of fixed) {
32
+ if (bounds.minY >= other.maxY || bounds.maxY <= other.minY
33
+ || bounds.minX >= other.maxX + gap || bounds.maxX + gap <= other.minX)
34
+ continue;
35
+ const shift = other.maxX + gap - bounds.minX;
36
+ dx += shift;
37
+ bounds = { ...bounds, minX: bounds.minX + shift, maxX: bounds.maxX + shift };
38
+ collision = true;
39
+ }
40
+ }
41
+ if (dx)
42
+ shifts.set(card.id, dx);
43
+ fixed.push(bounds);
44
+ }
45
+ return shifts.size ? cards.map((card) => shifts.has(card.id) ? { ...card, x: card.x + shifts.get(card.id) } : card) : [...cards];
46
+ }
47
+ export function iterationPosition(cards, source, gap) {
48
+ const x = source.x;
49
+ let y = source.y + source.height + gap * 2;
50
+ for (const neighbor of [...cards].sort((a, b) => a.y - b.y)) {
51
+ if (x >= neighbor.x + neighbor.width + gap || x + source.width + gap <= neighbor.x)
52
+ continue;
53
+ if (y < neighbor.y + neighbor.height + gap * 2 && y + source.height + gap * 2 > neighbor.y)
54
+ y = neighbor.y + neighbor.height + gap * 2;
55
+ }
56
+ return { x, y };
57
+ }
58
+ /** Zooms and centers content inside the padded viewport; null for empty content. */
59
+ export function fitCamera(bounds, viewport, padding) {
60
+ const width = bounds.maxX - bounds.minX;
61
+ const height = bounds.maxY - bounds.minY;
62
+ if (width <= 0 || height <= 0)
63
+ return null;
64
+ const zoom = Math.max(MIN_CAMERA_ZOOM, Math.min(Math.max(1, viewport.width - padding * 2) / width, Math.max(1, viewport.height - padding * 2) / height, MAX_CAMERA_ZOOM));
65
+ return centerCamera(bounds, zoom, viewport);
66
+ }
67
+ export function centerCamera(bounds, zoom, viewport) {
68
+ return {
69
+ x: -((bounds.minX + bounds.maxX) / 2) * zoom + viewport.width / 2,
70
+ y: -((bounds.minY + bounds.maxY) / 2) * zoom + viewport.height / 2,
71
+ zoom,
72
+ };
73
+ }
74
+ export function layoutShifts(before, after) {
75
+ const previous = new Map(before.map((card) => [card.id, card]));
76
+ return after.flatMap((card) => {
77
+ const old = previous.get(card.id);
78
+ if (!old || (old.x === card.x && old.y === card.y))
79
+ return [];
80
+ return [{ id: card.id, from: { x: old.x, y: old.y }, to: { x: card.x, y: card.y } }];
81
+ });
82
+ }
83
+ /** Replays recorded shifts as deltas, so frames moved since the creation keep their later moves. */
84
+ export function applyLayoutShifts(cards, shifts, forward) {
85
+ const byId = new Map(shifts.map((shift) => [shift.id, shift]));
86
+ return cards.map((card) => {
87
+ const shift = byId.get(card.id);
88
+ if (!shift)
89
+ return card;
90
+ const from = forward ? shift.from : shift.to;
91
+ const to = forward ? shift.to : shift.from;
92
+ return { ...card, x: card.x + to.x - from.x, y: card.y + to.y - from.y };
93
+ });
94
+ }
95
+ /** Reinserts recorded frames after their nearest recorded predecessor that still exists. */
96
+ export function reinsertInRecordedOrder(cards, recorded, ids) {
97
+ let next = [...cards];
98
+ for (const [index, added] of recorded.entries()) {
99
+ if (!ids.has(added.id))
100
+ continue;
101
+ const preceding = recorded.slice(0, index).reverse().find((card) => next.some((item) => item.id === card.id));
102
+ const insertion = preceding ? next.findIndex((card) => card.id === preceding.id) + 1 : 0;
103
+ next = [...next.slice(0, insertion), { ...added }, ...next.slice(insertion)];
104
+ }
105
+ return next;
106
+ }
@@ -1,8 +1,3 @@
1
1
  export declare function findClosestAnchor(target: EventTarget | null): HTMLAnchorElement | null;
2
2
  export declare function isEligibleNavigation(anchor: HTMLAnchorElement, event: MouseEvent): boolean;
3
3
  export declare function hasDifferentRoute(anchor: HTMLAnchorElement): boolean;
4
- /**
5
- * Returns whether the renderer should leave an anchor's browser behavior
6
- * intact instead of consuming it as an editing click.
7
- */
8
- export declare function shouldPreserveNativeLinkActivation(anchor: HTMLAnchorElement, event: MouseEvent): boolean;
@@ -37,32 +37,3 @@ export function hasDifferentRoute(anchor) {
37
37
  return false;
38
38
  }
39
39
  }
40
- /**
41
- * Returns whether the renderer should leave an anchor's browser behavior
42
- * intact instead of consuming it as an editing click.
43
- */
44
- export function shouldPreserveNativeLinkActivation(anchor, event) {
45
- // Command/Ctrl-click is the deep-selection gesture. Shift makes it the
46
- // explicit application-activation gesture instead.
47
- if (event.metaKey || event.ctrlKey)
48
- return event.shiftKey;
49
- // Shift-click is the additive inspector-selection gesture. Do not let a
50
- // link navigate or preserve its native activation in that mode.
51
- if (event.shiftKey)
52
- return false;
53
- if (anchor.hasAttribute("download"))
54
- return true;
55
- if (anchor.target && anchor.target !== "" && anchor.target !== "_self")
56
- return true;
57
- if (anchor.protocol !== "http:" && anchor.protocol !== "https:")
58
- return true;
59
- try {
60
- const url = new URL(anchor.href);
61
- if (url.origin !== window.location.origin)
62
- return true;
63
- }
64
- catch {
65
- return true;
66
- }
67
- return !hasDifferentRoute(anchor);
68
- }
@@ -1,9 +1,15 @@
1
- import { type WorkspaceContents } from "../changes/workspaceChanges.js";
1
+ import { type DraftContents } from "../changes/draftChanges.js";
2
2
  import type { CanvasCard } from "./canvasStore.js";
3
3
  import { type PreviewDocument } from "../changes/previewDiagnostics.js";
4
- import { type CompiledManagedStyles } from "../projection/workspaceProjection.js";
4
+ import { type CompiledManagedStyles, type DraftProjectionPlan } from "../projection/draftProjection.js";
5
5
  export declare const PROJECT_ID: string;
6
- export declare const WORKSPACE_ID: `${string}-${string}-${string}-${string}-${string}`;
6
+ export declare const WORKSPACE_ID: string;
7
+ export interface FrameProjection extends DraftProjectionPlan<CompiledManagedStyles> {
8
+ readonly draftId: string;
9
+ readonly draftRevision: number;
10
+ readonly revision: number;
11
+ readonly css: string;
12
+ }
7
13
  export interface CanvasProjectionStatus {
8
14
  sentRevision: number;
9
15
  appliedRevision: number;
@@ -22,10 +28,14 @@ export declare function computeProjection(): {
22
28
  textContentChanges: readonly import("../changes/editModel.js").TextContentChangeRecord[];
23
29
  componentOverrides: readonly import("../componentSemantics/types.js").ComponentOverride[];
24
30
  };
31
+ export declare function computeProjectionForCard(cardId: string): FrameProjection;
32
+ export declare function getCanonicalFrameProjection(cardId: string, expectedRevision: number): FrameProjection | null;
33
+ export declare function restoreCanonicalFrameProjection(doc: Document): void;
34
+ export declare function getCanvasCardIdForDocument(doc: Document): string | null;
25
35
  export declare function resetProjectionRevision(): void;
26
- export declare function sendProjectionToCard(card: CanvasCard, iframe: HTMLIFrameElement): void;
36
+ export declare function sendProjectionToCard(card: Pick<CanvasCard, "id">, iframe: HTMLIFrameElement): void;
27
37
  /** Temporarily projects a snapshot through the renderer that owns the document. */
28
- export declare function projectWorkspaceSnapshotToDocument(doc: Document, snapshot: WorkspaceContents): Promise<number | null>;
38
+ export declare function projectDraftToDocument(doc: Document, snapshot: DraftContents): Promise<number | null>;
29
39
  /** Returns whether an acknowledged temporary projection is still authoritative. */
30
40
  export declare function isCanvasProjectionRevisionCurrent(doc: Document, expectedRevision: number): boolean;
31
41
  /** Returns whether the live frame acknowledged its latest canonical workspace projection. */
@@ -1,18 +1,25 @@
1
- import { getWorkspaceChanges } from "../changes/workspaceChanges.js";
2
- import { getCanvasMode } from "./canvasStore.js";
1
+ import { draftIdForCard } from "../drafts/store.js";
2
+ import { getDraftChanges, getActiveDraftChanges } from "../changes/draftChanges.js";
3
+ import { getCanvasCards } from "./canvasStore.js";
4
+ import { getCanvasMode } from "./viewStore.js";
3
5
  import { PROTOCOL_VERSION } from "./frameProtocol.js";
4
6
  import { clearCanvasRenderedInstanceProjectionReports, setCanonicalRenderedInstanceOverrides, } from "../projection/renderedInstance.js";
5
7
  import { clearCanvasTextProjectionReports, setCanonicalTextContentChanges, } from "../projection/textProjection.js";
6
8
  import { clearCanvasStructuralProjectionReports } from "../projection/structuralProjection.js";
7
9
  import { invalidatePreviewDocumentSession, startPreviewDocumentSession, } from "../changes/previewDiagnostics.js";
8
- import { compileWorkspaceProjection, } from "../projection/workspaceProjection.js";
10
+ import { compileDraftProjection, } from "../projection/draftProjection.js";
9
11
  import { isOriginalPreviewActive } from "../shell/originalPreview.js";
10
12
  import { disposeBrowserCssInspection } from "../inspection/browserCssInspectionRegistry.js";
11
- export const PROJECT_ID = window.location.origin;
12
- export const WORKSPACE_ID = crypto.randomUUID?.() ?? `ws-${Date.now()}`;
13
+ export const PROJECT_ID = typeof window !== "undefined" && window.location?.origin
14
+ ? window.location.origin
15
+ : "";
16
+ export const WORKSPACE_ID = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
17
+ ? crypto.randomUUID()
18
+ : `ws-${Date.now()}`;
13
19
  let revision = 0;
14
20
  let lastRulesKey = null;
15
21
  let lastPeekActive = false;
22
+ const draftPlans = new Map();
16
23
  const frameProjectionStates = new Map();
17
24
  const previewDocuments = new Map();
18
25
  const projectionAcknowledgementListeners = new Set();
@@ -22,6 +29,7 @@ function createPreviewDocumentIdentity(cardId) {
22
29
  const previewDocument = {
23
30
  logicalDocument: `canvas:${cardId}`,
24
31
  sessionId: `canvas-session-${nextPreviewSessionId++}`,
32
+ draftId: draftIdForCard(cardId) ?? getActiveDraftChanges().draftId,
25
33
  };
26
34
  previewDocuments.set(cardId, previewDocument);
27
35
  return previewDocument;
@@ -50,7 +58,7 @@ function projectionKey(plan) {
50
58
  return `${plan.managedStyles.css}\u0000${JSON.stringify(plan.instanceOverrides)}\u0000${JSON.stringify(plan.structuralChanges)}\u0000${JSON.stringify(plan.textContentChanges)}\u0000${JSON.stringify(plan.componentOverrides)}`;
51
59
  }
52
60
  export function computeProjection() {
53
- const plan = compileWorkspaceProjection(getWorkspaceChanges());
61
+ const plan = compileDraftProjection(getActiveDraftChanges());
54
62
  if (isOriginalPreviewActive()) {
55
63
  // Hold-to-view-original: frames show the page without inspector changes
56
64
  // while canonical intent (and its projection key) stays untouched.
@@ -84,14 +92,53 @@ export function computeProjection() {
84
92
  }
85
93
  return { ...plan, css: plan.managedStyles.css, revision };
86
94
  }
95
+ export function computeProjectionForCard(cardId) {
96
+ const draftId = draftIdForCard(cardId);
97
+ const workspace = draftId ? getDraftChanges(draftId) : getActiveDraftChanges();
98
+ const peek = isOriginalPreviewActive();
99
+ const cached = draftPlans.get(workspace.draftId);
100
+ if (cached && cached.plan.draftRevision === workspace.revision && cached.peek === peek)
101
+ return cached.plan;
102
+ const source = peek ? { ...workspace, changes: [], structuralChanges: [] } : workspace;
103
+ const compiled = compileDraftProjection(source);
104
+ const plan = { ...compiled, draftId: workspace.draftId, draftRevision: workspace.revision, css: compiled.managedStyles.css, revision: ++revision };
105
+ draftPlans.set(workspace.draftId, { peek, plan });
106
+ return plan;
107
+ }
108
+ export function getCanonicalFrameProjection(cardId, expectedRevision) {
109
+ const state = frameProjectionStates.get(cardId);
110
+ const plan = state?.canonicalProjection;
111
+ const draftId = draftIdForCard(cardId);
112
+ const draft = draftId ? getDraftChanges(draftId) : null;
113
+ if (!state || !previewDocuments.has(cardId) || state.iframe.contentDocument !== state.document
114
+ || !plan || plan.revision !== expectedRevision || state.sentRevision !== expectedRevision
115
+ || (draft && (draft.draftId !== plan.draftId || draft.revision !== plan.draftRevision)))
116
+ return null;
117
+ return plan;
118
+ }
119
+ export function restoreCanonicalFrameProjection(doc) {
120
+ const id = getCanvasCardIdForDocument(doc);
121
+ const card = getCanvasCards().find((card) => card.id === id);
122
+ const frame = id ? frameRegistry.get(id) : undefined;
123
+ if (card && frame)
124
+ sendProjectionToCard(card, frame);
125
+ }
126
+ export function getCanvasCardIdForDocument(doc) {
127
+ for (const [id, state] of frameProjectionStates)
128
+ if (state.document === doc && state.iframe.contentDocument === doc)
129
+ return id;
130
+ return null;
131
+ }
87
132
  export function resetProjectionRevision() {
88
133
  revision = 0;
89
134
  lastRulesKey = null;
90
135
  lastPeekActive = false;
136
+ draftPlans.clear();
91
137
  for (const state of frameProjectionStates.values()) {
92
138
  state.sentRevision = -1;
93
139
  state.appliedRevision = -1;
94
140
  state.canonicalSentRevision = -1;
141
+ state.canonicalProjection = null;
95
142
  }
96
143
  projectionAcknowledgementVersion += 1;
97
144
  notifyProjectionAcknowledgementListeners();
@@ -100,7 +147,15 @@ export function sendProjectionToCard(card, iframe) {
100
147
  const win = iframe.contentWindow;
101
148
  if (!win)
102
149
  return;
103
- const { css, revision: rev, instanceOverrides, structuralChanges, textContentChanges, componentOverrides } = computeProjection();
150
+ let plan = computeProjectionForCard(card.id);
151
+ const state = frameProjectionStates.get(card.id);
152
+ if (state && plan.revision < state.sentRevision) {
153
+ plan = { ...plan, revision: ++revision };
154
+ draftPlans.set(plan.draftId, { peek: isOriginalPreviewActive(), plan });
155
+ }
156
+ if (state)
157
+ state.canonicalProjection = plan;
158
+ const { css, revision: rev, instanceOverrides, structuralChanges, textContentChanges, componentOverrides } = plan;
104
159
  sendProjectionMessage(card.id, iframe, rev, css, instanceOverrides, structuralChanges, textContentChanges, componentOverrides, true);
105
160
  }
106
161
  function sendProjectionMessage(cardId, iframe, rev, css, instanceOverrides, structuralChanges, textContentChanges, componentOverrides, canonical = false) {
@@ -124,12 +179,12 @@ function sendProjectionMessage(cardId, iframe, rev, css, instanceOverrides, stru
124
179
  win.postMessage(msg, window.location.origin);
125
180
  }
126
181
  /** Temporarily projects a snapshot through the renderer that owns the document. */
127
- export async function projectWorkspaceSnapshotToDocument(doc, snapshot) {
182
+ export async function projectDraftToDocument(doc, snapshot) {
128
183
  const entry = [...frameProjectionStates.entries()].find(([, state]) => state.document === doc);
129
184
  if (!entry)
130
185
  return null;
131
186
  const [cardId, state] = entry;
132
- const plan = compileWorkspaceProjection({ ...snapshot, revision: 0 });
187
+ const plan = compileDraftProjection({ ...snapshot, revision: 0 });
133
188
  revision += 1;
134
189
  sendProjectionMessage(cardId, state.iframe, revision, plan.managedStyles.css, plan.instanceOverrides, plan.structuralChanges, plan.textContentChanges, plan.componentOverrides);
135
190
  if (state.appliedRevision === revision && state.sentRevision === revision)
@@ -203,6 +258,8 @@ export function registerCardFrame(cardId, iframe) {
203
258
  const existing = frameProjectionStates.get(cardId);
204
259
  if (!existing || existing.iframe !== iframe || existing.document !== frameDocument) {
205
260
  if (existing) {
261
+ if (existing.canonicalProjection)
262
+ draftPlans.delete(existing.canonicalProjection.draftId);
206
263
  invalidatePreviewDocumentSession(existing.previewDocument.logicalDocument, existing.previewDocument.sessionId);
207
264
  previewDocuments.delete(cardId);
208
265
  }
@@ -215,6 +272,7 @@ export function registerCardFrame(cardId, iframe) {
215
272
  sentRevision: -1,
216
273
  appliedRevision: -1,
217
274
  canonicalSentRevision: -1,
275
+ canonicalProjection: null,
218
276
  });
219
277
  }
220
278
  else {
@@ -318,8 +376,6 @@ function notifyFrameRegistryListeners() {
318
376
  export function projectToAllReadyCards() {
319
377
  if (getCanvasMode() !== "canvas")
320
378
  return;
321
- const { css, revision: rev, instanceOverrides, structuralChanges, textContentChanges, componentOverrides } = computeProjection();
322
- for (const [cardId, iframe] of frameRegistry) {
323
- sendProjectionMessage(cardId, iframe, rev, css, instanceOverrides, structuralChanges, textContentChanges, componentOverrides, true);
324
- }
379
+ for (const [id, iframe] of frameRegistry)
380
+ sendProjectionToCard({ id }, iframe);
325
381
  }