@fuzdev/fuz_ui 0.198.1 → 0.200.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 (113) hide show
  1. package/dist/ApiDeclarationList.svelte +2 -1
  2. package/dist/ApiDeclarationList.svelte.d.ts.map +1 -1
  3. package/dist/ApiIndex.svelte +2 -2
  4. package/dist/ApiModule.svelte +3 -3
  5. package/dist/DeclarationDetail.svelte +145 -90
  6. package/dist/DeclarationDetail.svelte.d.ts.map +1 -1
  7. package/dist/LibraryDetail.svelte +14 -16
  8. package/dist/LibraryDetail.svelte.d.ts.map +1 -1
  9. package/dist/LibrarySummary.svelte +12 -12
  10. package/dist/Mdz.svelte +2 -2
  11. package/dist/MdzNodeView.svelte +8 -6
  12. package/dist/MdzNodeView.svelte.d.ts.map +1 -1
  13. package/dist/MdzRoot.svelte +30 -0
  14. package/dist/MdzRoot.svelte.d.ts +12 -0
  15. package/dist/MdzRoot.svelte.d.ts.map +1 -0
  16. package/dist/MdzStream.svelte +32 -0
  17. package/dist/MdzStream.svelte.d.ts +12 -0
  18. package/dist/MdzStream.svelte.d.ts.map +1 -0
  19. package/dist/MdzStreamNodeView.svelte +106 -0
  20. package/dist/MdzStreamNodeView.svelte.d.ts +9 -0
  21. package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -0
  22. package/dist/ProjectLinks.svelte +1 -1
  23. package/dist/api_search.svelte.d.ts.map +1 -1
  24. package/dist/api_search.svelte.js +4 -2
  25. package/dist/declaration.svelte.d.ts +11 -0
  26. package/dist/declaration.svelte.d.ts.map +1 -1
  27. package/dist/declaration.svelte.js +15 -3
  28. package/dist/library.svelte.d.ts +9 -70
  29. package/dist/library.svelte.d.ts.map +1 -1
  30. package/dist/library.svelte.js +24 -18
  31. package/dist/library_helpers.d.ts +3 -3
  32. package/dist/library_helpers.js +3 -3
  33. package/dist/mdz.d.ts.map +1 -1
  34. package/dist/mdz.js +37 -29
  35. package/dist/mdz_components.d.ts +37 -23
  36. package/dist/mdz_components.d.ts.map +1 -1
  37. package/dist/mdz_components.js +16 -4
  38. package/dist/mdz_helpers.d.ts +46 -14
  39. package/dist/mdz_helpers.d.ts.map +1 -1
  40. package/dist/mdz_helpers.js +189 -56
  41. package/dist/mdz_lexer.d.ts.map +1 -1
  42. package/dist/mdz_lexer.js +18 -22
  43. package/dist/mdz_opcodes.d.ts +174 -0
  44. package/dist/mdz_opcodes.d.ts.map +1 -0
  45. package/dist/mdz_opcodes.js +14 -0
  46. package/dist/mdz_opcodes_to_nodes.d.ts +20 -0
  47. package/dist/mdz_opcodes_to_nodes.d.ts.map +1 -0
  48. package/dist/mdz_opcodes_to_nodes.js +332 -0
  49. package/dist/mdz_stream_parser.d.ts +80 -0
  50. package/dist/mdz_stream_parser.d.ts.map +1 -0
  51. package/dist/mdz_stream_parser.js +354 -0
  52. package/dist/mdz_stream_parser_block.d.ts +76 -0
  53. package/dist/mdz_stream_parser_block.d.ts.map +1 -0
  54. package/dist/mdz_stream_parser_block.js +458 -0
  55. package/dist/mdz_stream_parser_inline.d.ts +52 -0
  56. package/dist/mdz_stream_parser_inline.d.ts.map +1 -0
  57. package/dist/mdz_stream_parser_inline.js +378 -0
  58. package/dist/mdz_stream_parser_link.d.ts +22 -0
  59. package/dist/mdz_stream_parser_link.d.ts.map +1 -0
  60. package/dist/mdz_stream_parser_link.js +215 -0
  61. package/dist/mdz_stream_parser_state.d.ts +187 -0
  62. package/dist/mdz_stream_parser_state.d.ts.map +1 -0
  63. package/dist/mdz_stream_parser_state.js +398 -0
  64. package/dist/mdz_stream_parser_text.d.ts +14 -0
  65. package/dist/mdz_stream_parser_text.d.ts.map +1 -0
  66. package/dist/mdz_stream_parser_text.js +50 -0
  67. package/dist/mdz_stream_parser_url.d.ts +60 -0
  68. package/dist/mdz_stream_parser_url.d.ts.map +1 -0
  69. package/dist/mdz_stream_parser_url.js +307 -0
  70. package/dist/mdz_stream_state.svelte.d.ts +44 -0
  71. package/dist/mdz_stream_state.svelte.d.ts.map +1 -0
  72. package/dist/mdz_stream_state.svelte.js +357 -0
  73. package/dist/mdz_token_parser.d.ts.map +1 -1
  74. package/dist/mdz_token_parser.js +8 -39
  75. package/dist/module.svelte.d.ts +2 -0
  76. package/dist/module.svelte.d.ts.map +1 -1
  77. package/dist/module.svelte.js +3 -1
  78. package/dist/site.svelte.d.ts +13 -0
  79. package/dist/site.svelte.d.ts.map +1 -1
  80. package/dist/site.svelte.js +8 -2
  81. package/dist/tsdoc_mdz.d.ts +2 -2
  82. package/dist/tsdoc_mdz.js +2 -2
  83. package/dist/vite_plugin_pkg_json.d.ts +63 -0
  84. package/dist/vite_plugin_pkg_json.d.ts.map +1 -0
  85. package/dist/vite_plugin_pkg_json.js +126 -0
  86. package/package.json +8 -7
  87. package/src/lib/api_search.svelte.ts +4 -2
  88. package/src/lib/declaration.svelte.ts +18 -3
  89. package/src/lib/library.svelte.ts +37 -20
  90. package/src/lib/library_helpers.ts +3 -3
  91. package/src/lib/mdz.ts +38 -29
  92. package/src/lib/mdz_components.ts +40 -19
  93. package/src/lib/mdz_helpers.ts +199 -56
  94. package/src/lib/mdz_lexer.ts +18 -20
  95. package/src/lib/mdz_opcodes.ts +205 -0
  96. package/src/lib/mdz_opcodes_to_nodes.ts +375 -0
  97. package/src/lib/mdz_stream_parser.ts +415 -0
  98. package/src/lib/mdz_stream_parser_block.ts +532 -0
  99. package/src/lib/mdz_stream_parser_inline.ts +414 -0
  100. package/src/lib/mdz_stream_parser_link.ts +271 -0
  101. package/src/lib/mdz_stream_parser_state.ts +539 -0
  102. package/src/lib/mdz_stream_parser_text.ts +77 -0
  103. package/src/lib/mdz_stream_parser_url.ts +365 -0
  104. package/src/lib/mdz_stream_state.svelte.ts +387 -0
  105. package/src/lib/mdz_token_parser.ts +13 -40
  106. package/src/lib/module.svelte.ts +5 -1
  107. package/src/lib/site.svelte.ts +17 -2
  108. package/src/lib/tsdoc_mdz.ts +2 -2
  109. package/src/lib/vite_plugin_pkg_json.ts +142 -0
  110. package/dist/package_helpers.d.ts +0 -150
  111. package/dist/package_helpers.d.ts.map +0 -1
  112. package/dist/package_helpers.js +0 -179
  113. package/src/lib/package_helpers.ts +0 -186
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Opcode types for the mdz streaming parser.
3
+ *
4
+ * Opcodes are serializable rendering instructions emitted by `MdzStreamParser`.
5
+ * They tell a renderer what to do next — open a container, append text, close it,
6
+ * or revert an optimistic assumption. Target-agnostic: works for HTML, Svelte, PDF, etc.
7
+ *
8
+ * The parser makes optimistic assumptions about ambiguous syntax (e.g., `**` is probably bold)
9
+ * and emits `revert` opcodes to correct when wrong. This enables true streaming rendering
10
+ * without ever re-parsing.
11
+ *
12
+ * @module
13
+ */
14
+ /**
15
+ * Unique monotonic identifier for each node created by the parser.
16
+ * IDs are never reused within a parser instance.
17
+ */
18
+ export type MdzNodeId = number;
19
+ /** Node types that can be opened as containers. */
20
+ export type MdzContainerNodeType = 'Paragraph' | 'Bold' | 'Italic' | 'Strikethrough' | 'Link' | 'Heading' | 'Element' | 'Component' | 'Codeblock' | 'Code';
21
+ /** Node types for self-contained leaf elements. */
22
+ export type MdzVoidNodeType = 'Hr';
23
+ /** Discriminant for leaf text nodes. */
24
+ export type MdzTextNodeType = 'Text' | 'Code';
25
+ /**
26
+ * Open a container node. The renderer starts a new element/wrapper.
27
+ * Children are subsequent opcodes until the matching `close`.
28
+ */
29
+ export interface MdzOpcodeOpen {
30
+ type: 'open';
31
+ id: MdzNodeId;
32
+ node_type: MdzContainerNodeType;
33
+ /** Byte offset in the full input where the opening delimiter begins. */
34
+ start: number;
35
+ /** Heading level (1-6). Present when `node_type` is `'Heading'`. */
36
+ level?: 1 | 2 | 3 | 4 | 5 | 6;
37
+ /** Tag name. Present when `node_type` is `'Element'` or `'Component'`. */
38
+ name?: string;
39
+ /** Language hint. Present when `node_type` is `'Codeblock'`. */
40
+ lang?: string | null;
41
+ }
42
+ /**
43
+ * Close a previously opened container node.
44
+ * Carries deferred metadata that wasn't known at open time.
45
+ */
46
+ export interface MdzOpcodeClose {
47
+ type: 'close';
48
+ id: MdzNodeId;
49
+ /** Byte offset in the full input immediately after the closing delimiter. */
50
+ end: number;
51
+ /** Link URL/path, resolved when `](url)` completes. */
52
+ reference?: string;
53
+ /** Link type, resolved alongside `reference`. */
54
+ link_type?: 'external' | 'internal';
55
+ /** Heading slug, computed from full heading content. */
56
+ heading_id?: string;
57
+ /**
58
+ * If true, consumer drops this node and its descendants from the tree.
59
+ * Used for whitespace-only paragraphs that match nothing in `mdz_parse`'s
60
+ * output — the streaming parser emits open/text speculatively, then
61
+ * retroactively drops the empty wrapper at close.
62
+ */
63
+ discard?: boolean;
64
+ }
65
+ /**
66
+ * Create a leaf text or code node.
67
+ * The parent is implicit — the innermost open container on the renderer's stack.
68
+ */
69
+ export interface MdzOpcodeText {
70
+ type: 'text';
71
+ id: MdzNodeId;
72
+ content: string;
73
+ text_type: MdzTextNodeType;
74
+ /** Byte offset where this node begins (for Code, the opening backtick). */
75
+ start: number;
76
+ /** Byte offset immediately after this node ends (for Code, after the closing backtick). */
77
+ end: number;
78
+ }
79
+ /**
80
+ * Append content to an existing text node.
81
+ * Streaming optimization — avoids creating a new node per chunk
82
+ * during plain text runs.
83
+ */
84
+ export interface MdzOpcodeAppendText {
85
+ type: 'append_text';
86
+ id: MdzNodeId;
87
+ content: string;
88
+ }
89
+ /**
90
+ * Trim `count` characters from the end of an existing text node.
91
+ * If trimming empties the node, the consumer removes it from its parent.
92
+ *
93
+ * Used by paragraph/codeblock close to drop the trailing newline that
94
+ * separates inline content from the block boundary. Emitted instead of
95
+ * retroactively mutating the prior `text`/`append_text` opcode, so the
96
+ * opcode stream is append-only.
97
+ */
98
+ export interface MdzOpcodeTrimText {
99
+ type: 'trim_text';
100
+ id: MdzNodeId;
101
+ count: number;
102
+ }
103
+ /**
104
+ * Create a self-contained leaf node (e.g., horizontal rule).
105
+ * Inserted as a child of the innermost open container, or at root level.
106
+ */
107
+ export interface MdzOpcodeVoid {
108
+ type: 'void';
109
+ id: MdzNodeId;
110
+ node_type: MdzVoidNodeType;
111
+ /** Byte offset in the full input where this element begins. */
112
+ start: number;
113
+ /** Byte offset immediately after this element ends. */
114
+ end: number;
115
+ }
116
+ /**
117
+ * Undo an optimistic open. Removes the container wrapper,
118
+ * inserts `replacement_text` as literal text at the container's position,
119
+ * and re-parents the container's children to the grandparent.
120
+ *
121
+ * When `wrap_node_type` and `wrap_id` are set, the replacement text and
122
+ * re-parented children are wrapped in a new container of the given type
123
+ * instead of being placed directly at the grandparent level. The wrapper
124
+ * is pushed onto the consumer's stack (open for future content). This is
125
+ * used for block-level reverts (e.g., codeblock → paragraph) where the
126
+ * grandparent is root and content needs a container.
127
+ */
128
+ export interface MdzOpcodeRevert {
129
+ type: 'revert';
130
+ id: MdzNodeId;
131
+ /** The delimiter text to emit as literal content (e.g., `"**"`, `"["`, `"<Tag>"`). */
132
+ replacement_text: string;
133
+ /** Byte offset of the original opening delimiter in the full input. */
134
+ start: number;
135
+ /** Wrap replacement text and re-parented children in a new container of this type. */
136
+ wrap_node_type?: MdzContainerNodeType;
137
+ /** ID for the wrapper node. Required when `wrap_node_type` is set. */
138
+ wrap_id?: MdzNodeId;
139
+ }
140
+ /**
141
+ * Retroactively wrap an existing text node in a container.
142
+ * Used for text-first auto-links: URL/path text streams as plain text,
143
+ * then gets wrapped in a Link when the URL boundary is found.
144
+ *
145
+ * When `trim_end` is set, trailing characters (punctuation) are trimmed
146
+ * from the target text node and placed in a new sibling Text node after
147
+ * the Link wrapper, identified by `trim_id`.
148
+ */
149
+ export interface MdzOpcodeWrap {
150
+ type: 'wrap';
151
+ /** ID for the new Link container node. */
152
+ id: MdzNodeId;
153
+ /** Container type to wrap in (always `'Link'` for now). */
154
+ node_type: 'Link';
155
+ /** ID of the existing text node to wrap. */
156
+ target_id: MdzNodeId;
157
+ /** Resolved URL or path reference. */
158
+ reference: string;
159
+ /** Whether the link is external (URL) or internal (path). */
160
+ link_type: 'external' | 'internal';
161
+ /** Byte offset where the URL/path begins. */
162
+ start: number;
163
+ /** Byte offset immediately after the URL/path (before any trimmed punctuation). */
164
+ end: number;
165
+ /** Number of trailing chars to trim from target and place after the link. */
166
+ trim_end?: number;
167
+ /** ID for the trimmed-text sibling node. Required when `trim_end` > 0. */
168
+ trim_id?: MdzNodeId;
169
+ }
170
+ /** All node types that can appear in the mdz tree. */
171
+ export type MdzNodeType = MdzContainerNodeType | MdzVoidNodeType | MdzTextNodeType;
172
+ /** Discriminated union of all mdz opcodes. */
173
+ export type MdzOpcode = MdzOpcodeOpen | MdzOpcodeClose | MdzOpcodeText | MdzOpcodeAppendText | MdzOpcodeTrimText | MdzOpcodeVoid | MdzOpcodeRevert | MdzOpcodeWrap;
174
+ //# sourceMappingURL=mdz_opcodes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdz_opcodes.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_opcodes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Opcode types for the mdz streaming parser.
3
+ *
4
+ * Opcodes are serializable rendering instructions emitted by `MdzStreamParser`.
5
+ * They tell a renderer what to do next — open a container, append text, close it,
6
+ * or revert an optimistic assumption. Target-agnostic: works for HTML, Svelte, PDF, etc.
7
+ *
8
+ * The parser makes optimistic assumptions about ambiguous syntax (e.g., `**` is probably bold)
9
+ * and emits `revert` opcodes to correct when wrong. This enables true streaming rendering
10
+ * without ever re-parsing.
11
+ *
12
+ * @module
13
+ */
14
+ export {};
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Converts a stream of mdz opcodes to the `MdzNode[]` tree representation.
3
+ *
4
+ * Stack-based replay: `open` pushes a frame, `close` pops and builds the node,
5
+ * `text`/`void` create leaf nodes, `append_text` extends the last text node,
6
+ * `revert` undoes an optimistic open and re-parents children.
7
+ *
8
+ * The output is identical to `mdz_parse()` from `mdz.ts`, enabling
9
+ * validation of the streaming parser against the existing fixture suite.
10
+ *
11
+ * @module
12
+ */
13
+ import type { MdzNode } from './mdz.js';
14
+ import type { MdzOpcode } from './mdz_opcodes.js';
15
+ /**
16
+ * Convert an array of mdz opcodes to the `MdzNode[]` tree.
17
+ * Produces output identical to `mdz_parse()`.
18
+ */
19
+ export declare const mdz_opcodes_to_nodes: (opcodes: Array<MdzOpcode>) => Array<MdzNode>;
20
+ //# sourceMappingURL=mdz_opcodes_to_nodes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdz_opcodes_to_nodes.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_opcodes_to_nodes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAC,OAAO,EAAwD,MAAM,UAAU,CAAC;AAC7F,OAAO,KAAK,EAAC,SAAS,EAAY,MAAM,kBAAkB,CAAC;AAwB3D;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,KAAK,CAAC,SAAS,CAAC,KAAG,KAAK,CAAC,OAAO,CAkN7E,CAAC"}
@@ -0,0 +1,332 @@
1
+ /**
2
+ * Converts a stream of mdz opcodes to the `MdzNode[]` tree representation.
3
+ *
4
+ * Stack-based replay: `open` pushes a frame, `close` pops and builds the node,
5
+ * `text`/`void` create leaf nodes, `append_text` extends the last text node,
6
+ * `revert` undoes an optimistic open and re-parents children.
7
+ *
8
+ * The output is identical to `mdz_parse()` from `mdz.ts`, enabling
9
+ * validation of the streaming parser against the existing fixture suite.
10
+ *
11
+ * @module
12
+ */
13
+ import { DEV } from 'esm-env';
14
+ import { extract_single_tag, mdz_heading_id, mdz_merge_adjacent_text, mdz_push_merging_text, } from './mdz_helpers.js';
15
+ /**
16
+ * Convert an array of mdz opcodes to the `MdzNode[]` tree.
17
+ * Produces output identical to `mdz_parse()`.
18
+ */
19
+ export const mdz_opcodes_to_nodes = (opcodes) => {
20
+ const root = [];
21
+ const stack = [];
22
+ // index node IDs to their text content (for append_text and wrap)
23
+ const text_nodes = new Map();
24
+ // track which children array contains each text node (for wrap)
25
+ const node_parents = new Map();
26
+ const target = () => {
27
+ return stack.length > 0 ? stack[stack.length - 1].children : root;
28
+ };
29
+ for (const op of opcodes) {
30
+ switch (op.type) {
31
+ case 'open': {
32
+ stack.push({
33
+ id: op.id,
34
+ node_type: op.node_type,
35
+ children: [],
36
+ start: op.start,
37
+ level: op.level,
38
+ name: op.name,
39
+ lang: op.lang,
40
+ });
41
+ break;
42
+ }
43
+ case 'close': {
44
+ if (DEV && stack.length === 0) {
45
+ throw new Error(`mdz_opcodes_to_nodes: close for id ${op.id} but stack is empty`);
46
+ }
47
+ const frame = stack.pop();
48
+ if (!frame)
49
+ break;
50
+ // discard signal: the parser determined this node and its descendants
51
+ // should not appear in the tree (e.g. whitespace-only paragraph)
52
+ if (op.discard)
53
+ break;
54
+ // apply deferred metadata
55
+ frame.end = op.end;
56
+ if (op.reference !== undefined)
57
+ frame.reference = op.reference;
58
+ if (op.link_type !== undefined)
59
+ frame.link_type = op.link_type;
60
+ if (op.heading_id !== undefined)
61
+ frame.heading_id = op.heading_id;
62
+ const node = build_node(frame);
63
+ if (node)
64
+ target().push(node);
65
+ break;
66
+ }
67
+ case 'text': {
68
+ const node = op.text_type === 'Code'
69
+ ? { type: 'Code', content: op.content, start: op.start, end: op.end }
70
+ : { type: 'Text', content: op.content, start: op.start, end: op.end };
71
+ text_nodes.set(op.id, node);
72
+ const dest = target();
73
+ dest.push(node);
74
+ node_parents.set(op.id, dest);
75
+ break;
76
+ }
77
+ case 'append_text': {
78
+ if (DEV && !text_nodes.has(op.id)) {
79
+ throw new Error(`mdz_opcodes_to_nodes: append_text for unknown id ${op.id}`);
80
+ }
81
+ const existing = text_nodes.get(op.id);
82
+ if (existing) {
83
+ existing.content += op.content;
84
+ existing.end += op.content.length;
85
+ }
86
+ break;
87
+ }
88
+ case 'trim_text': {
89
+ if (DEV && !text_nodes.has(op.id)) {
90
+ throw new Error(`mdz_opcodes_to_nodes: trim_text for unknown id ${op.id}`);
91
+ }
92
+ const existing = text_nodes.get(op.id);
93
+ if (existing) {
94
+ existing.content = existing.content.slice(0, existing.content.length - op.count);
95
+ existing.end -= op.count;
96
+ if (existing.content.length === 0) {
97
+ const parent = node_parents.get(op.id);
98
+ if (parent) {
99
+ const idx = parent.indexOf(existing);
100
+ if (idx !== -1)
101
+ parent.splice(idx, 1);
102
+ }
103
+ text_nodes.delete(op.id);
104
+ node_parents.delete(op.id);
105
+ }
106
+ }
107
+ break;
108
+ }
109
+ case 'void': {
110
+ target().push({ type: 'Hr', start: op.start, end: op.end });
111
+ break;
112
+ }
113
+ case 'revert': {
114
+ // find and remove the reverted node's frame from the stack.
115
+ // fast path: reverts from #revert_all_optimistic always target the
116
+ // top of stack, so check that first to avoid splice + array alloc.
117
+ let reverted_frame = null;
118
+ if (stack.length > 0 && stack[stack.length - 1].id === op.id) {
119
+ reverted_frame = stack.pop();
120
+ }
121
+ else {
122
+ for (let i = stack.length - 1; i >= 0; i--) {
123
+ if (stack[i].id === op.id) {
124
+ reverted_frame = stack.splice(i, 1)[0];
125
+ break;
126
+ }
127
+ }
128
+ }
129
+ if (DEV && !reverted_frame) {
130
+ throw new Error(`mdz_opcodes_to_nodes: revert for id ${op.id} but not found on stack`);
131
+ }
132
+ if (reverted_frame) {
133
+ if (op.wrap_node_type != null && op.wrap_id != null) {
134
+ // block-level revert: wrap content in a new container (e.g. Paragraph)
135
+ // and push it onto the stack so future opcodes flow into it.
136
+ const wrapper = {
137
+ id: op.wrap_id,
138
+ node_type: op.wrap_node_type,
139
+ children: [],
140
+ start: op.start,
141
+ };
142
+ if (op.replacement_text) {
143
+ mdz_push_merging_text(wrapper.children, {
144
+ type: 'Text',
145
+ content: op.replacement_text,
146
+ start: op.start,
147
+ end: op.start + op.replacement_text.length,
148
+ });
149
+ }
150
+ for (const child of reverted_frame.children) {
151
+ mdz_push_merging_text(wrapper.children, child);
152
+ }
153
+ stack.push(wrapper);
154
+ }
155
+ else {
156
+ const dest = target();
157
+ // Re-parent replacement text and children, coalescing adjacent
158
+ // Text nodes inline. This reduces deeply nested reverts from
159
+ // O(n²) array pushes to O(n) string concatenations — each revert
160
+ // merges into the parent's last text node instead of growing the array.
161
+ if (op.replacement_text) {
162
+ mdz_push_merging_text(dest, {
163
+ type: 'Text',
164
+ content: op.replacement_text,
165
+ start: op.start,
166
+ end: op.start + op.replacement_text.length,
167
+ });
168
+ }
169
+ for (const child of reverted_frame.children) {
170
+ mdz_push_merging_text(dest, child);
171
+ }
172
+ }
173
+ }
174
+ break;
175
+ }
176
+ case 'wrap': {
177
+ const text_node = text_nodes.get(op.target_id);
178
+ const parent_children = node_parents.get(op.target_id);
179
+ if (!text_node || !parent_children)
180
+ break;
181
+ const idx = parent_children.indexOf(text_node);
182
+ if (idx === -1)
183
+ break;
184
+ // handle trailing punctuation trim
185
+ let trimmed_node = null;
186
+ if (op.trim_end && op.trim_end > 0 && op.trim_id != null) {
187
+ const trimmed_content = text_node.content.slice(text_node.content.length - op.trim_end);
188
+ text_node.content = text_node.content.slice(0, text_node.content.length - op.trim_end);
189
+ text_node.end -= op.trim_end;
190
+ trimmed_node = {
191
+ type: 'Text',
192
+ content: trimmed_content,
193
+ start: text_node.end,
194
+ end: text_node.end + trimmed_content.length,
195
+ };
196
+ text_nodes.set(op.trim_id, trimmed_node);
197
+ node_parents.set(op.trim_id, parent_children);
198
+ }
199
+ // create Link wrapping the text node
200
+ const link = {
201
+ type: 'Link',
202
+ reference: op.reference,
203
+ children: [text_node],
204
+ link_type: op.link_type,
205
+ start: op.start,
206
+ end: op.end,
207
+ };
208
+ // replace text node with [Link, trimmed?] in parent
209
+ if (trimmed_node) {
210
+ parent_children.splice(idx, 1, link, trimmed_node);
211
+ }
212
+ else {
213
+ parent_children[idx] = link;
214
+ }
215
+ node_parents.delete(op.target_id);
216
+ break;
217
+ }
218
+ }
219
+ }
220
+ return root;
221
+ };
222
+ /**
223
+ * Build an `MdzNode` from a completed stack frame.
224
+ */
225
+ const build_node = (frame) => {
226
+ switch (frame.node_type) {
227
+ case 'Paragraph': {
228
+ const children = mdz_merge_adjacent_text(frame.children);
229
+ if (children.length === 0)
230
+ return null;
231
+ // extract single tag (MDX convention)
232
+ const single_tag = extract_single_tag(children);
233
+ if (single_tag)
234
+ return single_tag;
235
+ return {
236
+ type: 'Paragraph',
237
+ children,
238
+ start: children[0].start,
239
+ end: children[children.length - 1].end,
240
+ };
241
+ }
242
+ case 'Heading': {
243
+ const children = mdz_merge_adjacent_text(frame.children);
244
+ const id = frame.heading_id ?? mdz_heading_id(children);
245
+ return {
246
+ type: 'Heading',
247
+ level: frame.level ?? 1,
248
+ id,
249
+ children,
250
+ start: frame.start,
251
+ end: frame.end,
252
+ };
253
+ }
254
+ case 'Bold':
255
+ return {
256
+ type: 'Bold',
257
+ children: mdz_merge_adjacent_text(frame.children),
258
+ start: frame.start,
259
+ end: frame.end,
260
+ };
261
+ case 'Italic':
262
+ return {
263
+ type: 'Italic',
264
+ children: mdz_merge_adjacent_text(frame.children),
265
+ start: frame.start,
266
+ end: frame.end,
267
+ };
268
+ case 'Strikethrough':
269
+ return {
270
+ type: 'Strikethrough',
271
+ children: mdz_merge_adjacent_text(frame.children),
272
+ start: frame.start,
273
+ end: frame.end,
274
+ };
275
+ case 'Link':
276
+ return {
277
+ type: 'Link',
278
+ reference: frame.reference ?? '',
279
+ children: mdz_merge_adjacent_text(frame.children),
280
+ link_type: frame.link_type ?? 'internal',
281
+ start: frame.start,
282
+ end: frame.end,
283
+ };
284
+ case 'Code': {
285
+ // optimistic inline code container — concatenate children text into leaf MdzCodeNode
286
+ let content = '';
287
+ for (const c of frame.children) {
288
+ if (c.type === 'Text')
289
+ content += c.content;
290
+ }
291
+ return {
292
+ type: 'Code',
293
+ content,
294
+ start: frame.start,
295
+ end: frame.end,
296
+ };
297
+ }
298
+ case 'Codeblock': {
299
+ // code block content is in children as text
300
+ let content = '';
301
+ for (const c of frame.children) {
302
+ if (c.type === 'Text')
303
+ content += c.content;
304
+ }
305
+ return {
306
+ type: 'Codeblock',
307
+ lang: frame.lang ?? null,
308
+ content,
309
+ start: frame.start,
310
+ end: frame.end,
311
+ };
312
+ }
313
+ case 'Element':
314
+ return {
315
+ type: 'Element',
316
+ name: frame.name ?? '',
317
+ children: mdz_merge_adjacent_text(frame.children),
318
+ start: frame.start,
319
+ end: frame.end,
320
+ };
321
+ case 'Component':
322
+ return {
323
+ type: 'Component',
324
+ name: frame.name ?? '',
325
+ children: mdz_merge_adjacent_text(frame.children),
326
+ start: frame.start,
327
+ end: frame.end,
328
+ };
329
+ default:
330
+ return null;
331
+ }
332
+ };
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Streaming opcode parser for mdz.
3
+ *
4
+ * Fed chunks of text (e.g., from LLM output), emits opcodes as rendering
5
+ * instructions. Makes optimistic assumptions about ambiguous syntax and
6
+ * emits `revert` opcodes to correct when wrong. Never re-parses.
7
+ *
8
+ * The design was independently arrived at but shares goals with
9
+ * {@link https://bsky.app/profile/pngwn.at/post/3mi527zntb22n @pngwn.at}'s
10
+ * Penguin-Flavoured Markdown (PFM): restrict the syntax so streaming is tractable,
11
+ * render optimistically and correct when wrong, emit serializable opcodes
12
+ * to avoid re-parsing, and keep the opcodes target-agnostic so any renderer
13
+ * can consume them. mdz diverges in one respect: the Svelte consumer
14
+ * (`MdzStreamState`) does build a reactive tree from opcodes — the platform
15
+ * dictates this — but mutations are fine-grained via `$state`, not diffed.
16
+ *
17
+ * The parser is split across sibling modules: this file holds the public
18
+ * `MdzStreamParser` class and the `process_loop` / `process_inline`
19
+ * orchestrators. Per-category handlers (block / inline / link / url / text)
20
+ * live in `mdz_stream_parser_*.ts` as free functions taking the shared
21
+ * `MdzStreamParserState` as first argument.
22
+ *
23
+ * Usage:
24
+ * ```ts
25
+ * const parser = new MdzStreamParser();
26
+ * parser.feed('hello **bold');
27
+ * const ops1 = parser.take_opcodes(); // open Paragraph, text "hello ", open Bold, text "bold"
28
+ * parser.feed('** world');
29
+ * const ops2 = parser.take_opcodes(); // close Bold, text " world"
30
+ * parser.finish();
31
+ * const ops3 = parser.take_opcodes(); // close Paragraph
32
+ * ```
33
+ *
34
+ * @module
35
+ */
36
+ import type { MdzOpcode } from './mdz_opcodes.js';
37
+ /**
38
+ * Streaming opcode parser for mdz content.
39
+ * Feed chunks via `feed()`, retrieve opcodes via `take_opcodes()`, call `finish()` at end.
40
+ *
41
+ * The opcode sequence is not deterministic across chunk boundaries — the same input
42
+ * fed in different chunk sizes may produce different `text`/`append_text` splits and
43
+ * different optimistic/revert sequences. The final tree (via `mdz_opcodes_to_nodes`)
44
+ * matches the one-shot result for all input except one case: italic (`_..._`) where
45
+ * the opening and closing delimiters straddle a chunk boundary. Italic is
46
+ * non-optimistic — it requires a confirmed closer in the buffer — so when the
47
+ * closer arrives only in a later chunk, the opening `_` has already been emitted
48
+ * as text and italic cannot apply retroactively. See `try_italic` in
49
+ * `mdz_stream_parser_inline.ts`.
50
+ */
51
+ export declare class MdzStreamParser {
52
+ #private;
53
+ /**
54
+ * Feed a chunk of text to the parser.
55
+ * Opcodes are accumulated and retrieved via `take_opcodes()`.
56
+ */
57
+ feed(chunk: string): void;
58
+ /**
59
+ * Signal end of input. Resolves all pending state: closes open blocks,
60
+ * reverts unclosed optimistic opens, trims trailing newlines.
61
+ *
62
+ * Trailing-newline trimming is handled in one place: `trim_trailing_newline()`
63
+ * called at the top of `close_paragraph()` and `close_codeblock_at_eof()`,
64
+ * before either function reverts its inner stack. The trim sees the
65
+ * just-flushed text node's `last_text_id` (or a still-accumulated `\n`) and
66
+ * emits a `trim_text` opcode. Revert opcodes only fire after.
67
+ *
68
+ * Optimistic-container revert is handled by `close_paragraph` and
69
+ * `close_heading` (each pops its own inner stack), so no separate
70
+ * `revert_all_optimistic` is needed — optimistic containers can only exist
71
+ * inside an open Paragraph or Heading (parser invariant).
72
+ */
73
+ finish(): void;
74
+ /**
75
+ * Drain and return all accumulated opcodes.
76
+ * Destructive — empties the internal queue. The returned array is owned by the caller.
77
+ */
78
+ take_opcodes(): Array<MdzOpcode>;
79
+ }
80
+ //# sourceMappingURL=mdz_stream_parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdz_stream_parser.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,kBAAkB,CAAC;AAgEhD;;;;;;;;;;;;;GAaG;AACH,qBAAa,eAAe;;IAG3B;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAczB;;;;;;;;;;;;;;OAcG;IACH,MAAM,IAAI,IAAI;IA0Bd;;;OAGG;IACH,YAAY,IAAI,KAAK,CAAC,SAAS,CAAC;CAKhC"}