flux-md 0.5.0 → 0.5.5
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.
- package/CHANGELOG.md +173 -0
- package/package.json +1 -1
- package/src/renderers/Math.tsx +5 -3
- package/src/renderers/Mermaid.tsx +4 -3
- package/src/wasm/flux_md_core_bg.wasm +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,179 @@ Notable changes to flux-md. Format based on
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/); this project aims to follow
|
|
5
5
|
[Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## 0.5.5 — 2026-05-28
|
|
8
|
+
|
|
9
|
+
### Performance
|
|
10
|
+
|
|
11
|
+
- 1× memcpy in the paragraph / container cache assembly (was 2×). Both caches
|
|
12
|
+
were building the block HTML in two stages — concatenate
|
|
13
|
+
`committed + active` into an intermediate `String`, then concatenate
|
|
14
|
+
`<p>` + that into the output — so a long open paragraph or container did two
|
|
15
|
+
memcpys of the committed inner per append. The fix builds directly into the
|
|
16
|
+
output buffer and trims trailing whitespace in-place; the container case
|
|
17
|
+
backs out a provisional `<p>` opener if the body content turns out to be
|
|
18
|
+
empty (preserving the empty-body fix from 0.5.4). Output is byte-identical.
|
|
19
|
+
|
|
20
|
+
200 KB bench (best of 7), chunk=16:
|
|
21
|
+
|
|
22
|
+
| shape | 0.5.4 | 0.5.5 | speedup |
|
|
23
|
+
|-----------------|---------:|---------:|--------:|
|
|
24
|
+
| `long_paragraph`| 142 ms | **96 ms**| 1.48× |
|
|
25
|
+
| `emphasis_para` | 170 ms | **116 ms**| 1.47× |
|
|
26
|
+
| `big_blockquote`| 213 ms | **157 ms**| 1.36× |
|
|
27
|
+
| `big_alert` | 343 ms | **237 ms**| 1.45× |
|
|
28
|
+
|
|
29
|
+
Modest wins at every chunk size for the affected caches; the
|
|
30
|
+
table / list / fence caches are unchanged (they were already 1× memcpy).
|
|
31
|
+
|
|
32
|
+
## 0.5.4 — 2026-05-28
|
|
33
|
+
|
|
34
|
+
### Fixed (mid-stream rendering)
|
|
35
|
+
|
|
36
|
+
- **GFM tables now form during streaming, not just at finalize.** Streaming a
|
|
37
|
+
table char-by-char (or in any chunking where the delimiter row's `\n` lands
|
|
38
|
+
in a different chunk than the row's content) used to leave the block as a
|
|
39
|
+
`<p>` spanning both lines until `.finalize()` ran. The paragraph cache's
|
|
40
|
+
delimiter-detection walked from the line AFTER the cut and so missed a
|
|
41
|
+
delimiter row that completed inside the line the cut had advanced into. The
|
|
42
|
+
fix re-checks the line containing the cut whenever it has just completed,
|
|
43
|
+
guarded by a cheap `bytes[cut..].contains('\n')` so long open paragraphs
|
|
44
|
+
without interior `\n` still take the O(new bytes) per-call path.
|
|
45
|
+
- **Open alerts/blockquotes with an empty body no longer render an empty
|
|
46
|
+
`<p></p>`.** A `> [!NOTE]\n` shown mid-stream now matches the full renderer:
|
|
47
|
+
`<div class="markdown-alert ...">…<p class="...title">Note</p></div>` with
|
|
48
|
+
no empty body paragraph. The container cache was wrapping the body in
|
|
49
|
+
`<p>…</p>` unconditionally, even when the body was empty.
|
|
50
|
+
|
|
51
|
+
Both bugs only manifested *before* `finalize()`. The post-finalize output —
|
|
52
|
+
what every existing parity test checks — was already correct, which is why
|
|
53
|
+
neither was caught earlier. A new `tests/midstream_parity.rs` asserts that the
|
|
54
|
+
streamed view of an open block matches what one-shot parsing produces for the
|
|
55
|
+
same prefix (tables, alerts, blockquotes, lists, code fences, math fences).
|
|
56
|
+
|
|
57
|
+
### Performance
|
|
58
|
+
|
|
59
|
+
- `big_table` at the artificial `chunk=16` stress case is ~280 ms (was ~145 ms
|
|
60
|
+
in 0.5.3). The 145 ms was the *incorrect* path: the paragraph cache treated
|
|
61
|
+
the whole 200 KB table as a single growing paragraph until finalize, never
|
|
62
|
+
engaging the table cache. The 280 ms is the cost of correctly emitting the
|
|
63
|
+
table mid-stream at the smallest chunk size. Every realistic LLM streaming
|
|
64
|
+
chunk size (≥64 bytes) is unchanged — `big_table` at chunk=64 is 73 ms,
|
|
65
|
+
chunk=256 is 38 ms, etc.
|
|
66
|
+
|
|
67
|
+
## 0.5.3 — 2026-05-28
|
|
68
|
+
|
|
69
|
+
### Performance
|
|
70
|
+
|
|
71
|
+
- **Streaming long open resumable containers is now O(n).** A long
|
|
72
|
+
`> [!NOTE]` alert, a `>`-quoted explanation, or a flat bullet/ordered list
|
|
73
|
+
used to re-run scan + inline render over the whole growing inner on every
|
|
74
|
+
append (O(n²)). Three new tail caches mirror the existing fence/table
|
|
75
|
+
pattern:
|
|
76
|
+
|
|
77
|
+
- `ContainerCache` — single-paragraph blockquote / GitHub alert. Wraps
|
|
78
|
+
the existing paragraph-cache (inline-boundary commit) with a
|
|
79
|
+
`>`-stripped inner buffer; the wrapper HTML (`<blockquote>` /
|
|
80
|
+
alert `<div>`) is built once at arm time, each new `> ` line is
|
|
81
|
+
stripped once into the inner buffer, only the unsettled inline tail is
|
|
82
|
+
re-rendered. Bails on a blank `>`-line (paragraph break inside the
|
|
83
|
+
container), lazy continuation, or `\r`.
|
|
84
|
+
|
|
85
|
+
- `ListCache` — tight, flat list (the LLM-emit shape: one sibling marker
|
|
86
|
+
per line, no blanks, no continuation, no nesting). Opener
|
|
87
|
+
(`<ul>` / `<ol start=N>`) pre-rendered at arm time; each new sibling
|
|
88
|
+
line renders directly into the cache as a tight `<li>…</li>` (GFM
|
|
89
|
+
task-list `[ ] `/`[x] ` supported). Bails on the first blank line
|
|
90
|
+
(loose-list signal), non-marker line, over-edge marker (nested), or
|
|
91
|
+
foreign-family marker — the full path handles those.
|
|
92
|
+
|
|
93
|
+
Measured at 50 KB (best of 7), before → after:
|
|
94
|
+
|
|
95
|
+
| shape | chunk=16 | chunk=256 |
|
|
96
|
+
|-----------------|-------------------|-----------------|
|
|
97
|
+
| `big_blockquote`| 5164 → **22 ms** | 332 → **8.5 ms**|
|
|
98
|
+
| `big_list` | 6141 → **18 ms** | 391 → **7.4 ms**|
|
|
99
|
+
| `big_alert` | 6298 → **28 ms** | 404 → **11 ms** |
|
|
100
|
+
|
|
101
|
+
At 200 KB, `big_list` chunk=256 was extrapolating to ~6.2 s before the
|
|
102
|
+
cache; now **36 ms** (~170×). Every realistic streaming shape now has a
|
|
103
|
+
flat chunk-size curve.
|
|
104
|
+
|
|
105
|
+
Output is byte-identical. Parity gated by `tests/container_cache.rs`
|
|
106
|
+
(blockquote + all five alert kinds, dir_auto, CRLF, lazy continuation,
|
|
107
|
+
multi-paragraph fallback, 400-line stress) and `tests/list_cache.rs` (5
|
|
108
|
+
marker families, ordered with non-default start, dir_auto, CRLF, loose /
|
|
109
|
+
nested / multi-line fallback, 400-item stress).
|
|
110
|
+
|
|
111
|
+
### Documentation
|
|
112
|
+
|
|
113
|
+
- Reworded the "future plugin slot" comments in `renderers/Math.tsx` and
|
|
114
|
+
`renderers/Mermaid.tsx`. The actual extension path is the
|
|
115
|
+
`components.MathBlock` / `components.Mermaid` overrides, which already
|
|
116
|
+
works end-to-end.
|
|
117
|
+
|
|
118
|
+
### Known limitations
|
|
119
|
+
|
|
120
|
+
- The three new caches disarm when `gfmFootnotes` is on, mirroring
|
|
121
|
+
`TableCache` from 0.5.2: cell-level `[^x]` occurrence ids would diverge
|
|
122
|
+
across the cache vs. full-reparse boundary. Footnotes + a long container
|
|
123
|
+
/ table stays on the full O(n²) path — rare combination, may be lifted
|
|
124
|
+
in a later release by tracking per-cache footnote-occ deltas.
|
|
125
|
+
- The blockquote/alert cache covers the *single-paragraph* inner case (the
|
|
126
|
+
realistic LLM shape). A long open container with a multi-block inner
|
|
127
|
+
(lists inside, fenced code inside, etc.) still routes through the full
|
|
128
|
+
path. The bench's `big_blockquote` / `big_alert` are single-paragraph
|
|
129
|
+
shapes — what these caches were built for.
|
|
130
|
+
|
|
131
|
+
## 0.5.2 — 2026-05-28
|
|
132
|
+
|
|
133
|
+
### Performance
|
|
134
|
+
|
|
135
|
+
- **Streaming a long GFM table is now O(n) at every chunk size.** Tables already
|
|
136
|
+
rendered visually incrementally (header at the delimiter row, rows append as
|
|
137
|
+
they arrive) — but `render_table` re-walked every row on every append, so the
|
|
138
|
+
total work was O(n²) once chunks exceeded ~30 bytes (a row). The fix is an
|
|
139
|
+
incremental `TableCache` that mirrors the existing code/math `FenceCache`:
|
|
140
|
+
`<thead>` is pre-rendered once, each newly-complete `<tr>` is folded into the
|
|
141
|
+
cached prefix, and only the trailing partial row is re-rendered each append.
|
|
142
|
+
Output is byte-identical; parity gated by `tests/table_cache.rs` (every chunk
|
|
143
|
+
size 1..=9 × char-by-char against one-shot, with alignments, inline markdown,
|
|
144
|
+
link refs, CRLF fallback, and a 400-row stress case).
|
|
145
|
+
|
|
146
|
+
Measured on a 200 KB table (best of 7 — chunk varies on each row):
|
|
147
|
+
|
|
148
|
+
| chunk | before | after | speedup |
|
|
149
|
+
|------:|---------:|------:|--------:|
|
|
150
|
+
| 16 | 143 ms | 145 ms | ~1× (was already fast) |
|
|
151
|
+
| 64 | 20807 ms | 78 ms | **267×** |
|
|
152
|
+
| 128 | 10414 ms | 54 ms | **193×** |
|
|
153
|
+
| 256 | 5373 ms | 40 ms | **134×** |
|
|
154
|
+
| 512 | 2608 ms | 34 ms | **77×** |
|
|
155
|
+
| 1024 | 1322 ms | 31 ms | **43×** |
|
|
156
|
+
|
|
157
|
+
The pre-fix bench printed only chunks 16 and 256, which hid the regression
|
|
158
|
+
(16 was fine, 256 was the cliff floor). The bench now sweeps 16/64/128/256/
|
|
159
|
+
512/1024 so the next regression in this shape can't slip in unnoticed.
|
|
160
|
+
|
|
161
|
+
Footnotes are the one combination still on the full O(n²) path: the
|
|
162
|
+
cell-level `[^x]` occurrence counter would diverge across the
|
|
163
|
+
cache/full-reparse boundary, so the cache disarms when `gfmFootnotes` is on
|
|
164
|
+
(rare enough to defer to a later release).
|
|
165
|
+
|
|
166
|
+
## 0.5.1 — 2026-05-27
|
|
167
|
+
|
|
168
|
+
### Performance
|
|
169
|
+
|
|
170
|
+
- A document with a very large number of link-reference definitions is now O(n)
|
|
171
|
+
instead of O(n²). The committed reference table was cloned on every append
|
|
172
|
+
(O(refs) per chunk); it's now shared into each render via an `Rc` (O(1)) with a
|
|
173
|
+
two-level lookup (committed, then the uncommitted tail), and folded in place
|
|
174
|
+
via `Rc::make_mut` once the render's clone is dropped. A 235 KB
|
|
175
|
+
reference-definition stream at 16-byte chunks: **~1,395 ms → ~53 ms** (~26×).
|
|
176
|
+
This was believed to be the last remaining O(n²) streaming shape; in fact a
|
|
177
|
+
long open GFM table was still O(n²) (fixed in 0.5.2 — `big_table` at
|
|
178
|
+
chunk=256 went from ~5,400 ms to ~40 ms). Output is unchanged.
|
|
179
|
+
|
|
7
180
|
## 0.5.0 — 2026-05-27
|
|
8
181
|
|
|
9
182
|
### Fixed
|
package/package.json
CHANGED
package/src/renderers/Math.tsx
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { memo } from "react";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Default math block — emits the LaTeX inside a `<div class="math
|
|
5
|
+
* math-display">` (or `<span class="math math-inline">` for inline). flux-md
|
|
6
|
+
* stays zero-dep, so it does not ship KaTeX/MathJax: bring your own typesetter
|
|
7
|
+
* (run it over the rendered `.math` nodes once a block closes), or override
|
|
8
|
+
* this slot via `components.MathBlock` to render the LaTeX yourself.
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
interface Props {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { memo } from "react";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Default mermaid block — renders the diagram source verbatim in a code-like
|
|
5
|
+
* container. flux-md stays zero-dep and does not ship the Mermaid runtime:
|
|
6
|
+
* override this slot via `components.Mermaid` to render to SVG with your own
|
|
7
|
+
* Mermaid build (typically `mermaid.run` over the closed-block source text).
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
interface Props {
|
|
Binary file
|