@fuzdev/fuz_ui 0.198.0 → 0.199.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 (108) hide show
  1. package/README.md +5 -3
  2. package/dist/ApiIndex.svelte +2 -2
  3. package/dist/ApiModule.svelte +1 -1
  4. package/dist/LibraryDetail.svelte +14 -16
  5. package/dist/LibraryDetail.svelte.d.ts.map +1 -1
  6. package/dist/LibrarySummary.svelte +12 -12
  7. package/dist/Mdz.svelte +2 -2
  8. package/dist/MdzNodeView.svelte +8 -6
  9. package/dist/MdzNodeView.svelte.d.ts.map +1 -1
  10. package/dist/MdzRoot.svelte +30 -0
  11. package/dist/MdzRoot.svelte.d.ts +12 -0
  12. package/dist/MdzRoot.svelte.d.ts.map +1 -0
  13. package/dist/MdzStream.svelte +32 -0
  14. package/dist/MdzStream.svelte.d.ts +12 -0
  15. package/dist/MdzStream.svelte.d.ts.map +1 -0
  16. package/dist/MdzStreamNodeView.svelte +106 -0
  17. package/dist/MdzStreamNodeView.svelte.d.ts +9 -0
  18. package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -0
  19. package/dist/ProjectLinks.svelte +2 -0
  20. package/dist/ProjectLinks.svelte.d.ts.map +1 -1
  21. package/dist/declaration.svelte.js +2 -2
  22. package/dist/library.svelte.d.ts +9 -70
  23. package/dist/library.svelte.d.ts.map +1 -1
  24. package/dist/library.svelte.js +24 -18
  25. package/dist/library_helpers.d.ts +3 -3
  26. package/dist/library_helpers.js +3 -3
  27. package/dist/logos.d.ts +7 -0
  28. package/dist/logos.d.ts.map +1 -1
  29. package/dist/logos.js +9 -0
  30. package/dist/mdz.d.ts.map +1 -1
  31. package/dist/mdz.js +37 -29
  32. package/dist/mdz_components.d.ts +37 -23
  33. package/dist/mdz_components.d.ts.map +1 -1
  34. package/dist/mdz_components.js +16 -4
  35. package/dist/mdz_helpers.d.ts +46 -14
  36. package/dist/mdz_helpers.d.ts.map +1 -1
  37. package/dist/mdz_helpers.js +189 -56
  38. package/dist/mdz_lexer.d.ts.map +1 -1
  39. package/dist/mdz_lexer.js +18 -22
  40. package/dist/mdz_opcodes.d.ts +174 -0
  41. package/dist/mdz_opcodes.d.ts.map +1 -0
  42. package/dist/mdz_opcodes.js +14 -0
  43. package/dist/mdz_opcodes_to_nodes.d.ts +20 -0
  44. package/dist/mdz_opcodes_to_nodes.d.ts.map +1 -0
  45. package/dist/mdz_opcodes_to_nodes.js +332 -0
  46. package/dist/mdz_stream_parser.d.ts +80 -0
  47. package/dist/mdz_stream_parser.d.ts.map +1 -0
  48. package/dist/mdz_stream_parser.js +354 -0
  49. package/dist/mdz_stream_parser_block.d.ts +76 -0
  50. package/dist/mdz_stream_parser_block.d.ts.map +1 -0
  51. package/dist/mdz_stream_parser_block.js +458 -0
  52. package/dist/mdz_stream_parser_inline.d.ts +52 -0
  53. package/dist/mdz_stream_parser_inline.d.ts.map +1 -0
  54. package/dist/mdz_stream_parser_inline.js +378 -0
  55. package/dist/mdz_stream_parser_link.d.ts +22 -0
  56. package/dist/mdz_stream_parser_link.d.ts.map +1 -0
  57. package/dist/mdz_stream_parser_link.js +215 -0
  58. package/dist/mdz_stream_parser_state.d.ts +187 -0
  59. package/dist/mdz_stream_parser_state.d.ts.map +1 -0
  60. package/dist/mdz_stream_parser_state.js +398 -0
  61. package/dist/mdz_stream_parser_text.d.ts +14 -0
  62. package/dist/mdz_stream_parser_text.d.ts.map +1 -0
  63. package/dist/mdz_stream_parser_text.js +50 -0
  64. package/dist/mdz_stream_parser_url.d.ts +60 -0
  65. package/dist/mdz_stream_parser_url.d.ts.map +1 -0
  66. package/dist/mdz_stream_parser_url.js +307 -0
  67. package/dist/mdz_stream_state.svelte.d.ts +44 -0
  68. package/dist/mdz_stream_state.svelte.d.ts.map +1 -0
  69. package/dist/mdz_stream_state.svelte.js +357 -0
  70. package/dist/mdz_token_parser.d.ts.map +1 -1
  71. package/dist/mdz_token_parser.js +8 -39
  72. package/dist/module.svelte.js +1 -1
  73. package/dist/site.svelte.d.ts +13 -0
  74. package/dist/site.svelte.d.ts.map +1 -1
  75. package/dist/site.svelte.js +8 -2
  76. package/dist/tsdoc_mdz.d.ts +2 -2
  77. package/dist/tsdoc_mdz.js +2 -2
  78. package/dist/vite_plugin_pkg_json.d.ts +63 -0
  79. package/dist/vite_plugin_pkg_json.d.ts.map +1 -0
  80. package/dist/vite_plugin_pkg_json.js +126 -0
  81. package/package.json +6 -5
  82. package/src/lib/declaration.svelte.ts +2 -2
  83. package/src/lib/library.svelte.ts +37 -20
  84. package/src/lib/library_helpers.ts +3 -3
  85. package/src/lib/logos.ts +10 -0
  86. package/src/lib/mdz.ts +38 -29
  87. package/src/lib/mdz_components.ts +40 -19
  88. package/src/lib/mdz_helpers.ts +199 -56
  89. package/src/lib/mdz_lexer.ts +18 -20
  90. package/src/lib/mdz_opcodes.ts +205 -0
  91. package/src/lib/mdz_opcodes_to_nodes.ts +375 -0
  92. package/src/lib/mdz_stream_parser.ts +415 -0
  93. package/src/lib/mdz_stream_parser_block.ts +532 -0
  94. package/src/lib/mdz_stream_parser_inline.ts +414 -0
  95. package/src/lib/mdz_stream_parser_link.ts +271 -0
  96. package/src/lib/mdz_stream_parser_state.ts +539 -0
  97. package/src/lib/mdz_stream_parser_text.ts +77 -0
  98. package/src/lib/mdz_stream_parser_url.ts +365 -0
  99. package/src/lib/mdz_stream_state.svelte.ts +387 -0
  100. package/src/lib/mdz_token_parser.ts +13 -40
  101. package/src/lib/module.svelte.ts +1 -1
  102. package/src/lib/site.svelte.ts +17 -2
  103. package/src/lib/tsdoc_mdz.ts +2 -2
  104. package/src/lib/vite_plugin_pkg_json.ts +142 -0
  105. package/dist/package_helpers.d.ts +0 -150
  106. package/dist/package_helpers.d.ts.map +0 -1
  107. package/dist/package_helpers.js +0 -179
  108. package/src/lib/package_helpers.ts +0 -186
@@ -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"}