brookmd 0.22.2 → 0.23.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,39 @@ 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.0 — 2026-07-21
8
+
9
+ ### Added
10
+
11
+ - **Wire delta mode — the streaming re-emit floor is retired** (wire contract
12
+ **v1.2.0**, additive). Previously, every `append` re-emitted each open
13
+ block's full HTML across the WASM→worker→main-thread boundary, making total
14
+ emitted bytes O(n²/chunk) for a block that grows across many chunks — the
15
+ documented "re-emit floor". Now the parser can emit a **verified splice**
16
+ (`html_delta: { keep_bytes, keep_units, append }`) against the block's
17
+ previous emit instead; splices are established by byte comparison, never by
18
+ structural inference, so reconstruction is byte-exact by construction.
19
+
20
+ Measured at 200 KB / 256-byte chunks: a streaming list's total patch JSON
21
+ drops **119.6 MB → 0.78 MB (153× less)** with **2.8× faster** end-to-end
22
+ parse+serialize; an unclosed code fence **80.1 MB → 0.58 MB (137×, 4.9×
23
+ faster)**. Fast-committing shapes are unchanged (no regression). The curve
24
+ is now linear, so the gap keeps widening with document size.
25
+
26
+ - The npm package enables this **automatically and invisibly** — the worker
27
+ opts in, and the client reconstructs full blocks before anything else sees
28
+ them. No API change; `Block.html` is always complete.
29
+ - `brookmd-react-native` does the same across the JSI boundary.
30
+ - Raw-boundary consumers (`BrookParser`, native `BrookSession`, C ABI): the
31
+ mode is **opt-in** (`setWireDelta(true)` / `wire_delta: true`) and the
32
+ default wire is byte-identical to contract v1.1.0. Enabling it obliges you
33
+ to reconstruct per [WIRE.md §11](../../crates/brookmd-core/WIRE.md).
34
+ - Pinned by: reconstruction-parity suites (Rust corpus × chunkings, dual
35
+ UTF-8/UTF-16 offset verification), wire goldens in all five language
36
+ surfaces (core, FFI, C-ABI, Kotlin, Swift), a fuzzer extension asserting
37
+ per-patch reconstruction on arbitrary inputs, and a new scaling-gate test
38
+ that *fails CI* if delta-mode emitted bytes regress from linear.
39
+
7
40
  ## 0.22.2 — 2026-07-21
8
41
 
9
42
  ### Performance
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
- store.active = patch.active;
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;
@@ -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: Block[];
190
+ active: WireActiveBlock[];
170
191
  }
171
192
  /**
172
193
  * Per-block render-churn sample passed to an {@link RenderMetricsHook}. Lets you
@@ -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
@@ -25,6 +25,7 @@ const core = new WorkerCore({
25
25
  c?.dropHtmlTags ?? []
26
26
  );
27
27
  p.setBlockData(c?.blockData ?? false);
28
+ p.setWireDelta(true);
28
29
  return p;
29
30
  },
30
31
  post: (msg) => ctx.postMessage(msg),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brookmd",
3
- "version": "0.22.2",
3
+ "version": "0.23.0",
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"],