Releases: AIEPhoenix/ai-react-markdown
Release list
v1.6.0
- New
config.incrementalParseEnabled(defaultfalse, requiresblockMemoEnabled): during append-only streaming, the renderer freezes the stable prefix of the document at a verified-safe boundary, re-parses only the tail, and splices the previous frame's mdast/hast with the tail's — cutting the per-frame parse/transform cost to roughly the tail's share (84–94% less pipeline stage time measured on the benchmark payloads — exceeding the measurement study's 70–89% parse-only estimate; the freeze boundary covers ~73–87% of realistic LLM output). Block-memo cache keys are position-based, so the two optimizations compose: frozen blocks stay cache hits. - Safety is falsified, not assumed: a splice-equivalence suite asserts the spliced trees are deep-equal (positions included) to a full parse, per streaming frame, across the plugin-permutation catalog and adversarial fixtures (loose lists, rehype-raw swallow containers, open
$$math, late reference/footnote definitions, definition-list term claims, Unicode case-folded labels, CRLF). A Storybook play test additionally pins the live-DOM equality of flag-on vs flag-off streams in a real browser. - Footnotes splice instead of forcing per-frame full parses. Footnote numbering, footer membership/order, and backref ids are whole-document state inside mdast-util-to-hast, so the engine replays the prefix's footnote event sequence (definitions and references ×occurrence, in document order) at the tail head — the tail run rebuilds that state exactly, regenerates the complete document footer, and the footer's positions are rewritten back into document coordinates by a dual rule (injected-def segments per segment, tail-native the ordinary shift). The withDefs benchmark corpus flips from "measures the fallback" to >50% spliced frames; a footnote-heavy browser smoke pins live-DOM equality under StrictMode.
- Cross-chunk (
<AIMarkdownDocuments>) documents splice too. Each chunk's registry-driven phantom-definition suffix is handed to the engine as an always-tail input: the append gate and boundary scan see the chunk's own text alone, so phantom churn (labels arriving/leaving as sibling chunks stream) re-parses only the tail. The reference taint is the correctness backstop — a phantom's definition is never in the chunk's own text, so phantom-resolved refs never enter the frozen prefix. A dedicatedCrossChunkIncrementalComparestory and a coordinated browser smoke ship alongside the arbiter's suffix-churn scenarios. - Sanitize-stripped prefix nodes splice (HTML comments,
<?…?>bogus comments,<script>): their orphaned wrap separators are re-derived by a separator-run alignment model instead of tripping a full-parse fallback (which used to fire on every frame whose frozen prefix held one). - Every frame still re-checks a gate chain and silently falls back to the ordinary full parse when it can't prove safety: non-append content changes, no freeze-safe boundary yet, a container-nested definition that can't be re-injected verbatim, or a hast layout outside the alignment model. SSR always takes the full path. See the new "Incremental parse (prefix-freeze)" section in
docs/streaming-and-performance.md. - New opt-in
createRemendPreprocessor()— streaming tail repair built on Vercel Streamdown's zero-dependencyremendengine: unterminated**bold/`code/~~strike~~/links render styled mid-stream instead of literal. Tree-shakeable (only enters a bundle when imported), no-op on well-formed text (final frames identical),linkModedefaults to text-only under our URL sanitizer, math completion permanently off (the built-in LaTeX preprocessor owns$). Composes freely with block-memo; with incremental parsing, only the frames inside an unterminated construct fall back. Seedocs/content-preprocessors.md. - Dev stage telemetry gains an
ai-markdown:stage:scanmeasure (the boundary detector), andparse/transformnow report tail-only time when a frame spliced. TheBlockMemoComparisonbenchmark story gains anincrementaltoggle on the block-memo side. - Hardened before release by a recall-biased multi-angle review plus a same-day polishing pass: six probe-confirmed detector corners closed (indented-code merges, def-shaped continuation lines, blockquote-nested definitions, mid-line
$$, backtick-bearing fence info strings, html block types 3–5) and every one turned into a permanent arbiter fixture; the v1-era footnote bypass became fence/code-span aware (later removed entirely when v2's injection replay made footnotes splice); the detector gained checkpoint incremental scanning (scan stage 9→4 ms at 4×, 84→13 ms at 16×) with a resume-vs-fresh equivalence property test; the plugin chain was single-sourced (pluginChain.ts) with a directional consistency pin against the experiment record; anddocs/benchmark.mdships recalibrated real-browser numbers (84%/94% pipeline savings, boost p50 32→7.5 ms — unchanged by the hardening). - The v2 splice capabilities went through the same discipline before release: two further adversarial review rounds (8 + 4 independent finder angles, every engine claim probe-tested against the arbiter) closed two probe-confirmed correctness holes — the injection text itself introduced continuation context the detector never modeled (a trailing footnote-def body could swallow indented tail content; a definition-list
: desccould claim the injected block through the compressed join), both neutralized by a sentinel terminator definition appended to every injection, plus a mention-gate so a document literally writing the sentinel label falls back instead of mis-resolving — and one probe-confirmed regression (documents OPENING with a table silently never spliced). Hardening along the way: injection plans are cached and advanced incrementally (removing an O(stream²) per-frame walk), the engine got a throw fence (a mid-frame error can no longer strand a stale scan checkpoint), SSR renders skip the engine's seed scan entirely, and the detector checkpoint no longer retains a copy of the document (~2–3× doc size per mounted instance reclaimed). - Storybook gains a full comparison matrix:
IncrementalParseCompare,BoostCompare(everything-on vs legacy), their process-isolated variants, and aVerificationPlaygroundwith a live freeze-boundary bar — each same-page comparison carries a per-frame DOM-equality verifier (clobber prefixes normalized). - The measurement study behind the design ships as
packages/core/src/experiments/prefixFreeze/(ablation ladder L0–L4 with falsification tables — including why the double-blank-line rule that inspired this feature freezes 0% of typical single-blank LLM output).
1.5.x — Mantine 9
v1.5.1
- Core's block-memo cache no longer freezes stale content when a streaming document contains an unclosed raw-HTML container (
<details>before its</details>arrives — but any container tag qualifies). rehype-raw's HTML parsing reparents every following sibling into the open container, including the synthetic footnote section, while the block's source-level cache identity stays byte-identical; the first swallowed snapshot used to become a permanent cache hit, leaving a duplicated footnote section trapped inside the container and freezing trailing content mid-stream. Raw-HTML blocks now carry a structural digest of their rendered subtree, so the cache invalidates exactly while swallowing is in effect and recovers in one frame once the close tag lands. Markdown-native blocks skip the digest walk entirely — no new per-frame cost on large deterministic subtrees like KaTeX output. @ai-react-markdown/mantine's mermaid renderer is now streaming-aware. Whilestreamingis true, parse failures on truncated code are silent: the raw source shows as a plain code block until the first prefix parses, then the last good SVG stays up and refreshes on each subsequent success — no more "Mermaid Render Error" flashes mid-stream. When streaming ends, one corrective pass runs on the final code; its failure is the only one allowed to replace a rendered diagram. Astreamingfalse→true edge is treated as a new generation (chat "regenerate" reuses the same component instance) and resets the warm-up state, so a new stream never shows the previous generation's stale diagram or error tab. Static (non-streaming) rendering keeps the original conservative rule: a rendered diagram is never clobbered by a later transient failure.mermaid.renderno longer receives the host element: the host is hidden during warm-up, anddisplay: nonezeroes mermaid's getBBox text measurement — one-shot static renders came out as ~16px SVGs. Rendering through mermaid's own body-temp path decouples measurement from container visibility;suppressErrorRenderingadditionally keeps mermaid's temp nodes from accumulating indocument.bodywhen a draw-phase error slips pastparse.- Storybook gains a
Mantine/MantineAIMarkdown → Streamingstory that streams a mermaid-heavy document token by token;contentis a control, so arbitrary markdown can be streamed for eyeballing.
v1.5.0
@ai-react-markdown/mantinenow requires@mantine/coreand@mantine/code-highlight^9.0.0as peers (tested against 9.4.1). No API changes on our side — the package's surface survives every Mantine 9 breaking change untouched. Consumers inherit Mantine 9's defaults:md(8px) default radius, solid light-variant colors (Mantine'sv8CssVariablesResolverrestores the 8.x look), and a React 19.2 floor.- If you install
@mantine/hooksyourself, note that@mantine/core@9pins its hooks peer to the exact matching version — keep the three@mantine/*packages on one version. - Core is unchanged in behavior. One internal type adaptation for
@types/hast≥3.0.5: the aggregated footnote footer's<li>numbering is now stored as a string in hast (value: "3"instead ofvalue: 3) — the rendered DOM is byte-identical. - Core's optional
katexpeer widens to^0.16.0 || ^0.17.0. The range previously excluded 0.17 (a^0.16.0range does not match 0.17 under 0.x semver), so npm users on katex 0.17 hit spuriousERESOLVEconflicts — even though the library is continuously tested against 0.17.
1.4.x — Customization surface hardening
The 1.4 line opened up the customization surface (URL sanitization, document namespacing, design tokens) and put guardrails around it so consumers can extend safely.
v1.4.9
- The package now ships separate development and production builds behind the
developmentexports condition, and the published files contain noprocess.envreads at all — consumers without a bundler (import maps, plain<script type="module">, CDN ESM) no longer crash onprocess is not defined. A post-build assertion keeps the dist permanently free of env reads. See the README's "Development vs production builds" section for the SSR and Jest footguns that come with conditional exports. - Streaming no longer pays a second full markdown parse per token. Standalone chunks skip the definition-label scan entirely; coordinated chunks (
documentIdunder<AIMarkdownDocuments>) gate it behind an append-aware scanner that re-scans only the region since the last blank line, and only when a line-start[could introduce a new definition. The label set keeps its object identity when unchanged, so per-token re-registration and downstream memo invalidation stop too. urlTransformapplication is now convergent: original URLs are stashed on first transform and every pass recomputes from the original, so a memoized (re-entered) hast tree can never be double-transformed. Internal defensive tree clones are skipped when the tree is caller-owned — the common path allocates nothing.- Development builds emit per-stage
performance.measureentries (ai-markdown:stage:parse|transform|build|render) for pipeline profiling in the DevTools Performance panel; production builds compile the entire gate away.
v1.4.8
- Pushing a
v*tag now also publishes the matching GitHub release, with notes lifted from this file's section for that version (falling back to auto-generated notes). The npm publish and the GitHub release are a single CI step away from one tag push. No library runtime changes.
v1.4.7
- A footnote id with malformed percent-encoding now logs a development-mode warning instead of degrading silently (the aggregated footer renders an empty entry for that label — previously with no signal as to why).
- First release published via npm trusted publishing (OIDC): both tarballs carry provenance attestation linking them to the exact source commit and CI run. No npm token exists anywhere in the pipeline.
@ai-react-markdown/mantinegained an SSR smoke-test suite and a typecheck gate in CI (no runtime changes).
v1.4.5 — Token surface for default variant
Customization tokens for the default typography variant: spacing, font-size, and heading tokens now consume --aim-font-size-root, so changing the fontSize prop proportionally scales every dimension. New tokens: --aim-font-weight-strong (shared by all headings and <th>, default 700) and --aim-katex-font-size (defaults to --aim-font-size-root). See docs/design-tokens.md for the full token surface.
v1.4.4 — Cross-chunk URL XSS sealed
Cross-chunk link/image references now run a second per-attribute sanitization pass at render time in the consuming chunk. Previously a permissive urlTransform in one chunk could leak unsafe schemes into another chunk that defined them, bypassing the consuming chunk's policy. Now every consumer applies its own gates independently. Also: Registry public type narrowed to read-only surface (mutators are no longer part of the public type); sanitizeSchema public type tightening.
v1.4.3 — Document urlTransform + sanitizeSchema
Comprehensive JSDoc on both props: reference-stability requirements, composition with defaultUrlTransform, regex-escaping for scheme names, and the asymmetric stability model (function identity vs deep-equal data).
v1.4.2 — documentId shortening
documentId values longer than 16 chars (UUIDs, nanoids) are hashed via MurmurHash3 → Base62 down to ≤6 chars inside the rendered id="…" / href="#…" prefix only. State (state.documentId) and registry keying use the raw value, so deep linking and useDocumentRegistry(documentId) are unaffected — pure rendered-HTML compactness win.