@voithos-labs/aragonite 0.10.2 → 0.10.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 (255) hide show
  1. package/README.md +6 -21
  2. package/THIRD-PARTY-NOTICES.md +25 -0
  3. package/dist/a11y-strings.d.ts +18 -0
  4. package/dist/a11y-strings.js +18 -0
  5. package/dist/action-contracts.d.ts +7 -1
  6. package/dist/ambient/ambient-dom.js +5 -1
  7. package/dist/block-component.d.ts +14 -0
  8. package/dist/components/BlockDragHandle.svelte +35 -24
  9. package/dist/components/BlockHost.svelte +16 -9
  10. package/dist/components/Editor.svelte +381 -198
  11. package/dist/components/Editor.svelte.d.ts +1 -1
  12. package/dist/components/SelectionOverlay.svelte +17 -3
  13. package/dist/components/TailInsert.svelte +107 -0
  14. package/dist/components/TailInsert.svelte.d.ts +17 -0
  15. package/dist/components/block-content-selector.d.ts +6 -2
  16. package/dist/components/block-content-selector.js +6 -2
  17. package/dist/components/blocks/ThematicBreakBlock.svelte +19 -6
  18. package/dist/components/blocks/ThematicBreakBlock.svelte.d.ts +1 -0
  19. package/dist/components/blocks/code/CodeBlock.svelte +211 -30
  20. package/dist/components/blocks/code/CodeBlockRail.svelte +686 -0
  21. package/dist/components/blocks/code/CodeBlockRail.svelte.d.ts +26 -0
  22. package/dist/components/blocks/code/code-bootstrap.js +4 -0
  23. package/dist/components/blocks/code/code-context-actions.d.ts +1 -0
  24. package/dist/components/blocks/code/code-context-actions.js +24 -0
  25. package/dist/components/blocks/code/code-fence-exit.d.ts +15 -0
  26. package/dist/components/blocks/code/code-fence-exit.js +26 -0
  27. package/dist/components/blocks/code/code-languages.d.ts +6 -0
  28. package/dist/components/blocks/code/code-languages.js +11 -0
  29. package/dist/components/blocks/code/code-renderer.js +11 -0
  30. package/dist/components/blocks/directive/DirectiveContainerBlock.svelte +1 -1
  31. package/dist/components/blocks/editable-leaf.d.ts +37 -6
  32. package/dist/components/blocks/editable-leaf.js +243 -29
  33. package/dist/components/blocks/editable-surface.d.ts +9 -0
  34. package/dist/components/blocks/editable-surface.js +22 -3
  35. package/dist/components/blocks/list/ListItemBlock.svelte +3 -3
  36. package/dist/components/blocks/list/task-checkbox.d.ts +2 -0
  37. package/dist/components/blocks/list/task-checkbox.js +11 -2
  38. package/dist/components/blocks/surface-wiring.svelte.d.ts +4 -0
  39. package/dist/components/blocks/surface-wiring.svelte.js +8 -1
  40. package/dist/components/blocks/table/TableActionMenu.svelte +210 -86
  41. package/dist/components/blocks/table/TableActionMenu.svelte.d.ts +5 -0
  42. package/dist/components/blocks/table/TableBlock.svelte +205 -186
  43. package/dist/components/blocks/table/TableBlock.svelte.d.ts +1 -0
  44. package/dist/components/blocks/table/TableCellBlock.svelte +87 -22
  45. package/dist/components/blocks/table/TableRowBlock.svelte +4 -18
  46. package/dist/components/blocks/table/TableRowBlock.svelte.d.ts +0 -5
  47. package/dist/components/blocks/table/cell-clipboard.d.ts +10 -0
  48. package/dist/components/blocks/table/cell-clipboard.js +34 -1
  49. package/dist/components/blocks/table/cell-keydown-plan.d.ts +1 -1
  50. package/dist/components/blocks/table/cell-keydown-plan.js +3 -1
  51. package/dist/components/blocks/table/table-cell-paste.js +2 -1
  52. package/dist/components/blocks/table/table-menu-model.d.ts +35 -8
  53. package/dist/components/blocks/table/table-menu-model.js +36 -17
  54. package/dist/components/blocks/text/TextEditableBlock.svelte +58 -11
  55. package/dist/components/blocks/text/delimiter-autopair.d.ts +71 -0
  56. package/dist/components/blocks/text/delimiter-autopair.js +216 -0
  57. package/dist/components/blocks/text/edge-policy-dispatch.d.ts +4 -0
  58. package/dist/components/blocks/text/edge-policy-dispatch.js +56 -2
  59. package/dist/components/blocks/text/live-selection-edit.js +27 -0
  60. package/dist/components/blocks/text/text-keydown.d.ts +7 -1
  61. package/dist/components/blocks/text/text-keydown.js +11 -1
  62. package/dist/components/blocks/text/text-render.d.ts +1 -1
  63. package/dist/components/blocks/text/text-render.js +3 -1
  64. package/dist/components/blocks/text/widget-interaction.d.ts +3 -0
  65. package/dist/components/blocks/text/widget-interaction.js +144 -22
  66. package/dist/components/drag-handle.d.ts +35 -0
  67. package/dist/components/drag-handle.js +126 -0
  68. package/dist/components/editor-root-focus.d.ts +19 -0
  69. package/dist/components/editor-root-focus.js +67 -0
  70. package/dist/components/editor-root-geometry.d.ts +39 -0
  71. package/dist/components/editor-root-geometry.js +91 -0
  72. package/dist/components/editor-root-keydown.d.ts +1 -1
  73. package/dist/components/editor-root-keydown.js +12 -3
  74. package/dist/components/editor-root-listeners.d.ts +10 -6
  75. package/dist/components/editor-root-listeners.js +23 -22
  76. package/dist/components/editor-root-mode-flip.d.ts +36 -0
  77. package/dist/components/editor-root-mode-flip.js +92 -0
  78. package/dist/components/image/ImageOverlayHost.svelte +14 -6
  79. package/dist/components/image/ImageProperties.svelte +512 -70
  80. package/dist/components/image/ImageProperties.svelte.d.ts +6 -1
  81. package/dist/components/image/ImageResizeHandles.svelte +59 -34
  82. package/dist/components/image/image-crop.d.ts +39 -0
  83. package/dist/components/image/image-crop.js +74 -0
  84. package/dist/components/image/image-edit-commit.d.ts +1 -0
  85. package/dist/components/image/image-edit-commit.js +21 -5
  86. package/dist/components/image/image-source-bytes.js +10 -3
  87. package/dist/components/image/image-widget-editing.js +1 -0
  88. package/dist/components/image/widget-dom.js +5 -1
  89. package/dist/components/link-card/link-card-commit.js +1 -1
  90. package/dist/components/lrd-map-gate.js +1 -1
  91. package/dist/components/menu/BlockMenu.svelte +315 -0
  92. package/dist/components/menu/BlockMenu.svelte.d.ts +34 -0
  93. package/dist/components/menu/MenuIcon.svelte +153 -0
  94. package/dist/components/menu/MenuIcon.svelte.d.ts +51 -0
  95. package/dist/components/menu/clipboard-actions.d.ts +12 -0
  96. package/dist/components/menu/clipboard-actions.js +42 -0
  97. package/dist/components/menu/default-context-actions.d.ts +15 -0
  98. package/dist/components/menu/default-context-actions.js +76 -0
  99. package/dist/components/menu/flyout-placement.d.ts +6 -0
  100. package/dist/components/menu/flyout-placement.js +25 -0
  101. package/dist/core/inline/format-toggle.d.ts +12 -4
  102. package/dist/core/inline/format-toggle.js +94 -40
  103. package/dist/core/inline/image-dimensions.d.ts +3 -0
  104. package/dist/core/inline/image-dimensions.js +46 -10
  105. package/dist/core/inline/inline-widgets.d.ts +11 -0
  106. package/dist/core/inline/scan/brackets.js +1 -0
  107. package/dist/core/inline/scan/plugin-syntax.d.ts +8 -0
  108. package/dist/core/inline/scan/plugin-syntax.js +15 -1
  109. package/dist/core/inline-render.d.ts +6 -0
  110. package/dist/core/inline-render.js +32 -0
  111. package/dist/core/nodes.d.ts +14 -0
  112. package/dist/cursor/edge-affinity.js +2 -1
  113. package/dist/cursor/overlay-remeasure.js +8 -0
  114. package/dist/cursor/reveal-source.js +7 -2
  115. package/dist/cursor/widget-offset.d.ts +6 -0
  116. package/dist/cursor/widget-offset.js +61 -4
  117. package/dist/debug/interaction-trace.d.ts +4 -0
  118. package/dist/debug/interaction-trace.js +15 -0
  119. package/dist/decorations/decoration-state.svelte.js +1 -1
  120. package/dist/editor-actions/ancestry-folds.d.ts +2 -2
  121. package/dist/editor-actions/ancestry-folds.js +1 -1
  122. package/dist/editor-actions/block-edit-scope.js +1 -1
  123. package/dist/editor-actions/commit/text-batch.d.ts +3 -2
  124. package/dist/editor-actions/commit/text-batch.js +1 -1
  125. package/dist/editor-actions/commit/undo-controller.js +4 -2
  126. package/dist/editor-actions/container-edit.js +2 -1
  127. package/dist/editor-actions/enter-completion.d.ts +2 -0
  128. package/dist/editor-actions/enter-completion.js +23 -2
  129. package/dist/editor-actions/inline-range-commit.js +1 -1
  130. package/dist/editor-actions/reorder-action.js +19 -10
  131. package/dist/editor-actions/reorder-drag.js +29 -1
  132. package/dist/editor-actions/replacement-focus.d.ts +1 -1
  133. package/dist/editor-actions/replacement-focus.js +1 -1
  134. package/dist/editor-actions/search-replace.js +1 -1
  135. package/dist/editor-actions/table-context.d.ts +4 -1
  136. package/dist/editor-actions/table-context.js +57 -1
  137. package/dist/editor-events.d.ts +3 -0
  138. package/dist/editor-keys.d.ts +33 -0
  139. package/dist/editor-props.d.ts +21 -12
  140. package/dist/index.d.ts +1 -1
  141. package/dist/plugin.d.ts +6 -0
  142. package/dist/plugin.js +11 -0
  143. package/dist/plugins/latex/BlockMath.svelte +264 -29
  144. package/dist/plugins/latex/BlockMath.svelte.d.ts +2 -0
  145. package/dist/plugins/latex/index.d.ts +2 -1
  146. package/dist/plugins/latex/latex-kind.js +36 -3
  147. package/dist/plugins/latex/math-completion.js +4 -1
  148. package/dist/plugins/latex/math-layout.d.ts +15 -0
  149. package/dist/plugins/latex/math-layout.js +13 -0
  150. package/dist/plugins/latex/math-source.d.ts +19 -0
  151. package/dist/plugins/latex/math-source.js +97 -0
  152. package/dist/plugins/latex/register.d.ts +11 -2
  153. package/dist/plugins/latex/register.js +3 -1
  154. package/dist/plugins/latex/renderer.d.ts +3 -3
  155. package/dist/plugins/latex/renderer.js +13 -6
  156. package/dist/reactivity/list-windowing.svelte.d.ts +8 -8
  157. package/dist/reactivity/list-windowing.svelte.js +49 -24
  158. package/dist/schema/block-completions.d.ts +8 -0
  159. package/dist/schema/block-completions.js +11 -0
  160. package/dist/schema/context-actions.d.ts +31 -0
  161. package/dist/schema/context-actions.js +23 -0
  162. package/dist/schema/fenced-code-raw.js +31 -1
  163. package/dist/schema/operations.d.ts +8 -1
  164. package/dist/schema/reserved-chords.js +25 -4
  165. package/dist/schema/table-cell-raw.d.ts +1 -1
  166. package/dist/schema/table-cell-raw.js +1 -1
  167. package/dist/selection/block-hit-test.js +3 -2
  168. package/dist/selection/char-endpoint-snap.js +1 -1
  169. package/dist/selection/clipboard-text.js +6 -1
  170. package/dist/selection/covered-block.d.ts +10 -0
  171. package/dist/selection/covered-block.js +24 -0
  172. package/dist/selection/cross-block/dispatch.d.ts +3 -0
  173. package/dist/selection/cross-block/dispatch.js +13 -1
  174. package/dist/selection/cross-block/format-range.d.ts +1 -1
  175. package/dist/selection/cross-block/format-range.js +4 -15
  176. package/dist/selection/cross-block/format-toggle.js +1 -1
  177. package/dist/selection/cross-block/keydown.js +1 -1
  178. package/dist/selection/cross-block/ops.js +1 -1
  179. package/dist/selection/cross-block/paste.js +32 -30
  180. package/dist/selection/cross-block/type-replace.d.ts +3 -2
  181. package/dist/selection/cross-block/type-replace.js +62 -13
  182. package/dist/selection/dead-space-caret.d.ts +10 -0
  183. package/dist/selection/dead-space-caret.js +47 -1
  184. package/dist/selection/double-click-trim.d.ts +17 -0
  185. package/dist/selection/double-click-trim.js +57 -0
  186. package/dist/selection/drag-pointer.d.ts +7 -2
  187. package/dist/selection/drag-pointer.js +61 -2
  188. package/dist/selection/gap-caret.js +1 -1
  189. package/dist/selection/keyboard-extend.js +1 -1
  190. package/dist/selection/path-lookup.js +1 -1
  191. package/dist/selection/range-delete-ceremony.js +4 -2
  192. package/dist/selection/range-delete-chrome.js +2 -1
  193. package/dist/selection/range-delete-table-coverage.js +2 -1
  194. package/dist/selection/range-delete-table.js +3 -2
  195. package/dist/selection/range-delete.js +30 -2
  196. package/dist/selection/selection-restore.js +1 -1
  197. package/dist/selection/selection-state.svelte.d.ts +6 -0
  198. package/dist/selection/selection-state.svelte.js +36 -1
  199. package/dist/selection/table-endpoint-snap.js +1 -1
  200. package/dist/selection/table-rect-extend.js +1 -1
  201. package/dist/styles/editor-theme.css +57 -28
  202. package/dist/styles/editor.css +217 -18
  203. package/dist/testing/container-conformance.js +2 -2
  204. package/dist/testing/inline-conformance.js +2 -1
  205. package/dist/tree-operations/blockquote.js +1 -1
  206. package/dist/tree-operations/chain-rebuild.d.ts +63 -0
  207. package/dist/tree-operations/chain-rebuild.js +142 -0
  208. package/dist/tree-operations/children.d.ts +1 -1
  209. package/dist/tree-operations/children.js +1 -1
  210. package/dist/tree-operations/cleanup.js +1 -1
  211. package/dist/tree-operations/content-write.d.ts +50 -0
  212. package/dist/tree-operations/content-write.js +263 -0
  213. package/dist/tree-operations/index.d.ts +8 -3
  214. package/dist/tree-operations/index.js +6 -2
  215. package/dist/tree-operations/list/exit-replacement.js +1 -1
  216. package/dist/tree-operations/list/unwrap-merge.js +3 -3
  217. package/dist/tree-operations/node-ops.d.ts +17 -234
  218. package/dist/tree-operations/node-ops.js +47 -1113
  219. package/dist/tree-operations/node-primitives.d.ts +75 -0
  220. package/dist/tree-operations/node-primitives.js +117 -0
  221. package/dist/tree-operations/paste/apply.js +1 -1
  222. package/dist/tree-operations/paste/body-write.d.ts +1 -1
  223. package/dist/tree-operations/paste/body-write.js +2 -2
  224. package/dist/tree-operations/paste/container-match.js +4 -2
  225. package/dist/tree-operations/paste/dispatch.js +2 -1
  226. package/dist/tree-operations/paste/find-enclosing-list.js +1 -1
  227. package/dist/tree-operations/paste/focus-target.d.ts +1 -1
  228. package/dist/tree-operations/paste/list-absorb.js +1 -1
  229. package/dist/tree-operations/paste/list-break-out.js +1 -1
  230. package/dist/tree-operations/paste/parent-scope.js +1 -1
  231. package/dist/tree-operations/paste/paste-replacement.js +1 -1
  232. package/dist/tree-operations/paste/replace-block-at-parent.js +1 -1
  233. package/dist/tree-operations/path-mutate.d.ts +1 -1
  234. package/dist/tree-operations/path-mutate.js +2 -1
  235. package/dist/tree-operations/reorder-unit.js +1 -1
  236. package/dist/tree-operations/reorder.d.ts +5 -2
  237. package/dist/tree-operations/reorder.js +55 -2
  238. package/dist/tree-operations/settle.d.ts +105 -0
  239. package/dist/tree-operations/settle.js +660 -0
  240. package/dist/tree-operations/table-grid-clipboard.d.ts +21 -0
  241. package/dist/tree-operations/table-grid-clipboard.js +90 -0
  242. package/dist/tree-operations/unshare.d.ts +15 -77
  243. package/dist/tree-operations/unshare.js +15 -166
  244. package/docs/guide/consumer-guide.md +126 -96
  245. package/docs/guide/plugin-api.md +31 -3
  246. package/docs/guide/plugin-guide.md +26 -3
  247. package/package.json +4 -2
  248. package/dist/components/blocks/code/CodeLanguageChip.svelte +0 -127
  249. package/dist/components/blocks/code/CodeLanguageChip.svelte.d.ts +0 -14
  250. package/dist/components/blocks/table/TableGrip.svelte +0 -91
  251. package/dist/components/blocks/table/TableGrip.svelte.d.ts +0 -8
  252. package/dist/components/blocks/table/table-drop-target.d.ts +0 -1
  253. package/dist/components/blocks/table/table-drop-target.js +0 -16
  254. package/dist/components/blocks/table/table-reorder-drag.d.ts +0 -78
  255. package/dist/components/blocks/table/table-reorder-drag.js +0 -97
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The parent shapes, mints and path walks under every tree op. Children-array contract: an op
3
+ * mutating a container's top-level children takes the array as a parameter and mutates that,
4
+ * never `node.children` (the caller owns and republishes it, so a direct splice is overwritten).
5
+ * A descendant found by walking the live tree is the exception: mutate it in place on a
6
+ * caller-unshared spine (`unshare.ts`), a STRUCTURAL one via `commitMultiScope`.
7
+ */
8
+ import type { AnyBlockKind, CstNode, Document } from '../core/nodes';
9
+ import type { DocumentView, NodeView } from '../core/node-views';
10
+ import type { GrammarView } from '../schema/block-openers';
11
+ /** A children array an op mutates structurally: splice, delete, reorder. */
12
+ export type NodeParent = {
13
+ children: CstNode[];
14
+ };
15
+ /**
16
+ * A {@link NodeParent} that has answered which container owns it, for the owner's `bodyWrite`
17
+ * grammar and wrap slots. Nullable, not optional: skipping the question is a compile error.
18
+ */
19
+ export type BodyParent = NodeParent & {
20
+ ownerKind: AnyBlockKind | undefined;
21
+ owner: CstNode | undefined;
22
+ suffix?: string;
23
+ };
24
+ /** What the byte sinks accept: a whole `Document` IS the answer (the root owns no body grammar). */
25
+ export type BodyParentArg = BodyParent | Document;
26
+ /**
27
+ * What the separator settles accept: anything that can answer where the body starts. Wider than
28
+ * the byte sinks, since a settle writes a line ending, not body text.
29
+ */
30
+ export type SeparatorParent = {
31
+ kind?: string;
32
+ ownerKind?: AnyBlockKind;
33
+ suffix?: string;
34
+ children?: CstNode[];
35
+ owner?: CstNode;
36
+ };
37
+ /** Text made legal as a child's raw inside a container of kind `ownerKind`. */
38
+ export declare function normalizeBodyWrite(ownerKind: AnyBlockKind | undefined, raw: string): string;
39
+ /** {@link normalizeBodyWrite} for a sink holding the parent rather than the owner's kind. */
40
+ export declare const forBody: (parent: BodyParentArg, raw: string) => string;
41
+ /**
42
+ * `raw` made legal as `node`'s OWN bytes, for a sink that REPLACES the node with a reparse: the
43
+ * reparse re-derives metadata, so structure the rule restores from the old metadata lands first.
44
+ */
45
+ export declare function normalizeOwnRaw(node: NodeView, raw: string): string;
46
+ /**
47
+ * Write `raw` as `node`'s OWN bytes through its kind's rule, in place. Every sink writing a
48
+ * leaf's bytes without the kind's surface in front of it owes this or {@link normalizeOwnRaw}.
49
+ */
50
+ export declare function writeOwnRaw(node: CstNode, raw: string, grammar: GrammarView | undefined): void;
51
+ /**
52
+ * Every argument is required: a paragraph's raw ENDS in a line ending, so a mint site answers
53
+ * which document it lands in (G4.20). Fresh every call, or a shared instance aliases across
54
+ * tree positions (G1.9).
55
+ */
56
+ export declare function paragraphNode(leadingTrivia: string, text: string, lineEnding: string): CstNode;
57
+ /** The empty-paragraph placeholder keeping an emptied document or container caret-addressable. */
58
+ export declare function emptyParagraph(leadingTrivia: string, lineEnding: string): CstNode;
59
+ export declare function nodeAt(doc: Document, path: number[]): CstNode | Document | null;
60
+ export declare function nodeAt(doc: DocumentView, path: number[]): NodeView | DocumentView | null;
61
+ /** `nodeAt` pre-narrowed through `isBlockNode`: null at the document root or on no match. */
62
+ export declare function blockNodeAt(doc: Document, path: number[]): CstNode | null;
63
+ export declare function blockNodeAt(doc: DocumentView, path: number[]): NodeView | null;
64
+ export declare function isBlockNode(node: CstNode | Document): node is CstNode;
65
+ export declare function isBlockNode(node: NodeView | DocumentView): node is NodeView;
66
+ /**
67
+ * The stand-in for whatever the user types next: the maximally-continuable line, so a probe
68
+ * answers for the worst case. Openers are arbitrary code, so `probeLineOpensAsProse` is the
69
+ * runtime check that it still reads as prose.
70
+ */
71
+ export declare const NEXT_PROSE_LINE = "x";
72
+ /** First node inherits the original block's leadingTrivia; subsequent nodes keep theirs. */
73
+ export declare function normalizeReplacementTrivia(original: CstNode, replacement: CstNode[]): CstNode[];
74
+ /** Ensure every container has at least one child block, so the cursor always has a target. */
75
+ export declare function ensureEditableContainers(node: CstNode): void;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * The parent shapes, mints and path walks under every tree op. Children-array contract: an op
3
+ * mutating a container's top-level children takes the array as a parameter and mutates that,
4
+ * never `node.children` (the caller owns and republishes it, so a direct splice is overwritten).
5
+ * A descendant found by walking the live tree is the exception: mutate it in place on a
6
+ * caller-unshared spine (`unshare.ts`), a STRUCTURAL one via `commitMultiScope`.
7
+ */
8
+ import { parse } from '../core/parser';
9
+ import { trailingLineEnding } from '../core/lines';
10
+ import { getBlockKindDescriptor, tryGetBlockKindDescriptor } from '../schema/block-kind-descriptor';
11
+ import { reservedChromeKindOf } from '../schema/reserved-chrome';
12
+ const ownerKindOf = (parent) => 'ownerKind' in parent ? parent.ownerKind : undefined;
13
+ /** Text made legal as a child's raw inside a container of kind `ownerKind`. */
14
+ export function normalizeBodyWrite(ownerKind, raw) {
15
+ const owner = ownerKind === undefined ? undefined : tryGetBlockKindDescriptor(ownerKind);
16
+ return owner?.bodyWrite?.normalize(raw) ?? raw;
17
+ }
18
+ /** {@link normalizeBodyWrite} for a sink holding the parent rather than the owner's kind. */
19
+ export const forBody = (parent, raw) => normalizeBodyWrite(ownerKindOf(parent), raw);
20
+ /**
21
+ * `raw` made legal as `node`'s OWN bytes, for a sink that REPLACES the node with a reparse: the
22
+ * reparse re-derives metadata, so structure the rule restores from the old metadata lands first.
23
+ */
24
+ export function normalizeOwnRaw(node, raw) {
25
+ return tryGetBlockKindDescriptor(node.kind)?.normalizeRawWrite?.(raw, node) ?? raw;
26
+ }
27
+ /**
28
+ * Write `raw` as `node`'s OWN bytes through its kind's rule, in place. Every sink writing a
29
+ * leaf's bytes without the kind's surface in front of it owes this or {@link normalizeOwnRaw}.
30
+ */
31
+ export function writeOwnRaw(node, raw, grammar) {
32
+ const descriptor = tryGetBlockKindDescriptor(node.kind);
33
+ const legal = descriptor?.normalizeRawWrite?.(raw, node) ?? raw;
34
+ node.raw = legal;
35
+ // A context-dependent kind's raw does not reparse to itself, so a fragment parse would only
36
+ // mis-read metadata that was never parse-derived.
37
+ if (descriptor?.contextDependentKind)
38
+ return;
39
+ // In place means no reparse replaces the node, so parse-owned metadata re-derives here.
40
+ const reparsed = parse(legal, { grammar, scope: 'fragment' }).children;
41
+ if (reparsed.length === 1 && reparsed[0].kind === node.kind)
42
+ node.metadata = reparsed[0].metadata;
43
+ }
44
+ // ── Node minting ──
45
+ /**
46
+ * Every argument is required: a paragraph's raw ENDS in a line ending, so a mint site answers
47
+ * which document it lands in (G4.20). Fresh every call, or a shared instance aliases across
48
+ * tree positions (G1.9).
49
+ */
50
+ export function paragraphNode(leadingTrivia, text, lineEnding) {
51
+ return { kind: 'paragraph', leadingTrivia, raw: text + lineEnding };
52
+ }
53
+ /** The empty-paragraph placeholder keeping an emptied document or container caret-addressable. */
54
+ export function emptyParagraph(leadingTrivia, lineEnding) {
55
+ return paragraphNode(leadingTrivia, '', lineEnding);
56
+ }
57
+ export function nodeAt(doc, path) {
58
+ let cur = doc;
59
+ for (const idx of path) {
60
+ if (!cur.children || idx < 0 || idx >= cur.children.length)
61
+ return null;
62
+ cur = cur.children[idx];
63
+ }
64
+ return cur;
65
+ }
66
+ export function blockNodeAt(doc, path) {
67
+ const node = nodeAt(doc, path);
68
+ return node !== null && isBlockNode(node) ? node : null;
69
+ }
70
+ export function isBlockNode(node) {
71
+ return 'raw' in node;
72
+ }
73
+ // ── Grammar stand-in and replacement shape ──
74
+ /**
75
+ * The stand-in for whatever the user types next: the maximally-continuable line, so a probe
76
+ * answers for the worst case. Openers are arbitrary code, so `probeLineOpensAsProse` is the
77
+ * runtime check that it still reads as prose.
78
+ */
79
+ export const NEXT_PROSE_LINE = 'x';
80
+ /** First node inherits the original block's leadingTrivia; subsequent nodes keep theirs. */
81
+ export function normalizeReplacementTrivia(original, replacement) {
82
+ const originalTrivia = original.leadingTrivia ?? '';
83
+ return replacement.map((node, i) => {
84
+ const copy = { ...node };
85
+ copy.leadingTrivia = i === 0 ? originalTrivia : (copy.leadingTrivia ?? '');
86
+ return copy;
87
+ });
88
+ }
89
+ // ── Editable container backfill ──
90
+ /** Ensure every container has at least one child block, so the cursor always has a target. */
91
+ export function ensureEditableContainers(node) {
92
+ // A whole-block-focus kind is childless by design: the block itself is the caret target,
93
+ // and a backfilled paragraph its raw can't account for trips opaque-stale-raw.
94
+ if (getBlockKindDescriptor(node.kind).blockFocus === 'whole-block')
95
+ return;
96
+ if (node.children !== undefined) {
97
+ if (node.children.length === 0) {
98
+ // discovered-descendant mutation, see file header
99
+ const chromeKind = reservedChromeKindOf(node.kind);
100
+ // Backfilled lines are pure line ending, so they take the container's own (G4.20).
101
+ const lineEnding = trailingLineEnding(node.raw);
102
+ // A chrome-declaring container re-mints its child-0 leaf too, or the backfilled
103
+ // paragraph would occupy the reserved slot (G1.14).
104
+ if (chromeKind !== undefined) {
105
+ // Runtime chrome kind, so the mint takes the generic cast.
106
+ node.children.push({ kind: chromeKind, leadingTrivia: '', raw: lineEnding });
107
+ }
108
+ node.children.push(emptyParagraph('', lineEnding));
109
+ // The synthesized paragraph's ending already represents the blank `parseBlocks`
110
+ // routed into innerPrefix; keeping both double-counts the line on rebuild.
111
+ node.innerPrefix = '';
112
+ }
113
+ for (const child of node.children) {
114
+ ensureEditableContainers(child);
115
+ }
116
+ }
117
+ }
@@ -1,5 +1,5 @@
1
1
  /** Applies the results a paste surface hook produced to the document. */
2
- import { settledCaretTarget, updateNodeContent } from '../node-ops';
2
+ import { settledCaretTarget, updateNodeContent } from '../content-write';
3
3
  import { ensureUnsharedChild } from '../unshare';
4
4
  import { docPathFrom } from '../../cursor/coordinate-spaces';
5
5
  import { stampStructuralChange } from '../structural-change';
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The paste family's `bodyWrite` moment: paste builds nodes upstream of every
3
- * node-ops byte sink, so a container's escape lands here instead — on the clipboard text
3
+ * content-write byte sink, so a container's escape lands here instead — on the clipboard text
4
4
  * ahead of the strategy-picking parse, and on the built replacement at the splice.
5
5
  */
6
6
  import type { AnyBlockKind, CstNode, Document } from '../../core/nodes';
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * The paste family's `bodyWrite` moment: paste builds nodes upstream of every
3
- * node-ops byte sink, so a container's escape lands here instead — on the clipboard text
3
+ * content-write byte sink, so a container's escape lands here instead — on the clipboard text
4
4
  * ahead of the strategy-picking parse, and on the built replacement at the splice.
5
5
  */
6
6
  import { parse } from '../../core/parser';
7
7
  import { tryGetBlockKindDescriptor } from '../../schema/block-kind-descriptor';
8
- import { ensureEditableContainers, isBlockNode, nodeAt, normalizeBodyWrite, normalizeReplacementTrivia } from '../node-ops';
8
+ import { ensureEditableContainers, isBlockNode, nodeAt, normalizeBodyWrite, normalizeReplacementTrivia } from '../node-primitives';
9
9
  /** Clipboard text made legal inside every `bodyWrite`-declaring ancestor of the paste target. */
10
10
  export function normalizeClipboardForBody(doc, targetPath, text) {
11
11
  let out = text;
@@ -6,10 +6,12 @@
6
6
  import { CURSOR_END } from '../../block-component';
7
7
  import { devWarn } from '../../dev-warn';
8
8
  import { trailingLineEnding, trimTrailingLineEnding } from '../../core/lines';
9
- import { nodeAt, settledCaretTarget, updateNodeContent, writeOwnRaw } from '../node-ops';
9
+ import { nodeAt, writeOwnRaw } from '../node-primitives';
10
+ import { settledCaretTarget, updateNodeContent } from '../content-write';
10
11
  import { containerPasteFor } from './container-paste';
11
12
  import { rebuildContainerRawIfContainer } from '../../schema/container-raw';
12
- import { ensureUnsharedNode, ensureUnsharedPath, rebuildUnsharedChain } from '../unshare';
13
+ import { ensureUnsharedNode, ensureUnsharedPath } from '../unshare';
14
+ import { rebuildUnsharedChain } from '../chain-rebuild';
13
15
  import { containerScopeState } from './parent-scope';
14
16
  import { applyStructuralChangeToIdsRefs, stampStructuralChange } from '../structural-change';
15
17
  import { renumberOrderedList, templatePastedItemMarkers } from '../list/ordered-markers';
@@ -6,7 +6,8 @@
6
6
  * block's `pendingCursorOffset` may address a block the range delete is about to unmount.
7
7
  */
8
8
  import { parse } from '../../core/parser';
9
- import { cutRangeFromDisplay, isBlockNode, nodeAt } from '../node-ops';
9
+ import { isBlockNode, nodeAt } from '../node-primitives';
10
+ import { cutRangeFromDisplay } from '../node-ops';
10
11
  import { trailingLineEnding, trimTrailingLineEnding } from '../../core/lines';
11
12
  import { getPasteSurface } from '../paste-surfaces';
12
13
  import { isReservedChromeChild } from '../../schema/reserved-chrome';
@@ -1,4 +1,4 @@
1
- import { nodeAt } from '../node-ops';
1
+ import { nodeAt } from '../node-primitives';
2
2
  /** Returns null when no list ancestor exists or the target isn't a direct leaf of a listItem. */
3
3
  export function findEnclosingListForPaste(doc, targetPath) {
4
4
  if (targetPath.length < 3)
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import type { CstNode } from '../../core/nodes';
7
7
  import type { NodeView } from '../../core/node-views';
8
- import type { TrackedPosition } from '../node-ops';
8
+ import type { TrackedPosition } from '../settle';
9
9
  /**
10
10
  * Focus index for the replacement: the last PASTED node. Single-sourced so every structural
11
11
  * route skips the reattached residue identically. Applies only where the residue is a SEPARATE
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { CURSOR_END } from '../../block-component';
9
9
  import { metadataOf } from '../../core/nodes';
10
- import { nodeAt, ensureEditableContainers } from '../node-ops';
10
+ import { nodeAt, ensureEditableContainers } from '../node-primitives';
11
11
  import { cloneNode } from '../clone';
12
12
  import { containerPasteFor } from './container-paste';
13
13
  import { stampStructuralChange } from '../structural-change';
@@ -4,7 +4,7 @@
4
4
  * semantic type. Same-type pastes go through `list-absorb`.
5
5
  */
6
6
  import { CURSOR_END } from '../../block-component';
7
- import { nodeAt, ensureEditableContainers } from '../node-ops';
7
+ import { nodeAt, ensureEditableContainers } from '../node-primitives';
8
8
  import { cloneNode } from '../clone';
9
9
  import { spliceMany } from '../splice-many';
10
10
  import { stampStructuralChange } from '../structural-change';
@@ -4,7 +4,7 @@
4
4
  * scope — a caller holding a nested-bundle blockEdit would misroute through the wrong
5
5
  * container.
6
6
  */
7
- import { nodeAt } from '../node-ops';
7
+ import { nodeAt } from '../node-primitives';
8
8
  import { devWarn } from '../../dev-warn';
9
9
  /** Null when `blockPath`'s parent doesn't resolve to a container. */
10
10
  export function resolveParentScope(doc, blockPath, controller) {
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { snapToScalarBoundary, trailingLineEnding, trimTrailingLineEnding } from '../../core/lines';
7
7
  import { isBlankParagraph } from '../../core/parser';
8
- import { ensureEditableContainers } from '../node-ops';
8
+ import { ensureEditableContainers } from '../node-primitives';
9
9
  import { parseFirstBlock } from '../parse-block';
10
10
  export function buildPastedReplacement(leaf, offset, blocks) {
11
11
  if (blocks.length === 0)
@@ -3,7 +3,7 @@
3
3
  * rather than through the caller's blockEdit — paste-into-cell must mutate `doc.children`
4
4
  * while holding the row-level nested bundle.
5
5
  */
6
- import { nodeAt } from '../node-ops';
6
+ import { nodeAt } from '../node-primitives';
7
7
  import { spliceMany } from '../splice-many';
8
8
  import { trailingLineEnding } from '../../core/lines';
9
9
  import { normalizeReplacementForBody } from './body-write';
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Path-addressed child splices for the range-delete ceremony. Both route through
3
- * `spliceChildrenSettled` (`node-ops.ts`): they address containers at arbitrary depth, which is
3
+ * `spliceChildrenSettled` (`settle.ts`): they address containers at arbitrary depth, which is
4
4
  * exactly where a desynced `childIds` or an unsettled separator becomes permanent. `sharing` is
5
5
  * REQUIRED, not optional: the settle writes the surviving neighbours' own bytes, so a door
6
6
  * reached without it writes through a snapshot-shared node (G1.9). Callers unshare the PARENT
@@ -1,4 +1,5 @@
1
- import { nodeAt, spliceChildrenSettled } from './node-ops';
1
+ import { nodeAt } from './node-primitives';
2
+ import { spliceChildrenSettled } from './settle';
2
3
  export function deleteAtPath(doc, path, sharing) {
3
4
  if (path.length === 0)
4
5
  return;
@@ -6,7 +6,7 @@
6
6
  * than walking past to the document and teleporting the container to a top-level index.
7
7
  */
8
8
  import { tryGetBlockKindDescriptor } from '../schema/block-kind-descriptor';
9
- import { nodeAt } from './node-ops';
9
+ import { nodeAt } from './node-primitives';
10
10
  export function resolveReorderUnit(doc, path) {
11
11
  for (let depth = path.length; depth >= 1; depth--) {
12
12
  const parentPath = path.slice(0, depth - 1);
@@ -1,6 +1,6 @@
1
1
  import type { CstNode } from '../core/nodes';
2
2
  import type { SharingState } from './sharing';
3
- import { type SettledSplice } from './node-ops';
3
+ import { type SettledSplice } from './settle';
4
4
  import type { StructuralChange } from './structural-change';
5
5
  export declare function reorderChildren(children: CstNode[], from: number, to: number): StructuralChange;
6
6
  /**
@@ -10,4 +10,7 @@ export declare function reorderChildren(children: CstNode[], from: number, to: n
10
10
  * already be an owned array. The landing rides the result: a fold settling a seam the move
11
11
  * invalidated can sit above the moved block.
12
12
  */
13
- export declare function reorderChildrenWithTrivia(children: CstNode[], from: number, to: number, sharing: SharingState): SettledSplice;
13
+ export declare function reorderChildrenWithTrivia(children: CstNode[], from: number, to: number, sharing: SharingState,
14
+ /** Top-level blocks only: a blank line is a document separator, not a list's or a quote's,
15
+ * whose own children carry their marker and would read a bare blank line as an end. */
16
+ separators?: boolean): SettledSplice;
@@ -1,5 +1,7 @@
1
+ import { isBlankParagraph, parse } from '../core/parser';
2
+ import { trailingLineEnding } from '../core/lines';
1
3
  import { ensureUnsharedChild } from './unshare';
2
- import { absorbWindowSeams } from './node-ops';
4
+ import { absorbWindowSeams, settleSeparatorOnBlank } from './settle';
3
5
  import { devWarn } from '../dev-warn';
4
6
  // A stale index (a mid-drag delete shrank the array) would splice `undefined` into the
5
7
  // $state tree, so both entry points bail through this BEFORE any unshare or write.
@@ -37,7 +39,10 @@ export function reorderChildren(children, from, to) {
37
39
  * already be an owned array. The landing rides the result: a fold settling a seam the move
38
40
  * invalidated can sit above the moved block.
39
41
  */
40
- export function reorderChildrenWithTrivia(children, from, to, sharing) {
42
+ export function reorderChildrenWithTrivia(children, from, to, sharing,
43
+ /** Top-level blocks only: a blank line is a document separator, not a list's or a quote's,
44
+ * whose own children carry their marker and would read a bare blank line as an end. */
45
+ separators = false) {
41
46
  if (from === to)
42
47
  return { change: { op: 'noop' }, landing: to };
43
48
  if (isReorderOutOfBounds(from, to, children.length)) {
@@ -53,5 +58,53 @@ export function reorderChildrenWithTrivia(children, from, to, sharing) {
53
58
  for (let k = 0; k < windowTrivia.length; k++) {
54
59
  children[lo + k].leadingTrivia = windowTrivia[k];
55
60
  }
61
+ if (separators) {
62
+ // The rotation reseats every slot in the window, and a block can land flush under a
63
+ // paragraph that then reads its lines as its own (a table dissolving into the prose above
64
+ // it). The seam it VACATED is the exception: a pair rejoining once the block between them
65
+ // leaves is the reload's own reading, which the absorber below settles.
66
+ const vacated = from < to ? from : from + 1;
67
+ for (let at = lo; at <= hi + 1; at++) {
68
+ if (at !== vacated)
69
+ separateSeam(children, at, sharing);
70
+ }
71
+ // A blank line reseated by position can hold a line its follower holds too; the run owes
72
+ // exactly one, and none at the document head, where the reload reads each as a block.
73
+ for (let at = lo; at <= hi; at++) {
74
+ if (isBlankParagraph(children[at]))
75
+ settleSeparatorOnBlank({ children }, at, sharing);
76
+ }
77
+ }
56
78
  return absorbWindowSeams({ children }, lo, hi - lo + 1, to, change, sharing);
57
79
  }
80
+ /**
81
+ * Give the follower at `at` a separator where the reload would otherwise not read the two
82
+ * blocks back as themselves, and only then: a block that swallows across a blank line (an
83
+ * unterminated fence taking the prose below it) is the reload's true reading, left to the
84
+ * absorber, which the fold tests pin.
85
+ */
86
+ function separateSeam(children, at, sharing) {
87
+ if (at <= 0 || at >= children.length)
88
+ return;
89
+ const prev = children[at - 1];
90
+ const next = children[at];
91
+ const apart = withLeadingLine(next.leadingTrivia, trailingLineEnding(next.raw));
92
+ if (apart === next.leadingTrivia)
93
+ return;
94
+ if (readsAsBoth(prev, next.leadingTrivia, next) || !readsAsBoth(prev, apart, next))
95
+ return;
96
+ ensureUnsharedChild({ children }, at, sharing).leadingTrivia = apart;
97
+ }
98
+ function withLeadingLine(trivia, eol) {
99
+ return trivia.startsWith('\n') || trivia.startsWith('\r\n') ? trivia : eol + trivia;
100
+ }
101
+ // Two blocks of the same shape, not merely two: a quote lazily taking the first line of the
102
+ // prose below it still reads as two, with the remainder a different kind.
103
+ function readsAsBoth(prev, trivia, next) {
104
+ const blocks = parse(prev.raw + trivia + next.raw, { scope: 'fragment' }).children;
105
+ return (blocks.length === 2 &&
106
+ blocks[0].kind === prev.kind &&
107
+ blocks[0].raw === prev.raw &&
108
+ blocks[1].kind === next.kind &&
109
+ blocks[1].raw === next.raw);
110
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * The separator settle every splice owes its neighbourhood (syntax-tree.md § Blank lines), the
3
+ * seam absorb behind it (editor.md § 8), and the delete primitive built on both. `sharing` owns
4
+ * every in-place write (G1.9); the retire census in `lint/separator-write-doors.test.ts` names
5
+ * every door here.
6
+ */
7
+ import type { CstNode, Document } from '../core/nodes';
8
+ import type { NodeView } from '../core/node-views';
9
+ import type { SharingState } from './sharing';
10
+ import { type StructuralChange } from './structural-change';
11
+ import { type BodyParentArg, type NodeParent, type SeparatorParent } from './node-primitives';
12
+ /**
13
+ * Settle the separator at `index`: nothing needs one at the body head or below a blank block,
14
+ * where the parser would read the extra line as one more empty paragraph.
15
+ */
16
+ export declare function clearRedundantSeparator(parent: SeparatorParent, index: number, sharing?: SharingState): void;
17
+ /**
18
+ * A blank block IS a blank line, so it and its follower share ONE separator (G2.13). The
19
+ * follower's stands, so a later fill of this slot still finds the follower separated.
20
+ */
21
+ export declare function dropDoubledSeparator(parent: SeparatorParent, index: number, sharing?: SharingState): void;
22
+ /**
23
+ * The separator a block takes back when it stops being blank: its own blank line was what stood
24
+ * between it and a non-blank predecessor. Call at the fill.
25
+ */
26
+ export declare function restoreSeparatorOnFill(parent: SeparatorParent, index: number, sharing?: SharingState): void;
27
+ /**
28
+ * The separator the block BELOW a consumed blank line takes back: the same mint minus the
29
+ * blank-self guard, declined where its own follower already holds one (G2.13).
30
+ */
31
+ export declare function restoreSeparatorAfterBlank(parent: SeparatorParent, index: number, sharing?: SharingState): void;
32
+ /**
33
+ * The settle a block turning INTO a blank line owes: the run it joins carries exactly ONE
34
+ * separating line across every block in it AND its follower. The line already standing is
35
+ * kept; a mint lands at the run's head, the only slot one may take.
36
+ */
37
+ export declare function settleSeparatorOnBlank(parent: SeparatorParent, index: number, sharing?: SharingState): void;
38
+ /**
39
+ * The give-back twin of {@link settleSeparatorOnBlank}'s closer peel: a tail that stops being
40
+ * blank owes the borrowed `innerSuffix` line back, or the wrap emits a line nobody typed.
41
+ */
42
+ export declare function releaseWrapPeel(parent: SeparatorParent, index: number): void;
43
+ /**
44
+ * The commit ceremony's settle door: derive the spliced window from the change and settle it
45
+ * against `before`, the pre-mutate children. Nodes surviving inside the window are not removals,
46
+ * so a coarse descriptor over an in-place write settles as one.
47
+ */
48
+ export declare function settleSeparator(parent: SeparatorParent, before: readonly CstNode[], change: StructuralChange, sharing?: SharingState, tracked?: TrackedPosition): StructuralChange;
49
+ /**
50
+ * The out-of-commit-scope twin of {@link settleSeparator}, for a container discovered by walking
51
+ * the live tree: it splices through the `childIds` door and reads the pre-splice span itself.
52
+ */
53
+ export declare function spliceChildrenSettled(parent: CstNode | Document, at: number, removeCount: number, replacement: CstNode[], sharing?: SharingState): void;
54
+ /**
55
+ * What a seam settle absorbed: post-splice window position and size, and the net blocks eaten.
56
+ * `span + eaten` is the pre-absorb slot count, which is what a change descriptor reports.
57
+ */
58
+ interface SeamAbsorption {
59
+ at: number;
60
+ span: number;
61
+ eaten: number;
62
+ spliced: boolean;
63
+ }
64
+ /** A byte position the folds carry with them, written in place since each fold re-tiles the bytes. */
65
+ export interface TrackedPosition {
66
+ index: number;
67
+ offset: number;
68
+ }
69
+ /**
70
+ * A splice can leave neighbours whose adjacent bytes re-read as fewer blocks on reload. Absorb
71
+ * while the window's own bytes parse to fewer blocks, which is the reload's reading; a blank run
72
+ * is transparent to a container's continuation, so the window anchors at the nearest non-blank
73
+ * block above the seam, never below `floor`, and cascades.
74
+ */
75
+ export declare function absorbSeamReading(parent: NodeParent, seamLeft: number, floor: number, sharing?: SharingState, tracked?: TrackedPosition, headProbe?: number, onBeforeSplice?: () => void): SeamAbsorption;
76
+ /**
77
+ * Map a post-edit caret offset (in the committed text) to the parsed block it falls in, as a
78
+ * local display offset. An offset inside inter-block trivia lands at the next block's start;
79
+ * past-the-end clamps to the last.
80
+ */
81
+ export declare function focusTargetInReplacement(nodes: readonly NodeView[], offset: number): {
82
+ index: number;
83
+ offset: number;
84
+ };
85
+ /** What a splice settled: its change widened by every fold, and where a tracked index landed. */
86
+ export interface SettledSplice {
87
+ change: StructuralChange;
88
+ landing: number;
89
+ }
90
+ /**
91
+ * The seam question at every join the splice at `at` disturbed, its window's two edges and the
92
+ * joins inside it, since a move can invalidate a join that was already correct. Each fold
93
+ * cascades downward. `headProbe` names the one block whose bytes changed, letting each ask
94
+ * decline on its first line alone; dropped once anything folds, since its index has moved.
95
+ */
96
+ export declare function absorbWindowSeams(parent: NodeParent, at: number, added: number, landing: number, change: StructuralChange, sharing?: SharingState, tracked?: TrackedPosition, headProbe?: number, onBeforeSplice?: () => void): SettledSplice;
97
+ /** The materialized tail reported inside the sink's one contiguous window. */
98
+ export declare function widenForTailMint(change: StructuralChange, before: number, after: number): StructuralChange;
99
+ /**
100
+ * Remove the node at `blockIndex`, leaving the next sibling separated from its new predecessor
101
+ * and no more. Takes {@link BodyParentArg} because the settle can hand a freed line to the
102
+ * owner's wrap slots; the successor's trivia is the op's only in-place write.
103
+ */
104
+ export declare function deleteNode(parent: BodyParentArg, blockIndex: number, sharing?: SharingState): StructuralChange;
105
+ export {};