flux-md 0.3.1 → 0.3.2

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,16 @@ 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.3.2 — 2026-05-27
8
+
9
+ ### Documentation
10
+
11
+ - Rewrote the README to describe flux-md on its own terms and removed all
12
+ references to and comparisons with other libraries. No code changes — the
13
+ published API and behavior are identical to 0.3.1.
14
+ - Fixed the React quick-start example: import `useEffect` and guard the async
15
+ append loop so it can't run after unmount or a stream change.
16
+
7
17
  ## 0.3.1 — 2026-05-27
8
18
 
9
19
  ### Performance
@@ -34,12 +44,11 @@ Notable changes to flux-md. Format based on
34
44
  `<div class="math math-display">`) carrying the LaTeX as text content — bring
35
45
  your own KaTeX (flux-md stays zero-dep) or override `components.MathBlock`
36
46
  (which receives the LaTeX as `text`). Display fences are blank-line tolerant
37
- and stream incrementally. Addresses [Streamdown #522]. Off by default.
47
+ and stream incrementally. Off by default.
38
48
  - **`dirAuto`** — opt-in per-block `dir="auto"` on block-level text elements
39
49
  (`p`, `h1`–`h6`, `blockquote`, `ul`/`ol`/`li`, `table`, alerts, footnotes), so
40
50
  the browser detects each block's direction (RTL/LTR) independently in
41
- mixed-language documents. Code blocks stay LTR. Addresses [Streamdown #509].
42
- Off by default.
51
+ mixed-language documents. Code blocks stay LTR. Off by default.
43
52
 
44
53
  ### Performance
45
54
 
@@ -67,6 +76,3 @@ Notable changes to flux-md. Format based on
67
76
  - Initial public release: zero-dep streaming markdown, Rust→WASM core, one Web
68
77
  Worker per stream, CommonMark 0.31 (652/652) + GFM (tables, strikethrough,
69
78
  task lists, extended autolinks, GitHub alerts, footnotes).
70
-
71
- [Streamdown #522]: https://github.com/vercel/streamdown/issues/522
72
- [Streamdown #509]: https://github.com/vercel/streamdown/issues/509
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Zero-dep streaming markdown for the browser. Rust→WASM core, one Web Worker per stream, incremental parse with speculative closure for mid-stream constructs.
4
4
 
5
- Built because [Streamdown](https://streamdown.ai) crashes the main thread when you run 5 concurrent LLM calls. Same input, this library uses **~8× less peak heap, ~6× less retained memory, and ~2× less main-thread blocking** measured in-browser with `performance.memory`. See [the live demo](https://md.hsingh.app/) for an A/B comparison.
5
+ 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/).
6
6
 
7
7
  ## Install
8
8
 
@@ -37,18 +37,27 @@ client.finalize();
37
37
  In React:
38
38
 
39
39
  ```tsx
40
- import { useMemo } from "react";
40
+ import { useEffect, useMemo } from "react";
41
41
  import { FluxClient, FluxMarkdown } from "flux-md";
42
42
 
43
43
  export function ChatMessage({ stream }: { stream: AsyncIterable<string> }) {
44
44
  const client = useMemo(() => new FluxClient(), []);
45
+
45
46
  useEffect(() => {
47
+ let cancelled = false;
46
48
  (async () => {
47
- for await (const chunk of stream) client.append(chunk);
48
- client.finalize();
49
+ for await (const chunk of stream) {
50
+ if (cancelled) return; // stream changed / unmounted mid-flight
51
+ client.append(chunk);
52
+ }
53
+ if (!cancelled) client.finalize();
49
54
  })();
50
- return () => client.destroy();
55
+ return () => {
56
+ cancelled = true;
57
+ client.destroy();
58
+ };
51
59
  }, [stream]);
60
+
52
61
  return <FluxMarkdown client={client} />;
53
62
  }
54
63
  ```
@@ -57,7 +66,7 @@ Multiple concurrent streams just need multiple clients — each runs in its own
57
66
 
58
67
  ## What it does
59
68
 
60
- | Concern | flux-md | typical react-markdown / Streamdown |
69
+ | Concern | flux-md | conventional main-thread renderer |
61
70
  |---|---|---|
62
71
  | Re-parse on each token | No — only the active tail | Yes, full string |
63
72
  | Where parse runs | Web Worker (off main thread) | Main thread |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flux-md",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
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
  "main": "./src/index.ts",
package/src/hi.ts CHANGED
@@ -5,8 +5,8 @@
5
5
  * languages fall through to plain escaped text. ~6KB minified.
6
6
  *
7
7
  * Highlighting is per-block, runs once when the block closes. We never
8
- * highlight an open (streaming) block that's what gives us the big perf
9
- * win vs Streamdown's per-chunk Shiki invocation.
8
+ * highlight an open (streaming) block, which avoids re-highlighting the same
9
+ * code on every chunk the main perf win for streaming code.
10
10
  */
11
11
 
12
12
  const KEYWORDS_JS = new Set(