@miliastry/quasar 1.0.6 → 1.1.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 (273) hide show
  1. package/dist/Visuals/lyne.css +117 -6
  2. package/dist/Visuals/osu.css +36 -5
  3. package/dist/index.js +9664 -3282
  4. package/dist/index.mjs +9535 -3283
  5. package/dist/types/Analysis/Contracts/AnalysisReport.d.ts +22 -0
  6. package/dist/types/Analysis/Contracts/Contribution.d.ts +68 -0
  7. package/dist/types/Analysis/Contracts/Pass.d.ts +59 -0
  8. package/dist/types/Analysis/Contracts/PipelineContext.d.ts +34 -0
  9. package/dist/types/Analysis/Passes/Analysis/ColorUsageAnalyzer.d.ts +56 -0
  10. package/dist/types/Analysis/Passes/Analysis/GradientAnalyzer.d.ts +111 -0
  11. package/dist/types/Analysis/Passes/Analysis/MergeableColorAnalyzer.d.ts +26 -0
  12. package/dist/types/Analysis/Passes/Analysis/RainbowAnalyzer.d.ts +47 -0
  13. package/dist/types/Analysis/Passes/Analysis/SymbolAnalyzer.d.ts +89 -0
  14. package/dist/types/Analysis/Passes/Analysis/WaveAnalyzer.d.ts +44 -0
  15. package/dist/types/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.d.ts +4 -0
  16. package/dist/types/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.d.ts +4 -0
  17. package/dist/types/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.d.ts +4 -0
  18. package/dist/types/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.d.ts +4 -0
  19. package/dist/types/Analysis/Passes/Decision/DefaultDecision.d.ts +30 -0
  20. package/dist/types/Analysis/Passes/Decision/PaletteRemapDecision.d.ts +169 -0
  21. package/dist/types/Analysis/Passes/Decision/__tests__/DefaultDecision.test.d.ts +4 -0
  22. package/dist/types/Analysis/Passes/Transform/CollapseGradientTransform.d.ts +37 -0
  23. package/dist/types/Analysis/Passes/Transform/MergeColorsTransform.d.ts +28 -0
  24. package/dist/types/Analysis/Passes/Transform/RainbowCollapseTransform.d.ts +19 -0
  25. package/dist/types/Analysis/Passes/Transform/WaveCollapseTransform.d.ts +20 -0
  26. package/dist/types/Analysis/Passes/Utility/CharacterCountAnalyzer.d.ts +19 -0
  27. package/dist/types/Analysis/Pipeline/Pipeline.d.ts +69 -0
  28. package/dist/types/Analysis/Pipeline/PipelineBuilder.d.ts +31 -0
  29. package/dist/types/Analysis/Pipeline/PipelineStage.d.ts +17 -0
  30. package/dist/types/Analysis/Utils/color-utils.d.ts +60 -0
  31. package/dist/types/Analysis/__tests__/Integration.test.d.ts +8 -0
  32. package/dist/types/Analysis/__tests__/PaletteRemap.test.d.ts +14 -0
  33. package/dist/types/Analysis/__tests__/Pipeline.test.d.ts +10 -0
  34. package/dist/types/Analysis/__tests__/RoundTrip.test.d.ts +30 -0
  35. package/dist/types/Analysis/__tests__/SymbolAnalyzer.test.d.ts +11 -0
  36. package/dist/types/Analysis/index.d.ts +40 -0
  37. package/dist/types/BBCode/BBCodeDocumentModel.d.ts +92 -0
  38. package/dist/types/BBCode/BBCodeToGreenNode.d.ts +111 -0
  39. package/dist/types/BBCode/Parser.d.ts +78 -0
  40. package/dist/types/BBCode/index.d.ts +4 -0
  41. package/dist/types/Collab/positions.d.ts +55 -0
  42. package/dist/types/Commands/Command.d.ts +37 -0
  43. package/dist/types/Commands/CommandRegistry.d.ts +48 -0
  44. package/dist/types/Commands/DeleteNode.d.ts +7 -0
  45. package/dist/types/Commands/InsertText.d.ts +8 -0
  46. package/dist/types/Commands/SplitMerge.d.ts +9 -0
  47. package/dist/types/Commands/WrapInTag.d.ts +8 -0
  48. package/dist/types/Commands/index.d.ts +6 -0
  49. package/dist/types/Diff/TreeDiffer.d.ts +52 -0
  50. package/dist/types/Diff/__tests__/TreeDiffer.test.d.ts +1 -0
  51. package/dist/types/Diff/index.d.ts +2 -0
  52. package/dist/types/Edits/EditPlan.d.ts +144 -0
  53. package/dist/types/Edits/Optimizer.d.ts +78 -0
  54. package/dist/types/Edits/Rules/Rule.d.ts +91 -0
  55. package/dist/types/Edits/Rules/dropEmptyTags.d.ts +40 -0
  56. package/dist/types/Edits/Rules/dropRedundantNesting.d.ts +59 -0
  57. package/dist/types/Edits/Rules/index.d.ts +9 -0
  58. package/dist/types/Edits/Rules/mergeAdjacent.d.ts +93 -0
  59. package/dist/types/Edits/Rules/reorderWrappers.d.ts +42 -0
  60. package/dist/types/Edits/Rules/shortenHex.d.ts +30 -0
  61. package/dist/types/Edits/Rules/tagValue.d.ts +56 -0
  62. package/dist/types/Edits/Rules/unwrapInvisibleColor.d.ts +47 -0
  63. package/dist/types/Edits/applyEdits.d.ts +36 -0
  64. package/dist/types/Edits/index.d.ts +6 -0
  65. package/dist/types/Events/EventBus.d.ts +103 -0
  66. package/dist/types/Events/index.d.ts +2 -0
  67. package/dist/types/Formatter/Formatter.d.ts +32 -0
  68. package/dist/types/Formatter/index.d.ts +2 -0
  69. package/dist/types/HTML/HTMLDocumentModel.d.ts +13 -0
  70. package/dist/types/HTML/HTMLToGreenNode.d.ts +4 -0
  71. package/dist/types/Incremental/BracketIndex.d.ts +101 -0
  72. package/dist/types/Incremental/ChangeTracker.d.ts +69 -0
  73. package/dist/types/Incremental/IncrementalParser.d.ts +246 -0
  74. package/dist/types/Incremental/__tests__/BracketIndex.test.d.ts +1 -0
  75. package/dist/types/Incremental/__tests__/IncrementalParser.test.d.ts +1 -0
  76. package/dist/types/Incremental/index.d.ts +4 -0
  77. package/dist/types/Lexer/BBCodeLexer.d.ts +74 -0
  78. package/dist/types/Lexer/Lexer.d.ts +54 -0
  79. package/dist/types/Lexer/index.d.ts +4 -0
  80. package/dist/types/Linter/Linter.d.ts +58 -0
  81. package/dist/types/Linter/index.d.ts +2 -0
  82. package/dist/types/Markdown/MarkdownAST.d.ts +122 -0
  83. package/dist/types/Markdown/MarkdownDocumentModel.d.ts +15 -0
  84. package/dist/types/Markdown/MarkdownLexer.d.ts +155 -0
  85. package/dist/types/Markdown/MarkdownParser.d.ts +42 -0
  86. package/dist/types/Markdown/MarkdownToGreenNode.d.ts +6 -0
  87. package/dist/types/MilHibri/MilHibriDocumentModel.d.ts +21 -0
  88. package/dist/types/Model/DocumentModel.d.ts +318 -0
  89. package/dist/types/Model/NodeFactory.d.ts +29 -0
  90. package/dist/types/Model/TagRegistry.d.ts +165 -0
  91. package/dist/types/Model/index.d.ts +5 -0
  92. package/dist/types/Plugins/PluginAPI.d.ts +44 -0
  93. package/dist/types/Plugins/PluginRegistry.d.ts +81 -0
  94. package/dist/types/Plugins/index.d.ts +3 -0
  95. package/dist/types/Queries/QueryEngine.d.ts +32 -0
  96. package/dist/types/Queries/index.d.ts +1 -0
  97. package/dist/types/Reconciler/SurgicalReconciler.d.ts +23 -0
  98. package/dist/types/RenderPipeline/RenderPipeline.d.ts +46 -0
  99. package/dist/types/RenderPipeline/RenderTree.d.ts +55 -0
  100. package/dist/types/RenderPipeline/index.d.ts +4 -0
  101. package/dist/types/Repair/NestingRepair.d.ts +48 -0
  102. package/dist/types/Repair/index.d.ts +1 -0
  103. package/dist/types/Semantic/SemanticAnalyzer.d.ts +321 -0
  104. package/dist/types/Semantic/index.d.ts +2 -0
  105. package/dist/types/Symbols/SymbolTable.d.ts +47 -0
  106. package/dist/types/Symbols/index.d.ts +1 -0
  107. package/dist/types/Syntax/GreenNode.d.ts +154 -0
  108. package/dist/types/Syntax/GreenNodePool.d.ts +134 -0
  109. package/dist/types/Syntax/NodeMatcher.d.ts +75 -0
  110. package/dist/types/Syntax/RedNode.d.ts +280 -0
  111. package/dist/types/Syntax/RedNodeStore.d.ts +116 -0
  112. package/dist/types/Syntax/TreeBuilder.d.ts +54 -0
  113. package/dist/types/Syntax/__tests__/GreenNode.test.d.ts +1 -0
  114. package/dist/types/Syntax/__tests__/RedNode.test.d.ts +1 -0
  115. package/dist/types/Syntax/__tests__/RedNodeStore.test.d.ts +1 -0
  116. package/dist/types/Syntax/greenEdit.d.ts +64 -0
  117. package/dist/types/Syntax/hash.d.ts +17 -0
  118. package/dist/types/Syntax/index.d.ts +8 -0
  119. package/dist/types/Syntax/nodeAttr.d.ts +63 -0
  120. package/dist/types/Syntax/partition.d.ts +67 -0
  121. package/dist/types/Syntax/preserveNodeIds.d.ts +47 -0
  122. package/dist/types/Tokens/TokenResolver.d.ts +25 -0
  123. package/dist/types/Tokens/index.d.ts +1 -0
  124. package/dist/types/Transactions/Transaction.d.ts +36 -0
  125. package/dist/types/Transactions/UndoManager.d.ts +63 -0
  126. package/dist/types/Transactions/index.d.ts +3 -0
  127. package/dist/types/Transformers/ASTOptimizer.d.ts +34 -0
  128. package/dist/types/Transformers/GradientTransformer.d.ts +18 -0
  129. package/dist/types/Transformers/GrowTransformer.d.ts +16 -0
  130. package/dist/types/Transformers/RainbowTransformer.d.ts +17 -0
  131. package/dist/types/Transformers/SineWaveTransformer.d.ts +17 -0
  132. package/dist/types/Transformers/Transformer.d.ts +17 -0
  133. package/dist/types/Types/core.d.ts +104 -0
  134. package/dist/types/Types/diagnostics.d.ts +85 -0
  135. package/dist/types/Types/index.d.ts +11 -0
  136. package/dist/types/Types/operations.d.ts +109 -0
  137. package/dist/types/Types/queries.d.ts +67 -0
  138. package/dist/types/Types/symbols.d.ts +42 -0
  139. package/dist/types/Types/tokens.d.ts +75 -0
  140. package/dist/types/Utils/BBCodeGenerator.d.ts +21 -0
  141. package/dist/types/Utils/ColorMath.d.ts +92 -0
  142. package/dist/types/Utils/EffectMath.d.ts +643 -0
  143. package/dist/types/Utils/TagVocabulary.d.ts +104 -0
  144. package/dist/types/Utils/TagVocabulary.test.d.ts +1 -0
  145. package/dist/types/Utils/color.d.ts +29 -0
  146. package/dist/types/Utils/dom-to-svg.d.ts +28 -0
  147. package/dist/types/Utils/dom-to-svg.test.d.ts +1 -0
  148. package/dist/types/Utils/treeTransformers.d.ts +160 -0
  149. package/dist/types/Visitors/BBBlocksExporter.d.ts +27 -0
  150. package/dist/types/Visitors/BBCodeExporter.d.ts +90 -0
  151. package/dist/types/Visitors/BlockPatcher.d.ts +91 -0
  152. package/dist/types/Visitors/DOMMorpher.d.ts +13 -0
  153. package/dist/types/Visitors/HTMLRenderer.d.ts +380 -0
  154. package/dist/types/Visitors/JSONExporter.d.ts +42 -0
  155. package/dist/types/Visitors/MarkdownExporter.d.ts +28 -0
  156. package/dist/types/Visitors/SVGRenderer.d.ts +16 -0
  157. package/dist/types/Visitors/TiptapExporter.d.ts +40 -0
  158. package/dist/types/Visitors/Visitor.d.ts +35 -0
  159. package/dist/types/Visitors/index.d.ts +9 -0
  160. package/dist/types/Visuals/BoxDrawer.d.ts +49 -0
  161. package/dist/types/Visuals/index.d.ts +38 -0
  162. package/dist/types/index.d.ts +154 -0
  163. package/package.json +14 -10
  164. package/src/Analysis/Passes/Analysis/ColorUsageAnalyzer.ts +103 -0
  165. package/src/Analysis/Passes/Analysis/GradientAnalyzer.ts +294 -20
  166. package/src/Analysis/Passes/Analysis/SymbolAnalyzer.ts +257 -0
  167. package/src/Analysis/Passes/Decision/PaletteRemapDecision.ts +425 -0
  168. package/src/Analysis/Pipeline/Pipeline.ts +15 -2
  169. package/src/Analysis/index.ts +8 -2
  170. package/src/BBCode/BBCodeDocumentModel.ts +3 -0
  171. package/src/BBCode/BBCodeToGreenNode.ts +18 -12
  172. package/src/BBCode/Parser.ts +38 -1
  173. package/src/Edits/EditPlan.ts +290 -0
  174. package/src/Edits/Optimizer.ts +185 -0
  175. package/src/Edits/Rules/Rule.ts +145 -0
  176. package/src/Edits/Rules/dropEmptyTags.ts +110 -0
  177. package/src/Edits/Rules/dropRedundantNesting.ts +120 -0
  178. package/src/Edits/Rules/index.ts +28 -0
  179. package/src/Edits/Rules/mergeAdjacent.ts +224 -0
  180. package/src/Edits/Rules/reorderWrappers.ts +154 -0
  181. package/src/Edits/Rules/shortenHex.ts +79 -0
  182. package/src/Edits/Rules/tagValue.ts +105 -0
  183. package/src/Edits/Rules/unwrapInvisibleColor.ts +93 -0
  184. package/src/Edits/applyEdits.ts +54 -0
  185. package/src/Edits/index.ts +22 -0
  186. package/src/Events/EventBus.ts +18 -0
  187. package/src/HTML/HTMLToGreenNode.ts +185 -37
  188. package/src/Incremental/BracketIndex.ts +249 -0
  189. package/src/Incremental/IncrementalParser.ts +464 -41
  190. package/src/Lexer/BBCodeLexer.ts +25 -3
  191. package/src/Lexer/index.ts +1 -1
  192. package/src/Markdown/MarkdownAST.ts +65 -2
  193. package/src/Markdown/MarkdownLexer.ts +69 -14
  194. package/src/Markdown/MarkdownParser.ts +749 -62
  195. package/src/Markdown/MarkdownToGreenNode.ts +148 -27
  196. package/src/MilHibri/MilHibriDocumentModel.ts +56 -0
  197. package/src/Model/DocumentModel.ts +138 -5
  198. package/src/Model/TagRegistry.ts +129 -134
  199. package/src/Reconciler/SurgicalReconciler.ts +460 -0
  200. package/src/Repair/NestingRepair.ts +116 -0
  201. package/src/Repair/index.ts +1 -0
  202. package/src/Semantic/SemanticAnalyzer.ts +1802 -65
  203. package/src/Syntax/GreenNodePool.ts +12 -12
  204. package/src/Syntax/RedNode.ts +17 -3
  205. package/src/Syntax/RedNodeStore.ts +9 -1
  206. package/src/Syntax/nodeAttr.ts +172 -0
  207. package/src/Tokens/TokenResolver.ts +55 -0
  208. package/src/Tokens/index.ts +1 -0
  209. package/src/Types/core.ts +15 -0
  210. package/src/Utils/ColorMath.ts +276 -55
  211. package/src/Utils/EffectMath.ts +2462 -0
  212. package/src/Utils/TagVocabulary.ts +141 -0
  213. package/src/Utils/dom-to-svg.ts +28 -12
  214. package/src/Visitors/BBBlocksExporter.ts +15 -1
  215. package/src/Visitors/BBCodeExporter.ts +319 -32
  216. package/src/Visitors/BlockPatcher.ts +87 -11
  217. package/src/Visitors/DOMMorpher.ts +15 -5
  218. package/src/Visitors/HTMLRenderer.ts +374 -151
  219. package/src/Visitors/MarkdownExporter.ts +98 -41
  220. package/src/Visitors/TiptapExporter.ts +1 -0
  221. package/src/Visuals/index.ts +7 -4
  222. package/src/Visuals/lyne.css +117 -6
  223. package/src/Visuals/osu.css +36 -5
  224. package/src/index.ts +172 -5
  225. package/dist/index.d.mts +0 -4612
  226. package/dist/index.d.ts +0 -4612
  227. package/src/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.ts +0 -135
  228. package/src/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.ts +0 -84
  229. package/src/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.ts +0 -99
  230. package/src/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.ts +0 -119
  231. package/src/Analysis/Passes/Decision/__tests__/DefaultDecision.test.ts +0 -179
  232. package/src/Analysis/__tests__/Integration.test.ts +0 -162
  233. package/src/Analysis/__tests__/Pipeline.test.ts +0 -133
  234. package/src/Diff/__tests__/TreeDiffer.test.ts +0 -65
  235. package/src/Incremental/__tests__/IncrementalParser.test.ts +0 -164
  236. package/src/Syntax/__tests__/GreenNode.test.ts +0 -33
  237. package/src/Syntax/__tests__/RedNode.test.ts +0 -81
  238. package/src/Syntax/__tests__/RedNodeStore.test.ts +0 -104
  239. package/src/Tests/ASTOptimizerIdempotence.test.ts +0 -77
  240. package/src/Tests/BlockPatcher.test.ts +0 -437
  241. package/src/Tests/BlockPatcherWindowed.test.ts +0 -364
  242. package/src/Tests/BoxDrawer.test.ts +0 -217
  243. package/src/Tests/BoxRichTitle.test.ts +0 -105
  244. package/src/Tests/Chars500kBenchmark.test.ts +0 -151
  245. package/src/Tests/Chars500kEdits.test.ts +0 -321
  246. package/src/Tests/CollabPositions.test.ts +0 -146
  247. package/src/Tests/CompilerPathProfiling.test.ts +0 -186
  248. package/src/Tests/DOMMorpher.test.ts +0 -142
  249. package/src/Tests/DomPatchPerf.test.ts +0 -60
  250. package/src/Tests/EffectSegments.snapshot.json +0 -616
  251. package/src/Tests/EffectSegments.test.ts +0 -68
  252. package/src/Tests/FindNodeAtOffset.test.ts +0 -65
  253. package/src/Tests/Fuzzer.test.ts +0 -166
  254. package/src/Tests/GreenNodePool.test.ts +0 -153
  255. package/src/Tests/Lexer.test.ts +0 -238
  256. package/src/Tests/LyneMode.test.ts +0 -291
  257. package/src/Tests/ModelCoherence.test.ts +0 -180
  258. package/src/Tests/Partition.test.ts +0 -238
  259. package/src/Tests/PluginTags.test.ts +0 -150
  260. package/src/Tests/ProblematicSection.test.ts +0 -46
  261. package/src/Tests/ProblematicSectionHTML.test.ts +0 -58
  262. package/src/Tests/RedReuse.test.ts +0 -134
  263. package/src/Tests/ReproDelete20k.test.ts +0 -62
  264. package/src/Tests/SemanticValidators.test.ts +0 -136
  265. package/src/Tests/StableNodeIds.test.ts +0 -210
  266. package/src/Tests/StudioColorBloat.test.ts +0 -25
  267. package/src/Tests/StudioDebugText.test.ts +0 -27
  268. package/src/Tests/StudioTrailingChar.test.ts +0 -25
  269. package/src/Tests/StudioValidText.test.ts +0 -25
  270. package/src/Tests/UrlImgBug.test.ts +0 -23
  271. package/src/Tests/VisualBuilderFidelity.test.ts +0 -105
  272. package/src/Tests/referenceDocument.ts +0 -119
  273. package/src/Utils/dom-to-svg.test.ts +0 -86
@@ -0,0 +1,154 @@
1
+ /**
2
+ * DocumentEngine — GreenNode (Immutable Syntax Tree)
3
+ *
4
+ * The Green Tree is IMMUTABLE. It never changes after creation.
5
+ * This is the "compiled" representation of the source text.
6
+ *
7
+ * Properties:
8
+ * - Fully immutable (readonly)
9
+ * - Contains only structural information (kind, text, children, widths)
10
+ * - **No absolute position** — see below
11
+ * - No parent references
12
+ * - No identity (IDs live in Red Tree)
13
+ * - Can be shared across document versions
14
+ * - Safe to cache
15
+ *
16
+ * ─── Why there is no `range` ────────────────────────────────────────────────
17
+ *
18
+ * A green node knows how WIDE it is, not WHERE it is. That is the whole point:
19
+ * the two `[color=#e8b04b]` runs in a gradient are the same structure, and if
20
+ * each carried its own offsets they could never be the same object. Measured on
21
+ * the reference document: 1736 nodes, but only 702 distinct structures — 59.6%
22
+ * of the tree is duplicate.
23
+ *
24
+ * Position is a property of a node's PLACE in a tree, so it lives on the red
25
+ * node, which is the thing that has a place. `RedNode.range` computes it once
26
+ * at construction by accumulating widths from the root and caches it, so
27
+ * consumers see exactly the same `{start, end}` they always did.
28
+ *
29
+ * This also makes incremental editing cheaper: text inserted before a subtree
30
+ * changes where it is, not what it is, so nothing about it needs rebuilding.
31
+ * `shiftGreen` — which used to deep-copy every following sibling to bump its
32
+ * offsets — no longer exists, because there is nothing left to shift.
33
+ *
34
+ * ─── Why `width` is derived and never passed ────────────────────────────────
35
+ *
36
+ * `width = leadingWidth + (children ? Σ child widths : ownWidth) + trailingWidth`
37
+ *
38
+ * The partition invariant of roadmap point 14 — every character owned exactly
39
+ * once — used to be a runtime check that a careless caller could violate. Now
40
+ * it is arithmetic the constructor performs, so a tree that does not partition
41
+ * its source cannot be built. `Syntax/partition.ts` still exists to verify the
42
+ * one thing this cannot: that the root's width matches the source length.
43
+ *
44
+ * Inspired by Roslyn's Green Tree and SwiftSyntax.
45
+ */
46
+ export declare class GreenNode {
47
+ /** Syntax kind — maps to TokenKind for leaves, NodeKind for internals */
48
+ readonly kind: string;
49
+ /** Raw text of this node (empty for internal nodes) */
50
+ readonly text: string;
51
+ /** Child nodes */
52
+ readonly children: readonly GreenNode[];
53
+ /** Total width of this node in characters, delimiters included */
54
+ readonly width: number;
55
+ /** Whether this is a leaf (token) or internal node */
56
+ readonly isLeaf: boolean;
57
+ /**
58
+ * Characters this node owns BEFORE its first child — its opening delimiter.
59
+ * For `[b]hola[/b]` that is the 3 characters of `[b]`.
60
+ *
61
+ * `declare` is load-bearing here for the same reason as `_hashCache` below:
62
+ * a plain field declaration would emit a `defineProperty` per construction.
63
+ */
64
+ readonly leadingWidth: number;
65
+ /** Characters this node owns AFTER its last child — its closing delimiter. */
66
+ readonly trailingWidth: number;
67
+ /**
68
+ * Memoized structural hash — computed on first access, never invalidated.
69
+ *
70
+ * `declare` is load-bearing: with `target: ES2022`, TypeScript defaults
71
+ * `useDefineForClassFields` to true, so a plain field declaration emits a
72
+ * per-construction `defineProperty`. Measured on a 19.6 KB document that
73
+ * cost 23% of the parse phase (0.583 ms → 0.718 ms) for a slot most nodes
74
+ * never read. `declare` emits nothing; the constructor assigns it directly,
75
+ * which keeps the object shape monomorphic without the field-init overhead.
76
+ */
77
+ private _hashCache;
78
+ /**
79
+ * @param ownWidth Width of a childless node's own content: the length of a
80
+ * `text` token, or the 1-2 characters of a newline. Ignored
81
+ * when there are children, whose widths sum to the same
82
+ * thing. An empty element like `[b][/b]` passes 0 and gets
83
+ * its width from its two delimiters.
84
+ */
85
+ constructor(kind: string, text: string, children?: GreenNode[], leadingWidth?: number, trailingWidth?: number, ownWidth?: number);
86
+ /**
87
+ * Structural hash of this subtree: `(kind, text, widths, children hashes)`.
88
+ *
89
+ * Two subtrees that render identically at different offsets ARE structurally
90
+ * identical — that is the entire premise of structural sharing, and now that
91
+ * green nodes carry no position it is simply true rather than something the
92
+ * hash had to be careful to arrange.
93
+ *
94
+ * The widths ARE folded in: they are structure, not position. A `text` leaf
95
+ * of width 4 and one of width 5 are different nodes.
96
+ *
97
+ * Computed lazily: parsing paths that never intern pay nothing. Safe to
98
+ * memoize because a GreenNode is immutable once constructed.
99
+ */
100
+ get _hash(): number;
101
+ /** Get child at index */
102
+ childAt(index: number): GreenNode | undefined;
103
+ /** Find first child matching predicate */
104
+ findChild(predicate: (n: GreenNode) => boolean): GreenNode | undefined;
105
+ /** Count of direct children */
106
+ get childCount(): number;
107
+ /** Check if this node has a specific kind */
108
+ isKind(kind: string): boolean;
109
+ /**
110
+ * Offset of this node's first child, relative to this node's own start.
111
+ * Equal to `leadingWidth`; named for the places that read it as a position.
112
+ */
113
+ get innerOffset(): number;
114
+ /** Width of the span between this node's delimiters. */
115
+ get innerWidth(): number;
116
+ /** Walk all descendants in pre-order */
117
+ walk(visitor: (node: GreenNode, depth: number) => void | 'skip', depth?: number): void;
118
+ /**
119
+ * Walk all descendants in pre-order, with each node's absolute offset.
120
+ *
121
+ * The replacement for the walks that used to read `node.range`. Offsets are
122
+ * accumulated on the way down, which is the only place they exist.
123
+ */
124
+ walkWithOffset(visitor: (node: GreenNode, start: number, depth: number) => void | 'skip', start?: number, depth?: number): void;
125
+ /** Convert to a debug string */
126
+ toString(depth?: number, start?: number): string;
127
+ /** Get the source text for this node, given where it starts. */
128
+ getSourceText(source: string, start: number): string;
129
+ }
130
+ /**
131
+ * Build an internal node. Its width comes from its children plus its own
132
+ * delimiters — there is no way to declare a width that disagrees with them.
133
+ */
134
+ export declare function greenNode(kind: string, text: string, children?: GreenNode[], leadingWidth?: number, trailingWidth?: number, ownWidth?: number): GreenNode;
135
+ /**
136
+ * Absolute offsets of `node`'s children, given where `node` itself starts.
137
+ *
138
+ * Returns `children.length + 1` entries: entry `i` is where child `i` begins
139
+ * and entry `i + 1` is where it ends, so a run of children `[a, b)` spans
140
+ * `offsets[a] .. offsets[b]`.
141
+ *
142
+ * For code that walks the green tree and needs source positions — analyzers
143
+ * reporting diagnostics, transforms recording what they collapsed. Green nodes
144
+ * have no positions of their own, so a walk that needs them accumulates them,
145
+ * and this is the one-liner for doing that at a single level.
146
+ */
147
+ export declare function childOffsets(node: GreenNode, start: number): number[];
148
+ /**
149
+ * Build a token. `width` defaults to the text's own length, which is right for
150
+ * every leaf whose text IS its source (`text` nodes). Leaves whose `text` holds
151
+ * something else — `spacing` and `empty_line` carry `''` but occupy 1-2
152
+ * characters, element nodes carry their attributes — must pass it.
153
+ */
154
+ export declare function greenLeaf(kind: string, text: string, width?: number): GreenNode;
@@ -0,0 +1,134 @@
1
+ /**
2
+ * DocumentEngine — GreenNodePool (Structural Sharing via Interning)
3
+ *
4
+ * Roslyn-grade GreenNode Deduplication Pool.
5
+ *
6
+ * Ensures that structurally identical subtrees share the EXACT SAME
7
+ * GreenNode memory address in RAM. This is structural sharing:
8
+ * when the same BBCode block is pasted 10 times, the parser creates
9
+ * GreenNodes for it ONCE, and all 10 copies reference the same objects.
10
+ *
11
+ * Architecture:
12
+ * - Flyweight Pattern: GreenNodes are pure structural data without parent pointers.
13
+ * - Hash-based lookup: O(1) amortized via _hash property on GreenNode.
14
+ * - Reference-equality verification: identical children = same reference (because
15
+ * children are interned first, bottom-up construction guarantees this).
16
+ * - Per-document scope: interners are created per BBCodeDocumentModel, cleared on dispose.
17
+ *
18
+ * Key insight: because GreenNodes are immutable and children are interned bottom-up,
19
+ * two nodes with the same (kind, text, widths, children-references) are structurally
20
+ * identical. We use _hash for fast lookup, then verify with reference equality on
21
+ * children.
22
+ *
23
+ * ─── Why this used to be broken (roadmap S2) ────────────────────────────────
24
+ *
25
+ * Green nodes used to carry an absolute `range`, and the two halves of the pool
26
+ * disagreed about what to do with it:
27
+ *
28
+ * - `internLeaf` keyed on `kind:text` alone, so the second `"Hello"` in a
29
+ * document got back the node built for the FIRST one — carrying the first
30
+ * one's offsets. Enabling the interner made every position downstream lie
31
+ * silently: incremental reparse, `findNodeAtOffset`, diagnostics, the
32
+ * Monaco↔AST mapping.
33
+ * - `internNode` did the opposite and required identical start AND end before
34
+ * deduplicating, which no two distinct occurrences can have. The same block
35
+ * pasted ten times gave 0% dedup and 100% of the scanning cost.
36
+ *
37
+ * Neither was fixable while position lived on the green node, because those are
38
+ * the only two options: ignore it and lie, or respect it and never match. The
39
+ * fix was to remove position from the green tree entirely — see `GreenNode.ts`.
40
+ * Now `kind:text:width` genuinely identifies a leaf, and interning is simply
41
+ * correct.
42
+ */
43
+ import { GreenNode } from './GreenNode';
44
+ export interface GreenNodePoolStats {
45
+ size: number;
46
+ hits: number;
47
+ misses: number;
48
+ deduplicatedCount: number;
49
+ /** Number of hash collisions that were verified as true duplicates */
50
+ collisionHits: number;
51
+ /** Number of hash collisions that were false positives */
52
+ collisionMisses: number;
53
+ }
54
+ /**
55
+ * How much of the tree to deduplicate.
56
+ *
57
+ * Measured on the 19.6 KB reference document, against no interning at all:
58
+ *
59
+ * | mode | parse time | green tree memory |
60
+ * |----------|------------|-------------------|
61
+ * | `leaves` | +16% | −42% |
62
+ * | `full` | +110% | −56% |
63
+ *
64
+ * `leaves` is the default because it buys three quarters of the memory for a
65
+ * seventh of the time. Interning an internal node means hashing its whole
66
+ * subtree — `_hash` is lazy, so a parse that never interns never pays for it —
67
+ * and then walking its children to verify a bucket hit. Leaves cost one string
68
+ * key and one Map lookup, and they are where the duplication is: the reference
69
+ * document has 930 leaves and only 122 distinct ones.
70
+ */
71
+ export type InterningMode = 'leaves' | 'full';
72
+ export declare class GreenNodePool {
73
+ private static _instance;
74
+ private readonly _mode;
75
+ private _leafCache;
76
+ private _nodeCache;
77
+ private _hits;
78
+ private _misses;
79
+ private _collisionHits;
80
+ private _collisionMisses;
81
+ /** Singleton instance accessor (for backward compatibility) */
82
+ static get instance(): GreenNodePool;
83
+ constructor(mode?: InterningMode);
84
+ /**
85
+ * Create a new isolated interner (for per-document use).
86
+ * The returned pool shares no cache with the singleton.
87
+ */
88
+ static create(mode?: InterningMode): GreenNodePool;
89
+ /**
90
+ * Intern (deduplicate) a leaf GreenNode.
91
+ * If an identical leaf exists in the pool, returns the cached instance.
92
+ * Leaf key: `kind:text` — deterministic, no collision possible.
93
+ */
94
+ internLeaf(kind: string, text: string, width?: number): GreenNode;
95
+ /**
96
+ * Intern an internal GreenNode (structural sharing).
97
+ *
98
+ * Strategy:
99
+ * 1. Compute hash from (kind, text, children's _hash values) — O(n) where n = children count
100
+ * 2. Look up hash in bucket — O(1) amortized
101
+ * 3. Verify with reference equality on children — O(n) but cache-friendly
102
+ * 4. If verified, return existing (structural sharing achieved)
103
+ * 5. If not, store and return new node
104
+ *
105
+ * Because children are interned bottom-up, identical children are the same reference.
106
+ * This makes reference equality a perfect structural equality check.
107
+ */
108
+ internNode(kind: string, text: string, children: GreenNode[], leadingWidth?: number, trailingWidth?: number): GreenNode;
109
+ /**
110
+ * Compute a bucket key for internal nodes.
111
+ * Uses (kind, text, children-count, first-child-hash) for fast bucketing.
112
+ * This gives O(1) bucket lookup in the common case.
113
+ */
114
+ private computeBucketKey;
115
+ private fnv1a;
116
+ private hashCombine;
117
+ /** Clear the pool cache */
118
+ clear(): void;
119
+ /** Get current pool statistics */
120
+ get stats(): GreenNodePoolStats;
121
+ }
122
+ /**
123
+ * Convenience helper for interning green leaves, over the shared singleton.
124
+ *
125
+ * The internal-node twin of this helper used to sit alongside it and was
126
+ * removed: nothing imported it, and it could not have worked if anything had.
127
+ * `GreenNodePool.instance` is built with the default `'leaves'` mode, and
128
+ * `internNode` returns a fresh `GreenNode` unconditionally in that mode — so
129
+ * the one thing the helper existed for, structural sharing of internal nodes,
130
+ * was unreachable through the only pool it could reach. Interning internal
131
+ * nodes is done by passing a `GreenNodePool.create('full')` to the parser,
132
+ * which is what `BBCodeDocumentModel` does per document.
133
+ */
134
+ export declare function internGreenLeaf(kind: string, text: string, width?: number): GreenNode;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * DocumentEngine — NodeMatcher
3
+ *
4
+ * Matches nodes between old and new syntax trees after edits.
5
+ * Uses stable IDs, fingerprints, and source ranges to determine
6
+ * which nodes were moved, inserted, deleted, or unchanged.
7
+ *
8
+ * Critical for:
9
+ * - Maintaining selection across re-parses
10
+ * - Preserving scroll position
11
+ * - Smooth animations in visual blocks mode
12
+ * - Undo/redo integrity
13
+ */
14
+ import { RedNode } from './RedNode';
15
+ import type { NodeId } from '../Types/core';
16
+ export type MatchStatus = 'exact' | 'moved' | 'updated' | 'inserted' | 'deleted' | 'merged' | 'split';
17
+ export interface MatchResult {
18
+ oldNode: RedNode | null;
19
+ newNode: RedNode | null;
20
+ status: MatchStatus;
21
+ confidence: number;
22
+ }
23
+ export interface NodeMatch {
24
+ /** Map from old node ID to new node */
25
+ oldToNew: Map<NodeId, RedNode>;
26
+ /** Map from new node ID to old node */
27
+ newToOld: Map<NodeId, RedNode>;
28
+ /** All matches */
29
+ matches: MatchResult[];
30
+ /** Statistics */
31
+ stats: {
32
+ exact: number;
33
+ moved: number;
34
+ updated: number;
35
+ inserted: number;
36
+ deleted: number;
37
+ };
38
+ }
39
+ export declare class NodeMatcher {
40
+ /**
41
+ * Match nodes between old and new trees.
42
+ *
43
+ * Strategy (in order):
44
+ * 1. Match by stable ID (if present)
45
+ * 2. Match by fingerprint (content hash)
46
+ * 3. Match by source range overlap
47
+ * 4. Fall back to positional heuristic
48
+ */
49
+ match(oldRoot: RedNode, newRoot: RedNode): NodeMatch;
50
+ /**
51
+ * Phase 0 — descend in lockstep, pairing every subtree that did not change.
52
+ *
53
+ * At each level the common prefix and common suffix are trimmed by comparing
54
+ * `green._hash`, and only the window between them is descended into. That is
55
+ * what makes this robust to an edit that changes the number of children:
56
+ * requiring equal child counts before recursing sounds reasonable but means a
57
+ * single inserted node near the root disables the fast path for the whole
58
+ * tree — measured, it left `match` at 5.11 ms instead of 0.96 ms.
59
+ *
60
+ * Whatever is left in the middle window is handed to the fingerprint
61
+ * heuristic, which is the part that can cope with reordering.
62
+ */
63
+ private pairUnchanged;
64
+ /**
65
+ * Compute a content fingerprint for a node's subtree.
66
+ *
67
+ * Two nodes share a fingerprint iff their subtrees have the same pre-order
68
+ * sequence of (kind, text) pairs — the same equivalence the previous
69
+ * string-concatenating implementation tested, but as a 64-bit structural
70
+ * hash instead of an O(subtree) string built on every comparison.
71
+ */
72
+ fingerprint(node: RedNode): string;
73
+ /** Memoized, bottom-up fingerprint. O(n) across a whole tree. */
74
+ private fingerprintInto;
75
+ }
@@ -0,0 +1,280 @@
1
+ /**
2
+ * DocumentEngine — RedNode (Mutable Syntax Tree)
3
+ *
4
+ * The Red Tree wraps GreenNode and adds mutable state:
5
+ * - Parent references
6
+ * - Stable Node IDs
7
+ * - Version tracking
8
+ * - Diagnostic storage
9
+ *
10
+ * Multiple Red Trees can reference the same Green Tree
11
+ * (different views, different versions of edits).
12
+ *
13
+ * Inspired by Roslyn's Red Tree.
14
+ */
15
+ import { GreenNode } from './GreenNode';
16
+ import type { NodeId, DocumentNode, NodeKind, NodeMetadata } from '../Types/core';
17
+ import type { Diagnostic } from '../Types/diagnostics';
18
+ import type { Range } from '../Types/tokens';
19
+ export declare class RedNode {
20
+ /** The underlying immutable green node */
21
+ readonly green: GreenNode;
22
+ /** Stable identity */
23
+ readonly id: NodeId;
24
+ /** Parent reference (null for root) */
25
+ parent: RedNode | null;
26
+ /** Children as red nodes */
27
+ children: RedNode[];
28
+ /** Version counter for change tracking */
29
+ version: number;
30
+ /** Diagnostics for this node */
31
+ diagnostics: Diagnostic[];
32
+ /** The semantic kind */
33
+ kind: NodeKind;
34
+ /** Additional metadata */
35
+ metadata: NodeMetadata;
36
+ /**
37
+ * Cached position of this node inside `parent.children`.
38
+ *
39
+ * `declare` is load-bearing — see the note on `GreenNode._hashCache`: with
40
+ * `target: ES2022` a plain field declaration emits a `defineProperty` on every
41
+ * construction, and this class is instantiated once per node per parse.
42
+ *
43
+ * The cache is *self-validating*: `index` only trusts it when
44
+ * `parent.children[_idxCache] === this`. That single reference compare is what
45
+ * makes it safe against code that mutates the `children` array directly
46
+ * (`treeTransformers` does `cloned.children = []`) instead of going through the
47
+ * mutation methods — a stale entry is detected and recomputed rather than
48
+ * silently returning a wrong index. `-1` never validates, so it is a safe
49
+ * initial value.
50
+ */
51
+ private _idxCache;
52
+ /**
53
+ * Absolute start offset in the source.
54
+ *
55
+ * This is the red tree's job now: a green node knows its width, not its
56
+ * place, so that identical structures can be the same object (see the header
57
+ * of `GreenNode.ts`). The offset is accumulated on the way down during
58
+ * construction — one addition per node — and stored, so `range` stays the
59
+ * O(1) property every consumer already assumes it is.
60
+ *
61
+ * Held as the `Range` object itself rather than a number plus a getter that
62
+ * builds one: `range` is read from 148 call sites, and allocating there would
63
+ * trade a parse-time win for a read-time cost on every consumer. This is the
64
+ * same shape green nodes used to hold, so nothing downstream changes.
65
+ *
66
+ * `declare` for the usual reason: no `defineProperty` per construction.
67
+ */
68
+ private _range;
69
+ /**
70
+ * Pending offset shift for this subtree, applied on first read.
71
+ *
72
+ * `setStart` defers the walk: it records how far the subtree moved instead of
73
+ * adding the delta to every node's `_range` on the spot. The shift is a single
74
+ * integer here, on the adopted subtree ROOT — the node `setStart` was called
75
+ * on. Any offset read (`range`, `innerStart`, `innerEnd`, `findNodeAtOffset`)
76
+ * materializes the nearest pending ancestor's subtree via {@link materialize}.
77
+ *
78
+ * Why this is sound: a mid-document edit displaces every adopted block after
79
+ * it by the same delta, so adoption alone (thousands of `setStart` calls) was
80
+ * walking thousands of nodes per keystroke — measured as the dominant phase
81
+ * of `buildRed` (3.5-5.8 ms on the 547 KB fixture). The BlockPatcher locates
82
+ * the edited window by reference identity and only ever reads the few changed
83
+ * blocks' ranges, so the displaced-but-unchanged subtrees stay pending
84
+ * indefinitely — the walk never happens.
85
+ *
86
+ * Contract for future readers: app-layer interaction code (hover, links,
87
+ * selection ranges) DOES read `range` on user interaction. The first such
88
+ * read after a mid-document edit fires the deferred walk for the displaced
89
+ * tail — a one-time cost off the keystroke path, which is exactly the trade
90
+ * the laziness makes. Do not "optimize" by reading ranges back into the
91
+ * per-keystroke pipeline; the patcher stays churn-based by design.
92
+ *
93
+ * Composition across reparses: a node re-adopted while its ancestor is still
94
+ * pending must end up at the SUM of both deltas. `setStart` recomputes the
95
+ * delta from the untouched base `_range`, which is exactly the pending
96
+ * ancestor's base too, so composing during `materialize` (add the ancestor's
97
+ * delta into a child's pending delta) stays consistent.
98
+ */
99
+ private _lazyShift;
100
+ constructor(green: GreenNode, options?: {
101
+ id?: NodeId;
102
+ parent?: RedNode | null;
103
+ kind?: NodeKind;
104
+ diagnostics?: Diagnostic[];
105
+ metadata?: NodeMetadata;
106
+ /** Absolute start offset. Defaults to the parent's inner offset. */
107
+ start?: number;
108
+ });
109
+ /**
110
+ * Move this node (and its subtree) to a new absolute offset.
111
+ *
112
+ * Only for the tree-building paths, which create a node before they know
113
+ * where its parent will put it. Callers that mutate a live tree should
114
+ * rebuild instead — a red node's offset must always agree with its place.
115
+ *
116
+ * The move is deferred, not walked: the delta is recorded as a pending
117
+ * shift and applied by any offset read the first time the subtree is
118
+ * actually touched. Adoption calls this once per displaced block (thousands
119
+ * on a mid-document edit), and almost none of those subtrees are read on the
120
+ * same keystroke's hot path — the BlockPatcher locates the edit window by
121
+ * reference identity and only reads the few changed blocks.
122
+ *
123
+ * Re-adoption composes: a node whose subtree was shifted in a previous
124
+ * reparse and never read still carries a pending `_lazyShift`. The new
125
+ * target is absolute, so the delta is measured from the CURRENT effective
126
+ * start (`_range.start + _lazyShift`) and accumulated, not overwritten —
127
+ * otherwise the earlier shift would be applied twice.
128
+ */
129
+ setStart(start: number): void;
130
+ /**
131
+ * Apply `delta` to `node`'s subtree, composing with any nested pending shift.
132
+ *
133
+ * A node with its own pending shift has a base `_range` the parent's delta is
134
+ * relative to as well (both were computed from the same pre-shift tree), so
135
+ * the parent's delta can be folded into the child's pending delta instead of
136
+ * into its `_range` — the child's later materialization applies the sum. This
137
+ * is what makes nested shifts across reparses compose without double counting.
138
+ */
139
+ private static applyShift;
140
+ /**
141
+ * Push `delta` into every red node that lives under `node` — its title nodes
142
+ * as well as its children.
143
+ *
144
+ * The title subtree of a rich `[box=[b]heading[/b]]` is stored in
145
+ * `metadata.titleNodes`, never appended to `children`, so a loop over
146
+ * `children` alone leaves it at pre-edit offsets. That is not cosmetic:
147
+ * `findNodeAtOffset` consults `metadata.titleNodes` BEFORE `children`, so a
148
+ * stale title range mis-resolves caret, hover and selection inside the
149
+ * heading. Both shift paths — the recursive one and `materialize`'s ancestor
150
+ * loop — route through here so they cannot drift apart again.
151
+ */
152
+ private static shiftDescendants;
153
+ /**
154
+ * Materialize every pending shift on the path from here to the root.
155
+ *
156
+ * Walks ancestors top-down (root-most first), applying each pending delta to
157
+ * its subtree. Top-down order matters: an ancestor's delta must land in a
158
+ * child's pending delta BEFORE the child's own materialization runs, or the
159
+ * two shifts would be applied to different bases. After the ancestors are
160
+ * settled, this node itself is materialized if it still carries a shift.
161
+ *
162
+ * The common case — nothing pending anywhere on the path — is one upward
163
+ * pointer walk that allocates nothing: `_range` is read from 148 call sites,
164
+ * and a per-read allocation there would show up in every phase. The chain
165
+ * array is only built after a pending shift is actually found.
166
+ */
167
+ private materialize;
168
+ private static _isMutating;
169
+ /**
170
+ * Run `fn` inside a mutation boundary, so the structural mutators below are
171
+ * allowed to run. Nests safely; the previous state is restored on exit.
172
+ *
173
+ * Not needed for building a fresh subtree — see {@link initChildren}.
174
+ */
175
+ static allowMutation<T>(fn: () => T): T;
176
+ private assertMutating;
177
+ get text(): string;
178
+ /** Absolute span in the source. */
179
+ get range(): Range;
180
+ /** Absolute offset of this node's first child, past its opening delimiter. */
181
+ get innerStart(): number;
182
+ /** Absolute offset where this node's closing delimiter begins. */
183
+ get innerEnd(): number;
184
+ get isLeaf(): boolean;
185
+ get childCount(): number;
186
+ childAt(index: number): RedNode | undefined;
187
+ /** Get the root of the tree */
188
+ get root(): RedNode;
189
+ /** Get the depth from root */
190
+ get depth(): number;
191
+ /** Get previous sibling */
192
+ get previousSibling(): RedNode | null;
193
+ /** Get next sibling */
194
+ get nextSibling(): RedNode | null;
195
+ /**
196
+ * Position of this node within its parent, or -1 for a root.
197
+ *
198
+ * O(1) in the steady state. The cached index is seeded by the mutation methods
199
+ * (so the construction path never searches at all) and verified by a single
200
+ * reference compare on read, falling back to `indexOf` only when it is stale.
201
+ *
202
+ * This getter is hot: `HTMLRenderer.isPrevBlockBoundary` walks siblings
203
+ * backwards, so an O(n) implementation here made rendering quadratic —
204
+ * measured 14.11 ms for 4000 root siblings, growing 2.6x per doubling.
205
+ */
206
+ get index(): number;
207
+ /** Walk all descendants in pre-order */
208
+ walk(visitor: (node: RedNode, depth: number) => void | 'skip', depth?: number): void;
209
+ /** Find a descendant by ID */
210
+ findById(id: NodeId): RedNode | null;
211
+ /**
212
+ * Find the deepest RedNode containing the given text offset.
213
+ *
214
+ * The range is half-open — a node owns `[start, end)` — y eso no es un
215
+ * detalle: con el final inclusivo, un offset que cae justo en una frontera
216
+ * pertenecía a DOS nodos, al que termina ahí y al que empieza. Como los
217
+ * hijos se recorren en orden, ganaba el que termina, así que preguntar por
218
+ * el principio de un nodo devolvía **el nodo anterior**.
219
+ *
220
+ * Se veía en el preview: en un degradado, cada carácter es su propio nodo
221
+ * de color, y hacer clic en la `m` de «Welcome» resaltaba la `o`. Monaco
222
+ * seleccionaba bien —usa el rango del nodo, no esta búsqueda—, así que el
223
+ * desfase era solo del resaltado, que es justo lo que hacía difícil verlo.
224
+ *
225
+ * La única excepción es el final del documento: un cursor aparcado tras el
226
+ * último carácter no tiene carácter que lo contenga, y la raíz lo reclama
227
+ * para que quien pregunte reciba algo con sentido.
228
+ */
229
+ findNodeAtOffset(offset: number): RedNode | null;
230
+ /**
231
+ * Adopt a fully-built array of children in one shot.
232
+ *
233
+ * Construction-only, and deliberately outside the mutation lock. The tree
234
+ * builders used to call `allowMutation(() => { for (…) appendChild(…) })` once
235
+ * **per node**, which on a 1736-node document meant 1736 closures, 1736
236
+ * `try/finally` frames and 1736 static-flag saves — pure ceremony, since a
237
+ * node that no caller has seen yet cannot be observed mid-mutation. This
238
+ * populates `children`, `parent` and the index cache in a single pass instead.
239
+ *
240
+ * `version` intentionally stays at 1: a freshly built node has not been
241
+ * *edited*, and bumping once per child would make the initial version an
242
+ * accidental child count.
243
+ *
244
+ * Only safe while `this` is still unreachable from the rest of the tree. Use
245
+ * `appendChild` and friends for anything after that.
246
+ */
247
+ initChildren(children: RedNode[]): void;
248
+ /**
249
+ * Append a child node.
250
+ * This creates a new RedNode wrapping the green node.
251
+ */
252
+ appendChild(child: RedNode): void;
253
+ /**
254
+ * Refresh the cached index of every child from `from` onwards.
255
+ *
256
+ * Called after a splice, which shifts the tail. This is O(n) but so is the
257
+ * splice it follows, so it costs nothing asymptotically and keeps reads O(1).
258
+ */
259
+ private reindexFrom;
260
+ /**
261
+ * Insert a child at a specific index.
262
+ */
263
+ insertChildAt(index: number, child: RedNode): void;
264
+ /**
265
+ * Remove a child by ID.
266
+ */
267
+ removeChild(id: NodeId): RedNode | null;
268
+ /**
269
+ * Replace a child with a new one by ID.
270
+ */
271
+ replaceChild(id: NodeId, newChild: RedNode): boolean;
272
+ /**
273
+ * Bump version (when attributes/metadata change without structural change).
274
+ */
275
+ bumpVersion(): void;
276
+ /** Convert to the public DocumentNode interface */
277
+ toDocumentNode(): DocumentNode;
278
+ /** Debug string */
279
+ toString(depth?: number): string;
280
+ }