brookmd 0.25.0 → 0.25.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 CHANGED
@@ -4,6 +4,77 @@ 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.25.1 — 2026-07-27
8
+
9
+ A correction release. 0.25.0 shipped one user-visible regression and a dev-gate
10
+ that failed open in production; both are fixed here, along with a pre-existing
11
+ memory leak on the opt-in `childMemo` path. Upgrade from 0.25.0.
12
+
13
+ ### Fixed
14
+
15
+ - **WITHDRAWN: the streaming component-tag deferral from 0.25.0.** That change
16
+ gated component rendering on `open_tail`, which a container propagates
17
+ UNCHANGED to every sub-block of every list item. A component tag alone in a
18
+ **non-last list item** — whose one-line body carries no trailing newline of its
19
+ own — therefore never satisfied the gate again, and rendered as the literal
20
+ text `<Thinking>` for the entire rest of the stream instead of mounting the
21
+ consumer's component. Measured 201/201 ticks affected in a 200-item list, and
22
+ the deferral bought exactly one tick across the three shapes it was written
23
+ for. `brookmd-core` is reverted to its pre-0.24.0 behavior here (0.24.1); the
24
+ convergence property 0.25.0 claimed is withdrawn with it. The one-tick issue it
25
+ was chasing is already contained by the per-block error boundary.
26
+ - **Dev-only warnings ran in production.** The gate read `globalThis.process?.env`,
27
+ which is a different member path from the `process.env.NODE_ENV` free
28
+ identifier that esbuild / Vite / Next substitute — so in every bundled browser
29
+ production build it evaluated to "development", printed to real users, and
30
+ prevented dead-code elimination of the message strings. Every gate is now at
31
+ the call site in the literal form bundlers fold. Verified against a real Vite
32
+ build: 11 dev-warning strings survived a production bundle before, 0 now.
33
+ (Note the trade: in a realm with no `process` at all — an unbundled CDN
34
+ consumer — these six development warnings no longer fire. Error reporting via
35
+ `onBlockError` / `console.error` is NOT gated and is unaffected.)
36
+ - **`childMemo` retained every intermediate tree on a single-element block**
37
+ (pre-existing, opt-in path). The cache key embeds the segment text, so a block
38
+ whose HTML is ONE top-level element — which is every core-emitted block kind —
39
+ could never hit: each patch wrote an entry that was never read again, and
40
+ `CHILD_MEMO_CAP` bounds entry count, not bytes. Peak RSS grew 154 MB → 1.65 GB
41
+ over 800 patches of a streaming table. Such blocks now take the ordinary
42
+ whole-block walk.
43
+
44
+ ### Changed
45
+
46
+ - Shipped bundles are smaller than 0.25.0: gzipped, minified, production —
47
+ `index` 14,232 → 13,407 B, `react` 14,185 → 13,365 B, `client` 4,651 → 4,283 B.
48
+ The boundary's `console.error` keeps everything actionable in production (block
49
+ id, kind, override keys, HTML excerpt, the error) and moves only the
50
+ explanatory prose behind the dev gate.
51
+
52
+ ## brookmd-react-native 0.1.5 — 2026-07-27
53
+
54
+ ### Fixed
55
+
56
+ - Vendored native binaries rebuilt against `brookmd-core` 0.24.1, so the on-device
57
+ parser no longer carries the component-tag deferral regression withdrawn in
58
+ brookmd 0.25.1 (a component tag alone in a non-last list item rendered as
59
+ literal text for the rest of the stream). No JS changes.
60
+
61
+ ## brookmd-react-native 0.1.4 — 2026-07-26
62
+
63
+ ### Fixed
64
+
65
+ - **The native transport shim routes worker listeners by event type.** brookmd
66
+ 0.24.0 taught `BrookPool` to listen on `error` / `messageerror` (the browser's
67
+ out-of-band worker-failure channels). `NativeWorker.addEventListener` ignored
68
+ its `type` argument and registered every listener in one set, so the pool's
69
+ fatal handler received the ordinary `ready` / `patch` envelopes and read the
70
+ first as `brookmd worker failed to load`, killing every stream before it
71
+ started. Neither channel can fire for an in-process shim — there is no script
72
+ URL to fetch and no structured-clone step — so they are now accepted and never
73
+ fired. This was latent: the package pinned `brookmd` to `^0.23.0`, which
74
+ predates those listeners, so it only surfaced when the range moved.
75
+ - `brookmd` dependency range corrected `^0.23.0` → `^0.25.0`; vendored native
76
+ binaries rebuilt against `brookmd-core` 0.24.0.
77
+
7
78
  ## 0.25.0 — 2026-07-26
8
79
 
9
80
  Fixes a crash class where a `components` override could be invoked with the
package/dist/client.js CHANGED
@@ -29,10 +29,12 @@ function applyPatch(store, patch) {
29
29
  for (let i = 0; i < store.committedOrder.length; i++) {
30
30
  const b = store.committed.get(store.committedOrder[i]);
31
31
  if (b === void 0) {
32
- warnOnce(
33
- "orphan-committed",
34
- `brookmd: committed block ${store.committedOrder[i]} is missing from the store and was dropped. This is a bug in brookmd \u2014 please report it.`
35
- );
32
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
33
+ warnOnce(
34
+ "orphan-committed",
35
+ `brookmd: committed block ${store.committedOrder[i]} is missing from the store and was dropped. This is a bug in brookmd \u2014 please report it.`
36
+ );
37
+ }
36
38
  continue;
37
39
  }
38
40
  next[w++] = b;
@@ -40,7 +42,9 @@ function applyPatch(store, patch) {
40
42
  for (let i = 0; i < store.active.length; i++) {
41
43
  const b = store.active[i];
42
44
  if (b === void 0) {
43
- warnOnce("orphan-active", `brookmd: active block at index ${i} is undefined and was dropped.`);
45
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
46
+ warnOnce("orphan-active", `brookmd: active block at index ${i} is undefined and was dropped.`);
47
+ }
44
48
  continue;
45
49
  }
46
50
  next[w++] = b;
@@ -476,10 +480,12 @@ class BrookClient {
476
480
  if (this.failedError === null) return;
477
481
  this.pendingRebind = false;
478
482
  if (this.store.snapshot.length === 0 && !this.staleSnapshot) return;
479
- warnOnce(
480
- "rebind-reset",
481
- "brookmd: the parser was rebuilt on a new worker after a fatal failure, so the document restarted. Blocks rendered before the failure were dropped because the fresh parser renumbers from zero. Enable `recovery` (the default) to re-feed and heal invisibly instead."
482
- );
483
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
484
+ warnOnce(
485
+ "rebind-reset",
486
+ "brookmd: the parser was rebuilt on a new worker after a fatal failure, so the document restarted. Blocks rendered before the failure were dropped because the fresh parser renumbers from zero. Enable `recovery` (the default) to re-feed and heal invisibly instead."
487
+ );
488
+ }
483
489
  this.resetParser();
484
490
  }
485
491
  get ready() {
@@ -816,7 +822,9 @@ class BrookClient {
816
822
  view[i] = { ...nb, id: this.idNamespace + nb.id };
817
823
  }
818
824
  if (dropped) {
819
- warnOnce("merge-hole", "brookmd: the stale-merge produced an empty position \u2014 compacting.");
825
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
826
+ warnOnce("merge-hole", "brookmd: the stale-merge produced an empty position \u2014 compacting.");
827
+ }
820
828
  this.mergeDropped = true;
821
829
  return view.filter((b) => b !== void 0);
822
830
  }
@@ -238,7 +238,7 @@ function pushDecoratedText(out, text, decorators, ancestors, keyBase) {
238
238
  function resolveTagType(tag, components) {
239
239
  const c = tag.charCodeAt(0);
240
240
  if (c >= 65 && c <= 90 && BLOCK_KIND_KEYS.has(tag)) {
241
- if (components[tag] !== void 0) {
241
+ if (components[tag] !== void 0 && typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
242
242
  warnOnce(
243
243
  "kind-key-as-tag:" + tag,
244
244
  `brookmd: raw <${tag}> in the rendered HTML collides with the block-kind override key "${tag}". A block-kind override receives \`BlockComponentProps\` (with \`block\`), which an inline element cannot supply, so the raw tag is rendered as a plain element instead. Rename the raw tag or the override key.`
@@ -335,6 +335,7 @@ function htmlToReact(html, components, childMemoMap, opts) {
335
335
  const ctx = opts && (opts.decorators || opts.urlTransform) ? { decorators: opts.decorators, urlTransform: opts.urlTransform } : void 0;
336
336
  if (!childMemoMap) return nodesToReact(parseTrustedHtml(html), components, "", ctx, []);
337
337
  const segs = topLevelSegments(html);
338
+ if (segs.length < 2) return nodesToReact(parseTrustedHtml(html), components, "", ctx, []);
338
339
  const out = [];
339
340
  for (let idx = 0; idx < segs.length; idx++) {
340
341
  const seg = segs[idx];
package/dist/react.js CHANGED
@@ -19,10 +19,12 @@ import { warnOnce } from "./warn.js";
19
19
  const NO_DEFER_BLOCKS = [];
20
20
  const EMPTY_KEYS = [];
21
21
  function skipBadBlock(index) {
22
- warnOnce(
23
- "bad-block",
24
- `brookmd: snapshot position ${index} has no block kind and was skipped. This indicates a corrupted block store \u2014 please report it.`
25
- );
22
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
23
+ warnOnce(
24
+ "bad-block",
25
+ `brookmd: snapshot position ${index} has no block kind and was skipped. This indicates a corrupted block store \u2014 please report it.`
26
+ );
27
+ }
26
28
  return null;
27
29
  }
28
30
  const warnedUnstable = /* @__PURE__ */ new Set();
@@ -32,8 +34,7 @@ function useUnstablePropWarning(name, value) {
32
34
  const prevDefined = ref.current !== void 0 && ref.current !== null;
33
35
  const nextDefined = value !== void 0 && value !== null;
34
36
  ref.current = value;
35
- const env = globalThis.process?.env;
36
- if (prevDefined && nextDefined && !warnedUnstable.has(name) && (!env || env.NODE_ENV !== "production")) {
37
+ if (prevDefined && nextDefined && !warnedUnstable.has(name) && typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
37
38
  warnedUnstable.add(name);
38
39
  console.warn(
39
40
  `<BrookMarkdown>: the \`${name}\` prop changed identity between renders. Hoist it to module scope or wrap it in useMemo \u2014 a fresh identity each render busts the per-block memo and re-parses every block on every patch.`
@@ -625,8 +626,9 @@ class BlockBoundary extends Component {
625
626
  this.props.onBlockError(error, info);
626
627
  return;
627
628
  }
629
+ const hint = typeof process !== "undefined" && process.env.NODE_ENV !== "production" ? ` This is almost always a \`components\` override throwing. If it is registered for a block-kind or component-tag key, note that the SAME key is also dispatched for a matching element nested inside a block's HTML \u2014 that call gets attributes + children only, with no \`block\` prop. Guard with \`if (!block) return <>{children}</>\`.` : "";
628
630
  console.error(
629
- `brookmd: block ${info.blockId} (${info.kind}) failed to render and was skipped. This is almost always a \`components\` override throwing. If the override is registered for a block-kind or component-tag key, note that the SAME key is also dispatched for a matching element nested inside a block's HTML \u2014 that call gets attributes + children only, with no \`block\` prop. Guard with \`if (!block) return <>{children}</>\`.`,
631
+ `brookmd: block ${info.blockId} (${info.kind}) failed to render and was skipped.${hint}`,
630
632
  { componentKeys: info.componentKeys, html: info.html },
631
633
  error
632
634
  );
package/dist/warn.d.ts CHANGED
@@ -1,9 +1,4 @@
1
- /** True unless the bundler/runtime says NODE_ENV === "production". Read off
2
- * `globalThis` so there is no @types/node dependency; bundlers inline
3
- * `process.env.NODE_ENV`, and absence is treated as dev (same rule as the
4
- * unstable-prop tripwire in react.tsx). */
5
- export declare function isDev(): boolean;
6
- /** Warn once per `id` (dev only). Returns true if it actually warned. */
1
+ /** Warn once per `id`. Callers own the production gate see the contract above. */
7
2
  export declare function warnOnce(id: string, message: string): boolean;
8
3
  /** Test-only: clear the latch so a test can assert a warning fires. */
9
4
  export declare function __resetWarnOnce(): void;
package/dist/warn.js CHANGED
@@ -1,10 +1,6 @@
1
1
  const warned = /* @__PURE__ */ new Set();
2
- function isDev() {
3
- const env = globalThis.process?.env;
4
- return !env || env.NODE_ENV !== "production";
5
- }
6
2
  function warnOnce(id, message) {
7
- if (!isDev() || warned.has(id)) return false;
3
+ if (warned.has(id)) return false;
8
4
  warned.add(id);
9
5
  console.warn(message);
10
6
  return true;
@@ -14,6 +10,5 @@ function __resetWarnOnce() {
14
10
  }
15
11
  export {
16
12
  __resetWarnOnce,
17
- isDev,
18
13
  warnOnce
19
14
  };
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brookmd",
3
- "version": "0.25.0",
3
+ "version": "0.25.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"],