brookmd 0.22.2 → 0.23.1
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 +42 -0
- package/README.md +21 -1
- package/dist/client.js +14 -2
- package/dist/types-core.d.ts +22 -1
- package/dist/wasm/README.md +11 -2
- package/dist/wasm/brook_md_core.d.ts +10 -0
- package/dist/wasm/brook_md_core.js +12 -0
- package/dist/wasm/brook_md_core_bg.wasm +0 -0
- package/dist/wasm/brook_md_core_bg.wasm.d.ts +1 -0
- package/dist/worker.js +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,48 @@ Notable changes to brookmd (formerly `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.23.1 — 2026-07-21
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Documentation only: the README now covers wire delta mode (the 0.23.0
|
|
12
|
+
headline) — measured numbers, the automatic worker↔client behavior, and the
|
|
13
|
+
raw-boundary opt-in — plus refreshed platform-status notes across the
|
|
14
|
+
repository. No code changes; behavior identical to 0.23.0.
|
|
15
|
+
|
|
16
|
+
## 0.23.0 — 2026-07-21
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **Wire delta mode — the streaming re-emit floor is retired** (wire contract
|
|
21
|
+
**v1.2.0**, additive). Previously, every `append` re-emitted each open
|
|
22
|
+
block's full HTML across the WASM→worker→main-thread boundary, making total
|
|
23
|
+
emitted bytes O(n²/chunk) for a block that grows across many chunks — the
|
|
24
|
+
documented "re-emit floor". Now the parser can emit a **verified splice**
|
|
25
|
+
(`html_delta: { keep_bytes, keep_units, append }`) against the block's
|
|
26
|
+
previous emit instead; splices are established by byte comparison, never by
|
|
27
|
+
structural inference, so reconstruction is byte-exact by construction.
|
|
28
|
+
|
|
29
|
+
Measured at 200 KB / 256-byte chunks: a streaming list's total patch JSON
|
|
30
|
+
drops **119.6 MB → 0.78 MB (153× less)** with **2.8× faster** end-to-end
|
|
31
|
+
parse+serialize; an unclosed code fence **80.1 MB → 0.58 MB (137×, 4.9×
|
|
32
|
+
faster)**. Fast-committing shapes are unchanged (no regression). The curve
|
|
33
|
+
is now linear, so the gap keeps widening with document size.
|
|
34
|
+
|
|
35
|
+
- The npm package enables this **automatically and invisibly** — the worker
|
|
36
|
+
opts in, and the client reconstructs full blocks before anything else sees
|
|
37
|
+
them. No API change; `Block.html` is always complete.
|
|
38
|
+
- `brookmd-react-native` does the same across the JSI boundary.
|
|
39
|
+
- Raw-boundary consumers (`BrookParser`, native `BrookSession`, C ABI): the
|
|
40
|
+
mode is **opt-in** (`setWireDelta(true)` / `wire_delta: true`) and the
|
|
41
|
+
default wire is byte-identical to contract v1.1.0. Enabling it obliges you
|
|
42
|
+
to reconstruct per [WIRE.md §11](../../crates/brookmd-core/WIRE.md).
|
|
43
|
+
- Pinned by: reconstruction-parity suites (Rust corpus × chunkings, dual
|
|
44
|
+
UTF-8/UTF-16 offset verification), wire goldens in all five language
|
|
45
|
+
surfaces (core, FFI, C-ABI, Kotlin, Swift), a fuzzer extension asserting
|
|
46
|
+
per-patch reconstruction on arbitrary inputs, and a new scaling-gate test
|
|
47
|
+
that *fails CI* if delta-mode emitted bytes regress from linear.
|
|
48
|
+
|
|
7
49
|
## 0.22.2 — 2026-07-21
|
|
8
50
|
|
|
9
51
|
### Performance
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Zero-dep streaming markdown for the browser. Rust→WASM core, one Web Worker pe
|
|
|
4
4
|
|
|
5
5
|
Drop in a streaming-aware renderer — **React, Vue, Svelte, Solid, a framework-agnostic `<brook-markdown>` Web Component, or the vanilla DOM mount** — wire each LLM stream to a `BrookClient`, and the markdown renders incrementally off the main thread, block by block, with stable identities so unchanged blocks never re-reconcile.
|
|
6
6
|
|
|
7
|
-
Parsing runs entirely **off the main thread** — each stream gets its own pooled Web Worker, so many concurrent LLM responses render without contending for the UI thread. On each token the parser re-parses only the **active tail**, not the whole document, and heavy renderers (syntax highlighting, math, mermaid) are **deferred until a block closes**. The result is low retained memory and a main thread that stays responsive while streaming. See [the live demo](https://md.hsingh.app/).
|
|
7
|
+
Parsing runs entirely **off the main thread** — each stream gets its own pooled Web Worker, so many concurrent LLM responses render without contending for the UI thread. On each token the parser re-parses only the **active tail**, not the whole document; patches cross the worker boundary as **verified splices** (not full re-sends, so emitted bytes stay O(n) even for one giant growing block); and heavy renderers (syntax highlighting, math, mermaid) are **deferred until a block closes**. The result is low retained memory and a main thread that stays responsive while streaming. See [the live demo](https://md.hsingh.app/).
|
|
8
8
|
|
|
9
9
|
> **Beyond the browser:** the same Rust core also powers experimental React
|
|
10
10
|
> Native, Swift (iOS/macOS), Kotlin/Android, Flutter, and C-ABI bindings —
|
|
@@ -1044,6 +1044,26 @@ Headline numbers are not durable across machines, but the curve is: chunk size
|
|
|
1044
1044
|
shouldn't change the order of magnitude for any shape. If you hit one that does,
|
|
1045
1045
|
file an issue with the input and chunking — that's the next bench scenario.
|
|
1046
1046
|
|
|
1047
|
+
### Wire delta mode (automatic)
|
|
1048
|
+
|
|
1049
|
+
Parse work was already O(n), but until 0.23.0 the **bytes crossing the
|
|
1050
|
+
worker→main-thread boundary** were not: every append re-emitted the open
|
|
1051
|
+
block's full HTML, O(n²/chunk) total for one block that grows across many
|
|
1052
|
+
chunks (a long streaming list, a big code fence). Since 0.23.0 the parser
|
|
1053
|
+
emits **verified splices** instead — `{keep, append}` deltas against the
|
|
1054
|
+
block's previous emit, established by byte comparison so reconstruction is
|
|
1055
|
+
byte-exact by construction — and the client reassembles full blocks before
|
|
1056
|
+
anything else sees them. Zero API change; `Block.html` is always complete.
|
|
1057
|
+
|
|
1058
|
+
Measured at a 200 KB document in 256-byte chunks, a streaming list's total
|
|
1059
|
+
patch traffic drops from **119.6 MB to 0.78 MB (153× less, 2.8× faster
|
|
1060
|
+
end-to-end)**; an unclosed code fence from **80.1 MB to 0.58 MB (137×, 4.9×
|
|
1061
|
+
faster)**. Fast-committing prose is unchanged. Emitted bytes are now gated
|
|
1062
|
+
linear in CI alongside the parse-work counters. Raw-boundary consumers (the
|
|
1063
|
+
WASM `BrookParser`, native bindings, C ABI) keep byte-identical v1 wire by
|
|
1064
|
+
default and can opt in with `setWireDelta(true)` — see
|
|
1065
|
+
[`WIRE.md` §11](https://github.com/siinghd/brookmd/blob/main/crates/brookmd-core/WIRE.md).
|
|
1066
|
+
|
|
1047
1067
|
## Security
|
|
1048
1068
|
|
|
1049
1069
|
brookmd is XSS-safe by default — its HTML output is meant to be injected via
|
package/dist/client.js
CHANGED
|
@@ -10,7 +10,19 @@ function applyPatch(store, patch) {
|
|
|
10
10
|
if (!store.committed.has(b.id)) store.committedOrder.push(b.id);
|
|
11
11
|
store.committed.set(b.id, b);
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
const active = new Array(patch.active.length);
|
|
14
|
+
for (let i = 0; i < patch.active.length; i++) {
|
|
15
|
+
const entry = patch.active[i];
|
|
16
|
+
if ("html_delta" in entry) {
|
|
17
|
+
const { html_delta, ...rest } = entry;
|
|
18
|
+
const prev = store.active.find((b) => b.id === entry.id);
|
|
19
|
+
if (!prev) throw new Error(`brookmd: html_delta for block ${entry.id} without a base`);
|
|
20
|
+
active[i] = { ...rest, html: prev.html.slice(0, html_delta.keep_units) + html_delta.append };
|
|
21
|
+
} else {
|
|
22
|
+
active[i] = entry;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
store.active = active;
|
|
14
26
|
const next = new Array(store.committedOrder.length + store.active.length);
|
|
15
27
|
for (let i = 0; i < store.committedOrder.length; i++) {
|
|
16
28
|
next[i] = store.committed.get(store.committedOrder[i]);
|
|
@@ -679,13 +691,13 @@ class BrookClient {
|
|
|
679
691
|
let patch;
|
|
680
692
|
try {
|
|
681
693
|
patch = JSON.parse(msg.patch);
|
|
694
|
+
applyPatch(this.store, patch);
|
|
682
695
|
} catch (e) {
|
|
683
696
|
const message = e instanceof Error ? e.message : String(e);
|
|
684
697
|
if (this.onError) this.onError({ message: `brookmd: malformed patch (${message})` });
|
|
685
698
|
else console.error("brookmd: malformed patch:", message);
|
|
686
699
|
break;
|
|
687
700
|
}
|
|
688
|
-
applyPatch(this.store, patch);
|
|
689
701
|
if (msg.final === true && this.staleSnapshot) {
|
|
690
702
|
this.staleTrimmed = true;
|
|
691
703
|
this.mergeCache = null;
|
package/dist/types-core.d.ts
CHANGED
|
@@ -164,9 +164,30 @@ export interface Block {
|
|
|
164
164
|
open: boolean;
|
|
165
165
|
speculative: boolean;
|
|
166
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Wire delta mode (WIRE.md §11): the splice an active block carries in place
|
|
169
|
+
* of `html` when it was already emitted in the previous patch. Reconstruct
|
|
170
|
+
* with `prev.slice(0, keep_units) + append` (JS strings are UTF-16, so
|
|
171
|
+
* `keep_units` is the right offset here; `keep_bytes` is the same prefix for
|
|
172
|
+
* byte-oriented consumers).
|
|
173
|
+
*/
|
|
174
|
+
export interface WireHtmlDelta {
|
|
175
|
+
keep_bytes: number;
|
|
176
|
+
keep_units: number;
|
|
177
|
+
append: string;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* An `active` array entry as serialized: a full {@link Block}, or (wire delta
|
|
181
|
+
* mode only) every Block field except `html` plus an `html_delta` splice.
|
|
182
|
+
* {@link applyPatch} reconstructs deltas into full Blocks — nothing past the
|
|
183
|
+
* store ever sees this union.
|
|
184
|
+
*/
|
|
185
|
+
export type WireActiveBlock = Block | (Omit<Block, "html"> & {
|
|
186
|
+
html_delta: WireHtmlDelta;
|
|
187
|
+
});
|
|
167
188
|
export interface Patch {
|
|
168
189
|
newly_committed: Block[];
|
|
169
|
-
active:
|
|
190
|
+
active: WireActiveBlock[];
|
|
170
191
|
}
|
|
171
192
|
/**
|
|
172
193
|
* Per-block render-churn sample passed to an {@link RenderMetricsHook}. Lets you
|
package/dist/wasm/README.md
CHANGED
|
@@ -52,15 +52,24 @@ entirely:
|
|
|
52
52
|
|
|
53
53
|
```toml
|
|
54
54
|
[dependencies]
|
|
55
|
-
brookmd-core = { version = "0.
|
|
55
|
+
brookmd-core = { version = "0.23", default-features = false }
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
## Wire format
|
|
59
59
|
|
|
60
60
|
Blocks and patches serialize to a stable, language-agnostic JSON wire format —
|
|
61
|
-
see [WIRE.md](WIRE.md) (wire contract v1.
|
|
61
|
+
see [WIRE.md](WIRE.md) (wire contract v1.2.0). Native consumers can produce the
|
|
62
62
|
same bytes as the WASM/JS boundary via `wire::patch_to_json` / `wire::blocks_to_json`.
|
|
63
63
|
|
|
64
|
+
Contract v1.2.0 adds the opt-in **wire delta mode**
|
|
65
|
+
(`StreamParser::set_wire_delta`): active blocks re-emitted across appends
|
|
66
|
+
serialize as verified `html_delta` splices against their previous emit instead
|
|
67
|
+
of full `html`, making total emitted bytes O(n) for a block that grows across
|
|
68
|
+
many appends (WIRE.md §11). Off by default — the default wire stays
|
|
69
|
+
byte-identical to v1.1.0. A consumer that enables it reconstructs
|
|
70
|
+
`prev[..keep] + append` per patch; the npm and React Native packages do this
|
|
71
|
+
transparently.
|
|
72
|
+
|
|
64
73
|
## Links
|
|
65
74
|
|
|
66
75
|
- npm package: <https://www.npmjs.com/package/brookmd>
|
|
@@ -112,6 +112,15 @@ export class BrookParser {
|
|
|
112
112
|
* when rendering untrusted input — bypasses XSS protection.
|
|
113
113
|
*/
|
|
114
114
|
setUnsafeHtml(on: boolean): void;
|
|
115
|
+
/**
|
|
116
|
+
* Opt-in wire delta mode (WIRE.md §11): active blocks re-emitted across
|
|
117
|
+
* appends serialize as verified `html_delta` splices against their previous
|
|
118
|
+
* emit instead of full `html`, making total emitted bytes O(n) for a block
|
|
119
|
+
* that grows across many appends. Off by default (wire byte-identical to
|
|
120
|
+
* pre-delta releases). A consumer that enables this must reconstruct
|
|
121
|
+
* active `html` per WIRE.md §11 — the npm package's client does.
|
|
122
|
+
*/
|
|
123
|
+
setWireDelta(on: boolean): void;
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
|
@@ -137,6 +146,7 @@ export interface InitOutput {
|
|
|
137
146
|
readonly brookparser_setHtmlSanitize: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
|
|
138
147
|
readonly brookparser_setInlineComponentTags: (a: number, b: number, c: number) => void;
|
|
139
148
|
readonly brookparser_setUnsafeHtml: (a: number, b: number) => void;
|
|
149
|
+
readonly brookparser_setWireDelta: (a: number, b: number) => void;
|
|
140
150
|
readonly __wbindgen_export: (a: number, b: number) => number;
|
|
141
151
|
readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
|
142
152
|
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
|
|
@@ -255,6 +255,18 @@ export class BrookParser {
|
|
|
255
255
|
setUnsafeHtml(on) {
|
|
256
256
|
wasm.brookparser_setUnsafeHtml(this.__wbg_ptr, on);
|
|
257
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Opt-in wire delta mode (WIRE.md §11): active blocks re-emitted across
|
|
260
|
+
* appends serialize as verified `html_delta` splices against their previous
|
|
261
|
+
* emit instead of full `html`, making total emitted bytes O(n) for a block
|
|
262
|
+
* that grows across many appends. Off by default (wire byte-identical to
|
|
263
|
+
* pre-delta releases). A consumer that enables this must reconstruct
|
|
264
|
+
* active `html` per WIRE.md §11 — the npm package's client does.
|
|
265
|
+
* @param {boolean} on
|
|
266
|
+
*/
|
|
267
|
+
setWireDelta(on) {
|
|
268
|
+
wasm.brookparser_setWireDelta(this.__wbg_ptr, on);
|
|
269
|
+
}
|
|
258
270
|
}
|
|
259
271
|
if (Symbol.dispose) BrookParser.prototype[Symbol.dispose] = BrookParser.prototype.free;
|
|
260
272
|
function __wbg_get_imports() {
|
|
Binary file
|
|
@@ -20,6 +20,7 @@ export const brookparser_setGfmTagfilter: (a: number, b: number) => void;
|
|
|
20
20
|
export const brookparser_setHtmlSanitize: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
|
|
21
21
|
export const brookparser_setInlineComponentTags: (a: number, b: number, c: number) => void;
|
|
22
22
|
export const brookparser_setUnsafeHtml: (a: number, b: number) => void;
|
|
23
|
+
export const brookparser_setWireDelta: (a: number, b: number) => void;
|
|
23
24
|
export const __wbindgen_export: (a: number, b: number) => number;
|
|
24
25
|
export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
|
25
26
|
export const __wbindgen_add_to_stack_pointer: (a: number) => number;
|
package/dist/worker.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brookmd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.1",
|
|
4
4
|
"description": "Zero-dep streaming markdown for the browser. Rust→WASM core, Web Worker per stream, incremental parse with speculative closure.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": ["./dist/worker.js", "./dist/styles.css"],
|