Skip to content

Releases: AIEPhoenix/ai-react-markdown

v1.6.0

Choose a tag to compare

@github-actions github-actions released this 16 Jul 05:05
  • New config.incrementalParseEnabled (default false, requires blockMemoEnabled): 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 dedicated CrossChunkIncrementalCompare story 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-dependency remend engine: 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), linkMode defaults 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. See docs/content-preprocessors.md.
  • Dev stage telemetry gains an ai-markdown:stage:scan measure (the boundary detector), and parse/transform now report tail-only time when a frame spliced. The BlockMemoComparison benchmark story gains an incremental toggle 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; and docs/benchmark.md ships 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 : desc could 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 a VerificationPlayground with 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

Choose a tag to compare

@github-actions github-actions released this 14 Jul 06:52
  • 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. While streaming is 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. A streaming false→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.render no longer receives the host element: the host is hidden during warm-up, and display: none zeroes 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; suppressErrorRendering additionally keeps mermaid's temp nodes from accumulating in document.body when a draw-phase error slips past parse.
  • Storybook gains a Mantine/MantineAIMarkdown → Streaming story that streams a mermaid-heavy document token by token; content is a control, so arbitrary markdown can be streamed for eyeballing.

v1.5.0

Choose a tag to compare

@github-actions github-actions released this 13 Jul 07:31
  • @ai-react-markdown/mantine now requires @mantine/core and @mantine/code-highlight ^9.0.0 as 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's v8CssVariablesResolver restores the 8.x look), and a React 19.2 floor.
  • If you install @mantine/hooks yourself, note that @mantine/core@9 pins 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 of value: 3) — the rendered DOM is byte-identical.
  • Core's optional katex peer widens to ^0.16.0 || ^0.17.0. The range previously excluded 0.17 (a ^0.16.0 range does not match 0.17 under 0.x semver), so npm users on katex 0.17 hit spurious ERESOLVE conflicts — 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

Choose a tag to compare

@github-actions github-actions released this 13 Jul 06:27
  • The package now ships separate development and production builds behind the development exports condition, and the published files contain no process.env reads at all — consumers without a bundler (import maps, plain <script type="module">, CDN ESM) no longer crash on process 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 (documentId under <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.
  • urlTransform application 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.measure entries (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

Choose a tag to compare

@github-actions github-actions released this 15 Jun 06:46
  • 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

Choose a tag to compare

@AIEPhoenix AIEPhoenix released this 12 Jun 07:53
  • 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/mantine gained an SSR smoke-test suite and a typecheck gate in CI (no runtime changes).

v1.4.5 — Token surface for default variant

Choose a tag to compare

@AIEPhoenix AIEPhoenix released this 21 May 07:31

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

Choose a tag to compare

@AIEPhoenix AIEPhoenix released this 21 May 07:31

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

Choose a tag to compare

@AIEPhoenix AIEPhoenix released this 21 May 07:31

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

Choose a tag to compare

@AIEPhoenix AIEPhoenix released this 21 May 07:31

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.