@jxsuite/studio 0.33.0 → 0.35.0

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 (106) hide show
  1. package/dist/iframe-entry.js +6238 -0
  2. package/dist/iframe-entry.js.map +35 -0
  3. package/dist/studio.js +30972 -17222
  4. package/dist/studio.js.map +328 -83
  5. package/package.json +9 -9
  6. package/src/browse/browse.ts +11 -4
  7. package/src/canvas/canvas-helpers.ts +2 -56
  8. package/src/canvas/canvas-live-render.ts +102 -435
  9. package/src/canvas/canvas-origin.ts +66 -0
  10. package/src/canvas/canvas-patcher.ts +63 -403
  11. package/src/canvas/canvas-render.ts +70 -212
  12. package/src/canvas/canvas-utils.ts +37 -65
  13. package/src/canvas/iframe-channel.ts +154 -0
  14. package/src/canvas/iframe-drop.ts +484 -0
  15. package/src/canvas/iframe-entry.ts +600 -0
  16. package/src/canvas/iframe-host.ts +1373 -0
  17. package/src/canvas/iframe-inline-edit.ts +367 -0
  18. package/src/canvas/iframe-insert.ts +164 -0
  19. package/src/canvas/iframe-interaction.ts +176 -0
  20. package/src/canvas/iframe-keys.ts +85 -0
  21. package/src/canvas/iframe-overlay.ts +218 -0
  22. package/src/canvas/iframe-patch.ts +363 -0
  23. package/src/canvas/iframe-protocol.ts +361 -0
  24. package/src/canvas/iframe-render.ts +458 -0
  25. package/src/canvas/iframe-slash.ts +114 -0
  26. package/src/canvas/iframe-subtree.ts +113 -0
  27. package/src/canvas/path-mapping.ts +86 -0
  28. package/src/canvas/serialize-scope.ts +65 -0
  29. package/src/editor/canvas-context-menu.ts +40 -0
  30. package/src/editor/canvas-slash-bridge.ts +21 -0
  31. package/src/editor/context-menu.ts +2 -1
  32. package/src/editor/inline-edit-apply.ts +183 -0
  33. package/src/editor/inline-edit.ts +99 -21
  34. package/src/editor/inline-link.ts +89 -0
  35. package/src/editor/insert-zone-action.ts +35 -0
  36. package/src/editor/merge-tags.ts +26 -2
  37. package/src/editor/repeater-scope.ts +144 -0
  38. package/src/editor/shortcuts.ts +14 -28
  39. package/src/editor/slash-menu.ts +73 -42
  40. package/src/files/files.ts +5 -1
  41. package/src/new-project/design-fields.ts +260 -0
  42. package/src/new-project/import-tab.ts +322 -0
  43. package/src/new-project/new-project-modal.ts +472 -89
  44. package/src/new-project/templates.ts +61 -0
  45. package/src/packages/ensure-deps.ts +6 -0
  46. package/src/packages/pull-package-sync.ts +339 -0
  47. package/src/page-params.ts +383 -0
  48. package/src/panels/ai-chat/attached-context.ts +49 -0
  49. package/src/panels/ai-chat/chat-markdown.ts +38 -0
  50. package/src/panels/ai-chat/chat-view.ts +250 -0
  51. package/src/panels/ai-chat/composer.ts +315 -0
  52. package/src/panels/ai-chat/sessions-view.ts +98 -0
  53. package/src/panels/ai-panel.ts +189 -457
  54. package/src/panels/block-action-bar.ts +296 -138
  55. package/src/panels/canvas-dnd-bridge.ts +397 -0
  56. package/src/panels/component-preview.ts +56 -0
  57. package/src/panels/dnd.ts +41 -17
  58. package/src/panels/drag-ghost.ts +62 -0
  59. package/src/panels/editors.ts +1 -1
  60. package/src/panels/git-panel.ts +16 -1
  61. package/src/panels/overlays.ts +10 -125
  62. package/src/panels/properties-panel.ts +210 -0
  63. package/src/panels/right-panel.ts +21 -7
  64. package/src/panels/signals-panel.ts +136 -22
  65. package/src/panels/stylebook-doc.ts +373 -0
  66. package/src/panels/stylebook-panel.ts +46 -689
  67. package/src/panels/tab-bar.ts +159 -13
  68. package/src/panels/toolbar.ts +3 -2
  69. package/src/platforms/devserver.ts +45 -1
  70. package/src/services/agent-seed.ts +91 -0
  71. package/src/services/ai-models.ts +64 -0
  72. package/src/services/ai-session-store.ts +250 -0
  73. package/src/services/ai-settings.ts +5 -0
  74. package/src/services/automation.ts +140 -0
  75. package/src/services/document-assistant.ts +98 -38
  76. package/src/services/import-client.ts +134 -0
  77. package/src/services/monaco-setup.ts +12 -0
  78. package/src/services/render-critic.ts +9 -9
  79. package/src/services/settings-store.ts +93 -0
  80. package/src/settings/css-vars-editor.ts +2 -2
  81. package/src/store.ts +4 -62
  82. package/src/studio.ts +115 -40
  83. package/src/tabs/doc-op-apply.ts +89 -0
  84. package/src/tabs/patch-ops.ts +6 -2
  85. package/src/tabs/tab.ts +23 -4
  86. package/src/tabs/transact.ts +2 -74
  87. package/src/types.ts +98 -18
  88. package/src/ui/ai-credentials-form.ts +205 -0
  89. package/src/ui/jx-theme.ts +63 -0
  90. package/src/ui/media-picker.ts +6 -4
  91. package/src/ui/spectrum.ts +13 -0
  92. package/src/utils/canvas-media.ts +0 -137
  93. package/src/utils/edit-display.ts +23 -3
  94. package/src/utils/geometry.ts +43 -0
  95. package/src/utils/link-target.ts +93 -0
  96. package/src/utils/strip-events.ts +54 -0
  97. package/src/view.ts +0 -23
  98. package/src/workspace/workspace.ts +14 -1
  99. package/src/canvas/canvas-subtree-render.ts +0 -113
  100. package/src/editor/component-inline-edit.ts +0 -349
  101. package/src/editor/content-inline-edit.ts +0 -207
  102. package/src/editor/insertion-helper.ts +0 -308
  103. package/src/panels/canvas-dnd.ts +0 -329
  104. package/src/panels/panel-events.ts +0 -306
  105. package/src/panels/preview-render.ts +0 -132
  106. package/src/panels/pseudo-preview.ts +0 -75
@@ -0,0 +1,361 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * The versioned message protocol spoken across the {@link IframeChannel} between the editor (parent)
4
+ * and the canvas iframe. This is the Phase 1 message set (render the document); later phases extend
5
+ * both unions with selection/geometry/patch/DnD/inline-edit messages.
6
+ *
7
+ * Every parent→iframe message and the iframe's render acknowledgements carry a `gen` (generation)
8
+ * counter so the iframe can ignore stale commands and the parent can correlate acknowledgements —
9
+ * the cross-frame analog of the legacy renderer's `renderGeneration` staleness guard.
10
+ */
11
+
12
+ import type { JxDocOp } from "../tabs/patch-ops";
13
+ import type { JxContentResult, SlashCommand } from "../editor/inline-edit";
14
+ import type { JxMutableNode } from "@jxsuite/schema/types";
15
+
16
+ /**
17
+ * The wire form of a value-carrying document op (the `forward` half of a recorded
18
+ * {@link JxDocOpPair} — see {@link file://../tabs/patch-ops.ts}). It is structurally a
19
+ * {@link JxDocOp}: it carries the inserted/replaced `node` and the set `value`, NOT just a path, so
20
+ * the iframe can fold it into its shadow doc and re-render subtrees without ever reading the
21
+ * parent's reactive document.
22
+ */
23
+ export type WireDocOp = JxDocOp;
24
+
25
+ export const CANVAS_MODES = ["preview", "design", "edit", "stylebook"] as const;
26
+
27
+ /**
28
+ * How the iframe renders the document: live `preview`, instrumented `design`/`edit`, or the
29
+ * `stylebook` specimen catalog (hit/hover/measure only — no inline editing, DnD, or insert zones).
30
+ */
31
+ export type CanvasMode = (typeof CANVAS_MODES)[number];
32
+
33
+ export function isCanvasMode(value: unknown): value is CanvasMode {
34
+ return typeof value === "string" && (CANVAS_MODES as readonly string[]).includes(value);
35
+ }
36
+
37
+ /**
38
+ * Serializable form of the render path-mapping context. `arrayPaths` is a `Set` in the renderer but
39
+ * crosses the boundary as a string array (Sets aren't structured-clone-friendly across our wire).
40
+ */
41
+ export interface WireMapperCtx {
42
+ canvasMode: string;
43
+ layoutWrapped: boolean;
44
+ pageContentPrefix: (string | number)[] | null;
45
+ pageContentOffset: number | null;
46
+ arrayPaths: string[];
47
+ }
48
+
49
+ /** Messages the editor (parent) sends into the canvas iframe. */
50
+ export type ParentToIframe =
51
+ | { kind: "init"; gen: number }
52
+ | {
53
+ kind: "render";
54
+ doc: unknown;
55
+ // The RAW (pre-resolution) page document the forward ops are recorded against. The iframe
56
+ // Keeps it as a non-reactive shadow doc — its patch source-of-truth. `doc` above is the
57
+ // Resolved render doc (layout-wrapped); this raw doc's paths match the forward-op paths and
58
+ // The stamped data-jx-path attributes.
59
+ shadowDoc: unknown;
60
+ mode: CanvasMode;
61
+ docBase: string;
62
+ mapperCtx: WireMapperCtx;
63
+ siteStyle: Record<string, unknown> | null;
64
+ gen: number;
65
+ }
66
+ // Ask the iframe to measure the given document paths and post their current rects back. Used to
67
+ // Draw the selection overlay regardless of where the selection change originated (canvas click,
68
+ // Layers panel, keyboard) — the parent can't measure iframe nodes itself (cross-origin bridge).
69
+ | { kind: "measure"; paths: (string | number)[][]; reqId: number }
70
+ // Apply a surgical edit: fold each value-carrying forward op into the shadow doc and patch the DOM
71
+ // In place. `gen` matches the last render so the iframe drops patches superseded by a re-render.
72
+ | { kind: "patch"; forwardOps: WireDocOp[]; gen: number }
73
+ // Replace the rendered ROOT's style block in place (stylebook live style editing): the iframe
74
+ // Folds it into the shadow doc and re-runs the runtime's reapplyStyle on the root element, which
75
+ // Regenerates the whole scoped-CSS cascade (real @media included) without a re-render. `style` is
76
+ // Already transposed parent-side (transposeStylebookStyle); `gen` must equal the iframe's
77
+ // RenderedGen — a stale update is dropped (the superseding render carries the same style).
78
+ | { kind: "styleUpdate"; style: Record<string, unknown>; gen: number }
79
+ // Enter inline editing on the node at `path` (used to re-enter after a split/insert re-renders).
80
+ | { kind: "enterEdit"; path: (string | number)[] }
81
+ // Commit and end the inline-edit session if one is live (a no-op otherwise). Posted by the parent
82
+ // When focus/intent leaves the edit surface in the PARENT realm (tab switch, layers-panel click,
83
+ // Chrome pointerdown outside the edit toolbars) — the iframe can't observe those itself. Carries
84
+ // No identity: each host talks to exactly one iframe, so the parent routes the resulting
85
+ // EditCommit by the posting host's tab.
86
+ | { kind: "endEdit" }
87
+ // The parent slash menu resolved a selection — the iframe engine deletes the "/filter" text and
88
+ // Runs its insert flow (which posts editInsert back).
89
+ | { kind: "slashSelect"; cmd: SlashCommand }
90
+ // The parent dismissed the slash menu (outside click / Escape / no matches). The iframe bridge
91
+ // Flips closed but KEEPS its stored onSelect — the parent's select() dismisses the menu BEFORE it
92
+ // Fires onSelect, so a slashSelect may legitimately arrive after this.
93
+ | { kind: "slashDismissed" }
94
+ // Apply a format/link/insert intent to the iframe's cached selection range (4b-2 format toolbar).
95
+ // The parent toolbar lives in the parent realm but never touches the iframe Selection — it posts
96
+ // The author's intent and the iframe applies it where the edited DOM (and its Selection) live.
97
+ | {
98
+ kind: "applyFormat";
99
+ intent:
100
+ | {
101
+ command:
102
+ | "bold"
103
+ | "italic"
104
+ | "underline"
105
+ | "strikethrough"
106
+ | "subscript"
107
+ | "superscript"
108
+ | "code";
109
+ }
110
+ | { command: "link"; href: string | null } // Null/"" = remove
111
+ | { command: "insertData"; token: string };
112
+ }
113
+ // ─── Cross-frame DnD (Phase 4c spike) ──────────────────────────────────────
114
+ // Begin a drag session in the iframe: `src` is the realm-agnostic source kind, `dragSeq` is the
115
+ // Per-session id (parent drops any reply with a different seq), `gen` is the render the session
116
+ // Started against (parent drops dragOver/dropResult whose gen != the iframe's last-rendered gen).
117
+ | { kind: "dragStart"; src: DragSrcKind; dragSeq: number; gen: number }
118
+ // The pointer moved over the canvas. `cursor` is already converted to IFRAME-VIEWPORT coords.
119
+ | { kind: "dragMove"; cursor: { x: number; y: number }; dragSeq: number }
120
+ // The pointer was released over the canvas — the iframe computes the drop FRESH and posts dropResult.
121
+ | { kind: "drop"; cursor: { x: number; y: number }; dragSeq: number }
122
+ // The pointer left this canvas (dropped elsewhere / migrated to another panel): the iframe forgets
123
+ // The session + stops any auto-scroll. `dragMove`/`drop` arriving after are no-ops.
124
+ | { kind: "dragEnd"; dragSeq: number }
125
+ // The drag was cancelled (Escape/abort from the parent realm) — same teardown as dragEnd.
126
+ | { kind: "dragCancel"; dragSeq: number };
127
+
128
+ /** A node's bounding box, in the iframe's own viewport coordinates. */
129
+ export interface SerializableRect {
130
+ x: number;
131
+ y: number;
132
+ width: number;
133
+ height: number;
134
+ }
135
+
136
+ // ─── Cross-frame drag-and-drop (Phase 4c) ──────────────────────────────────────
137
+
138
+ /**
139
+ * The structural placement a drop resolves to, applied via the realm-agnostic
140
+ * `applyDropInstruction`.
141
+ */
142
+ export type DropInstructionType = "reorder-above" | "reorder-below" | "make-child";
143
+
144
+ /**
145
+ * The kind of thing being dragged. `tree-node` carries the source's existing document path (a
146
+ * move); `block` carries nothing on the wire — its full `fragment` (a {@link JxMutableNode}) is
147
+ * retained PARENT-SIDE keyed by `dragSeq` and never crosses the boundary.
148
+ */
149
+ export type DragSrcKind = { type: "tree-node"; path: (string | number)[] } | { type: "block" };
150
+
151
+ /**
152
+ * A display-only drop preview the iframe posts on `dragOver`: where the drop indicator should draw
153
+ * (`referenceRect`, in iframe-viewport coords) and the resolved structural placement. `edge` is the
154
+ * geometric side (added for the indicator slice; unused in the spike). The actual drop is
155
+ * recomputed FRESH in the `drop` handler — a preview is never the source of truth for the applied
156
+ * mutation.
157
+ */
158
+ export interface DropPreview {
159
+ instruction: DropInstructionType;
160
+ targetPath: (string | number)[];
161
+ referenceRect: SerializableRect;
162
+ edge: "top" | "bottom" | "inside";
163
+ }
164
+
165
+ /** A document path plus the iframe-space rect of the node it resolves to. */
166
+ export interface NodeHit {
167
+ path: (string | number)[];
168
+ rect: SerializableRect;
169
+ }
170
+
171
+ // ─── Cross-frame insertion affordance ("+") ─────────────────────────────────────
172
+
173
+ /**
174
+ * One candidate insertion point the iframe computed from its own pointer hit-test (the cross-origin
175
+ * cousin of the legacy insertion-helper). `edge` is the geometric side of the hovered node the "+"
176
+ * anchors to; the parent positions a clickable "+" from `rect` (a small anchor box in
177
+ * iframe-viewport coords, mapped via {@link canvasRectToParent} at scale=1 like the drop indicator).
178
+ * On click the parent runs the unchanged parent-realm slash-menu → mutateInsertNode flow with
179
+ * `insertParentPath` + `index`. A `center` zone is an empty container (insert as its first child);
180
+ * top/bottom/left/right insert a sibling before (`index`) or after the hovered node.
181
+ */
182
+ export interface InsertZone {
183
+ edge: "top" | "bottom" | "left" | "right" | "center";
184
+ insertParentPath: (string | number)[];
185
+ index: number;
186
+ rect: SerializableRect;
187
+ }
188
+
189
+ /**
190
+ * A keyboard event flattened for the bridge. The iframe forwards the global-shortcut subset (so
191
+ * undo/redo/save/delete/… still fire when focus is inside the canvas iframe); the parent rebuilds a
192
+ * synthetic `keydown` from these fields and dispatches it to its existing shortcut handler.
193
+ */
194
+ export interface SerializedKey {
195
+ key: string;
196
+ code: string;
197
+ ctrlKey: boolean;
198
+ metaKey: boolean;
199
+ shiftKey: boolean;
200
+ altKey: boolean;
201
+ }
202
+
203
+ /** Messages the canvas iframe sends back to the editor (parent). */
204
+ export type IframeToParent =
205
+ | { kind: "ready" }
206
+ | { kind: "renderComplete"; gen: number }
207
+ | { kind: "renderError"; gen: number; message: string }
208
+ // The iframe's measured content height in CSS px. The host sizes the iframe ELEMENT to this so the
209
+ // Canvas never scrolls internally — the parent canvas pans/scrolls instead, every node stays inside
210
+ // The iframe box (hit-testable), and the parent-drawn overlay tracks it (it can't follow an internal
211
+ // Scroll). Viewport units are transposed to container units (runtime `transposeCanvasUnits`) so a
212
+ // `100vh` section can't feed back into an ever-growing height. Posted after each render and on reflow.
213
+ // `fragment` = the rendered root is a component DEFINITION (a fragment, not a page): the host then
214
+ // Drops the iframe's 480px pre-measurement floor so a short component hugs its content.
215
+ | { kind: "contentHeight"; height: number; fragment: boolean }
216
+ // A serializable snapshot of the iframe's resolved `$defs` (buildScope's output — content
217
+ // Collections / `$prototype` data sources eagerly resolved). The parent adopts it into
218
+ // `S.canvas.scope` so the data-explorer panel shows live data instead of "pending" (the iframe,
219
+ // Not the parent, now resolves the scope). `gen` lets the parent drop a snapshot from a superseded
220
+ // Render. Posted right after `renderComplete`; values are JSON-safe deep clones (see serialize-scope).
221
+ | { kind: "dataScope"; gen: number; scope: Record<string, unknown> }
222
+ | { kind: "hit"; hit: NodeHit }
223
+ | { kind: "hover"; hit: NodeHit | null }
224
+ // Candidate insertion "+" zones for the hovered node, recomputed on pointermove (cross-origin
225
+ // Cousin of the legacy insertion-helper). `null` clears the "+" (cursor mid-element / left the
226
+ // Canvas). The parent draws a clickable "+" from each zone's rect and, on click, runs the
227
+ // Parent-realm slash-menu → mutateInsertNode flow with the zone's insertParentPath + index.
228
+ | { kind: "insertZones"; zones: InsertZone[] | null }
229
+ // Response to `measure`: the rects of whichever requested paths resolved to a node (missing paths
230
+ // Are simply omitted). `reqId` echoes the request so the parent can drop stale responses.
231
+ | { kind: "geometry"; reqId: number; hits: NodeHit[] }
232
+ // A patch applied cleanly (echoes gen so the host can re-measure the selection overlay).
233
+ | { kind: "patchComplete"; gen: number }
234
+ // A patch could not be applied surgically — the parent escalates to a full render.
235
+ | { kind: "patchError"; gen: number; message: string }
236
+ // A global-shortcut keystroke captured inside the iframe, for the parent to re-dispatch.
237
+ | { kind: "forwardKey"; event: SerializedKey }
238
+ // A wheel event over the canvas iframe, for the parent's zoom/pan handler. The iframe is sized to
239
+ // Its content (never scrolls itself) and a cross-origin OOPIF doesn't bubble wheel to the parent, so
240
+ // It forwards the deltas + modifiers + its own cursor (iframe-viewport coords) for the host to map.
241
+ | {
242
+ kind: "forwardWheel";
243
+ deltaX: number;
244
+ deltaY: number;
245
+ x: number;
246
+ y: number;
247
+ ctrlKey: boolean;
248
+ metaKey: boolean;
249
+ shiftKey: boolean;
250
+ }
251
+ // Inline editing started in the iframe (the parent shows the format toolbar from here).
252
+ | { kind: "editStart"; path: (string | number)[] }
253
+ // Committed inline-edit content (rich `children` else `textContent`) for the parent to persist.
254
+ | {
255
+ kind: "editCommit";
256
+ path: (string | number)[];
257
+ children: (JxMutableNode | string)[] | null;
258
+ textContent: string | null;
259
+ }
260
+ // Enter split a paragraph: keep `before` in the node, insert a new one with `after`.
261
+ | {
262
+ kind: "editSplit";
263
+ path: (string | number)[];
264
+ before: JxContentResult;
265
+ after: JxContentResult;
266
+ }
267
+ // Slash-insert: swap the tag in place when empty, else commit + insert a new element after.
268
+ | {
269
+ kind: "editInsert";
270
+ path: (string | number)[];
271
+ cmd: SlashCommand;
272
+ commitData: JxContentResult | undefined;
273
+ }
274
+ // A serializable selection snapshot for the parent format toolbar (4b-2). The iframe owns the
275
+ // Selection; the parent renders pressed-state/position from this and never reads the iframe DOM.
276
+ | {
277
+ kind: "selectionChanged";
278
+ // Monotonic per edit session; the parent drops snapshots with a stale (<=) seq.
279
+ seq: number;
280
+ path: (string | number)[];
281
+ // Strong/em/u/del/sub/sup/code/a active across the WHOLE selection (both endpoints).
282
+ activeTags: string[];
283
+ // Caret/selection bbox in IFRAME-VIEWPORT coords (null when unmeasurable).
284
+ rect: SerializableRect | null;
285
+ collapsed: boolean;
286
+ link: { active: boolean; href: string | null };
287
+ // D-B: always null this phase — repeater item/index merge-tag scope is a follow-up.
288
+ localScope: null;
289
+ }
290
+ // The inline-edit session ended.
291
+ | { kind: "editEnd" }
292
+ // ─── Slash-menu bridge ──────────────────────────────────────────────────────
293
+ // The engine (in the iframe) detected "/" in a live edit session; the parent shows the real
294
+ // Lit/Spectrum menu. Re-posted with a new `filter` as the author keeps typing (the engine's
295
+ // UpdateSlashMenu drives it). `rect` is the edited element's bbox in IFRAME-VIEWPORT coords.
296
+ | { kind: "slashShow"; rect: SerializableRect; filter: string }
297
+ // A menu-navigation key pressed in the iframe while the parent menu is open. The iframe
298
+ // Intercepts these four keys capture-phase (restoring the "menu captures Enter" contract) and the
299
+ // Host drives the parent menu's key handler directly — no synthetic keydown redispatch.
300
+ | { kind: "slashNav"; key: "ArrowUp" | "ArrowDown" | "Enter" | "Escape" }
301
+ // Iframe-side dismissal (backspace past the "/", session end, a click inside the iframe — the
302
+ // Parent's outside-click listener can't see iframe clicks).
303
+ | { kind: "slashDismiss" }
304
+ // ─── Context menu ───────────────────────────────────────────────────────────
305
+ // Right-click in the canvas. `path` is the nearest data-jx-path node (null on empty space — the
306
+ // Browser menu is still suppressed, legacy parity); x/y are IFRAME-VIEWPORT coords for the host
307
+ // To convert via its empirical geometry.
308
+ | { kind: "contextMenu"; path: (string | number)[] | null; x: number; y: number }
309
+ // ─── Cross-frame DnD (Phase 4c spike) ──────────────────────────────────────
310
+ // Flow 3 (grab-anywhere): the iframe detected a drag begin on an element body (pointerdown past a
311
+ // Movement threshold on a `[data-jx-path]`, NOT during an inline-edit). The parent starts a
312
+ // Coordinator session as a `tree-node` source with this `path` and synthesizes dragMove from its
313
+ // Own pointermove over the iframe. `dragSeq` is the iframe's pre-allocated session hint; the parent
314
+ // Bumps its own authoritative seq in beginDragSession.
315
+ | { kind: "dragOriginate"; path: (string | number)[]; dragSeq: number }
316
+ // A NATIVE drag stream entered this iframe with NO session bound here. Chromium delivers
317
+ // Dragover/drop to the frame UNDER THE CURSOR, so a parent-originated drag (palette/layers)
318
+ // Crosses onto the canvas without the parent ever seeing a cursor inside the iframe rect — it
319
+ // Never binds a host. On this message the bridge binds/migrates its live pragmatic session to
320
+ // This host (posting dragStart); the iframe's native handlers then drive dragOver/dropResult.
321
+ | { kind: "nativeDragEnter" }
322
+ // The iframe cancelled a flow-3 (iframe-originated) drag locally (Escape during a body-grab): the
323
+ // Parent tears down its ghost/indicator. Single-sourced through the iframe for that case so cancel
324
+ // Never double-fires (the parent-source flows cancel via pragmatic instead).
325
+ | { kind: "dragEnd"; dragSeq: number }
326
+ // A display-only drop preview for the parent's indicator. `gen` lets the parent drop previews
327
+ // Computed against a superseded render; `preview` is null when the cursor resolves to no drop.
328
+ // `cursor` is present ONLY for flow-3 (iframe-driven) drags, in IFRAME-VIEWPORT coords: the parent
329
+ // Has no pointer during an iframe-originated drag, so it positions the ghost by forward-converting
330
+ // This cursor. Parent-driven flows (1/2/4) omit it (the parent already has the raw cursor).
331
+ | {
332
+ kind: "dragOver";
333
+ dragSeq: number;
334
+ gen: number;
335
+ preview: DropPreview | null;
336
+ cursor?: { x: number; y: number };
337
+ }
338
+ // The resolved drop, computed FRESH from the current DOM. The parent (if non-stale, non-null)
339
+ // Applies it via applyDropInstruction with the retained source data.
340
+ | {
341
+ kind: "dropResult";
342
+ dragSeq: number;
343
+ gen: number;
344
+ instruction: DropInstructionType | null;
345
+ targetPath: (string | number)[] | null;
346
+ };
347
+
348
+ /** The iframe→parent selection snapshot that drives the parent format toolbar. */
349
+ export type SelectionSnapshot = Extract<IframeToParent, { kind: "selectionChanged" }>;
350
+
351
+ /** The author's format/link/insert intent the parent toolbar posts back to the iframe. */
352
+ export type ApplyFormatIntent = Extract<ParentToIframe, { kind: "applyFormat" }>["intent"];
353
+
354
+ /** The parent→iframe drag-session start message (Phase 4c). */
355
+ export type DragStartMsg = Extract<ParentToIframe, { kind: "dragStart" }>;
356
+
357
+ /** The iframe→parent display-only drop-preview message (Phase 4c). */
358
+ export type DragOverMsg = Extract<IframeToParent, { kind: "dragOver" }>;
359
+
360
+ /** The iframe→parent insertion-affordance ("+") message. */
361
+ export type InsertZonesMsg = Extract<IframeToParent, { kind: "insertZones" }>;