@gmb/bitmark-parser 6.10.1 → 6.11.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.
Files changed (50) hide show
  1. package/README.md +288 -22
  2. package/dist/browser/bitmark-parser.min.js +4 -4
  3. package/dist/browser/bitmark-parser.min.js.map +1 -1
  4. package/dist/browser/cjs/index.cjs +1053 -345
  5. package/dist/browser/cjs/index.cjs.map +1 -1
  6. package/dist/browser/cjs/index.d.cts +1175 -34
  7. package/dist/browser/esm/index.d.ts +1175 -34
  8. package/dist/browser/esm/index.js +1048 -345
  9. package/dist/browser/esm/index.js.map +1 -1
  10. package/dist/browser/esm/worker-entry.js +712 -96
  11. package/dist/browser/esm/worker-entry.js.map +1 -1
  12. package/dist/browser/wasm/bitmark_browser_full_wasm_bg.wasm +0 -0
  13. package/dist/browser/wasm/bitmark_json_wasm_bg.wasm +0 -0
  14. package/dist/browser/wasm/bitmark_wasm_bg.wasm +0 -0
  15. package/dist/index.cjs +124 -32
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +1175 -34
  18. package/dist/index.d.ts +1175 -34
  19. package/dist/index.js +119 -32
  20. package/dist/index.js.map +1 -1
  21. package/dist/legacy.cjs +4 -4
  22. package/dist/legacy.cjs.map +1 -1
  23. package/dist/legacy.js +4 -4
  24. package/dist/legacy.js.map +1 -1
  25. package/dist/worker-entry.cjs +7 -1
  26. package/dist/worker-entry.cjs.map +1 -1
  27. package/package.json +14 -8
  28. package/schema/bitmark.schema.json +2746 -573
  29. package/translations/translations.json +2940 -0
  30. package/wasm/bitmark_wasm.d.ts +53 -12
  31. package/wasm/bitmark_wasm.js +171 -47
  32. package/wasm/bitmark_wasm_bg.wasm +0 -0
  33. package/wasm/bitmark_wasm_bg.wasm.d.ts +4 -1
  34. package/wasm/package.json +1 -1
  35. package/wasm-bitmark-json/bitmark_json_wasm.d.ts +39 -12
  36. package/wasm-bitmark-json/bitmark_json_wasm.js +129 -53
  37. package/wasm-bitmark-json/bitmark_json_wasm_bg.wasm +0 -0
  38. package/wasm-bitmark-json/bitmark_json_wasm_bg.wasm.d.ts +3 -1
  39. package/wasm-bitmark-json/package.json +1 -1
  40. package/wasm-browser-full/bitmark_browser_full_wasm.d.ts +149 -0
  41. package/wasm-browser-full/bitmark_browser_full_wasm.js +647 -0
  42. package/wasm-browser-full/bitmark_browser_full_wasm_bg.wasm +0 -0
  43. package/wasm-browser-full/bitmark_browser_full_wasm_bg.wasm.d.ts +23 -0
  44. package/wasm-browser-full/package.json +20 -0
  45. package/dist/browser/bitmark_json_wasm_bg.wasm +0 -0
  46. package/dist/browser/bitmark_wasm_bg.wasm +0 -0
  47. package/dist/browser/cjs/bitmark_json_wasm_bg.wasm +0 -0
  48. package/dist/browser/cjs/bitmark_wasm_bg.wasm +0 -0
  49. package/dist/browser/esm/bitmark_json_wasm_bg.wasm +0 -0
  50. package/dist/browser/esm/bitmark_wasm_bg.wasm +0 -0
package/README.md CHANGED
@@ -55,21 +55,43 @@ const bitInfo = info({ infoType: "list", format: "text" });
55
55
  console.log(version());
56
56
  ```
57
57
 
58
- ### WASM feature modules (`full` / `bitmark-json`)
58
+ ### WASM variants (`full` / `browser-full` / `bitmark-json`)
59
59
 
60
- The package ships **two wasm builds of the same engine** and selects one at
60
+ The package ships **three wasm builds of the same engine** and selects one at
61
61
  runtime — the API surface never changes:
62
62
 
63
63
  | Feature | Contents | Size (approx.) |
64
64
  | --- | --- | --- |
65
- | `full` (default) | everything | ~749 KB (~310 KB gzip) |
66
- | `bitmark-json` | bitmark ↔ JSON only: `convert`/`canonicalize`/`transform` (formats `auto`/`bitmark`/`json`, plus the `text` output), `info`, breakscape, text fragments | ~560 KB (~231 KB gzip) |
65
+ | `full` (Node default) | everything: rich `info` metadata + built-in translations + the semantic `diff` | ~1082 KB (~423 KB gzip) |
66
+ | `browser-full` (browser default) | the same conversions and `diff`, without either | ~935 KB (~374 KB gzip) |
67
+ | `bitmark-json` | bitmark ↔ JSON only: `convert`/`canonicalize`/`transform` (formats `auto`/`bitmark`/`json`, plus the `text` output), `info`, breakscape, text fragments — no `diff` | ~705 KB (~277 KB gzip) |
67
68
 
68
69
  Not in `bitmark-json`: the markup formats (`html`, `xml`, `xml-niso-iec`, …)
69
70
  as input or output (they return an `error: … not supported in this build`
70
71
  string), the `mappingReport` option (same error), and `lex` (throws
71
72
  `UnsupportedFeatureError`).
72
73
 
74
+ `full` and `browser-full` convert **identically** — they differ only in what
75
+ `info` can report: the META fields (tag descriptions, group provenance and raw
76
+ mapping patterns) and the built-in translations table. Bit titles, bit
77
+ descriptions and the bit-group / resource-group catalogs are in every variant,
78
+ in English; `register` supplies translations to the lean ones (see *Display
79
+ names and languages*).
80
+
81
+ Why the defaults differ: only the browser pays a download-latency cost for
82
+ those strings, and a Node backend loading wasm from disk has the same
83
+ requirements as the native CLI. But which channel an artifact ends up in is
84
+ not knowable from the artifact — browser code is routinely package-managed and
85
+ webpack-built — so both variants are available on both platforms and the
86
+ defaults are conveniences only. **If you bundle the Node entry for the
87
+ browser, select `browser-full` explicitly.**
88
+
89
+ > **Migrating from ≤ 6.10:** `full` used to mean what `browser-full` means now.
90
+ > Browser code that explicitly selected `"full"` should move to
91
+ > `"browser-full"` to keep its download size close to what it was; leave it on
92
+ > `"full"` to gain the metadata and built-in translations for ~49 KB gzip.
93
+ > Nothing else changes — the capabilities that were in `full` are in both.
94
+
73
95
  ```js
74
96
  import { init, convert, variant } from "@gmb/bitmark-parser";
75
97
 
@@ -88,12 +110,16 @@ Notes:
88
110
  - **Node**: with no explicit `init`, the first call lazily loads `full`
89
111
  synchronously — existing zero-init usage is unchanged. `initSync({ feature })`
90
112
  selects a variant synchronously.
91
- - **Browser**: `init()` is required as before; only the selected variant's
92
- `.wasm` is fetched (both small JS glue modules are in the bundle). Overlapping
93
- `init` calls: same-feature calls coalesce; a different-feature call supersedes
94
- an unfinished one (last call wins); every promise resolves once the
95
- finally-active module is live. In the browser, `initSync(bytes, { feature })`
96
- instantiates from bytes you supply.
113
+ - **Browser**: `init()` is required as before, and loads `browser-full`; only
114
+ the selected variant's `.wasm` is fetched (all three small JS glue modules
115
+ are in the bundle). Overlapping `init` calls: same-feature calls coalesce; a
116
+ different-feature call supersedes an unfinished one (last call wins); every
117
+ promise resolves once the finally-active module is live. In the browser,
118
+ `initSync(bytes, { feature })` instantiates from bytes you supply.
119
+ - **Supplying your own `.wasm`**: glue and module are a pair, so name the
120
+ feature the bytes belong to — `init({ feature, module_or_path })`. The
121
+ back-compat positional form `init(module_or_path)` stays on `full`, since
122
+ those bytes are `bitmark_wasm_bg.wasm`.
97
123
  - **Workers**: `transformParallel` workers load the active variant.
98
124
  - **Legacy API**: works on `bitmark-json` (it follows the active module) except
99
125
  its HTML-dependent members (e.g. `convertHtmlTable`), which throw.
@@ -124,8 +150,12 @@ Options:
124
150
  bit's config cannot resolve, at the bit body/footer — never inside cards or
125
151
  consumed by a chain) is appended after all real keys as an array of strings
126
152
  (a valueless `[@key]` → `true`); a key colliding with a configured key of
127
- the bit is `_`-prefixed. Unknown properties are never converted back to
128
- bitmark, and each occurrence warns whether or not it is included.
153
+ the bit is `_`-prefixed. A bare tag the bit does not declare (`[!…]`,
154
+ `[?…]`, `[#…]`, … on a bit whose config has no such tag) is an unknown
155
+ too, under the key a declaring bit would use (`[!x]` → `"instruction":
156
+ ["x"]`); a rejected resource attachment is not — it is still emitted, with
157
+ a warning. Unknown properties are never converted back to bitmark, and
158
+ each occurrence warns whether or not it is included.
129
159
  Also available on `bitmarkToObjects` and as the CLI's
130
160
  `--include-unknown-properties`
131
161
  - `mappingReport` — replace the converted output with a human-readable
@@ -153,7 +183,7 @@ Options:
153
183
  For bitmark input, output is a JSON array of entries with:
154
184
 
155
185
  - `bit` — serialized bit payload
156
- - `parser` — parser metadata, `errors` / `warnings`, and `infos` (informational notices for recoveries that are not necessarily wrong, e.g. text kept as text because it only looks like an unclosed tag)
186
+ - `parser` — parser metadata, `errors` / `warnings`, and `infos` (informational notices for recoveries that are not necessarily wrong, e.g. text kept as text because it only looks like an unclosed tag). Each issue carries a stable machine-readable `code` (`"unknown-property"`, `"missing-required-tag"`, …) beside its human-readable `message`, plus `text` and `location`. **Branch on `code`** — a shipped code never changes meaning, whereas the message text is not contractual and may be reworded in any release
157
187
  - `bitmark` — source bitmark text
158
188
 
159
189
  #### canonicalize(input: string, options?: CanonicalizeOptions): string
@@ -162,7 +192,90 @@ Re-emit input in its own format in canonical form — the single same-format sur
162
192
 
163
193
  #### transform(input: string, options?: TransformOptions): string
164
194
 
165
- Apply path patches to each bit (per-bit transform pipeline; build patches with `patchEntry`). `transformParallel` is the worker-thread variant returning a `Promise<string>`.
195
+ Apply a patch document to a document, bit by bit, and re-emit it.
196
+ `transformParallel` is the worker-thread variant returning a `Promise<string>`.
197
+
198
+ - `patch` — a `PatchDocument`, or the shorthand: a bare array of entries
199
+ applied to every bit (build entries with `patchEntry`). Either form may be
200
+ a JSON string
201
+ - `preHook` / `postHook` — per-bit hooks; the pre-hook may return patches, an
202
+ output-format override or `drop`. Hooks see the original bits, in order,
203
+ before the document reshapes anything; a hook's patches apply after the
204
+ document's
205
+ - `inputFormat`, `outputFormat`, `mode`, `pretty`, `indent`,
206
+ `spacesAroundValues` — as for `convert`
207
+
208
+ A patch document addresses bits by their position (or id) in the input
209
+ **before** any entry is applied, so it reads like a diff:
210
+
211
+ ```json
212
+ {
213
+ "version": 1,
214
+ "entries": [
215
+ { "select": { "index": 3, "id": "mc-014" }, "patch": [{ "path": "title", "op": "set", "value": "New" }] },
216
+ { "select": { "index": 9 }, "remove": true },
217
+ { "insert": { "after": 4 }, "bit": { "type": "article", "body": "Inserted" } },
218
+ { "select": { "index": 7 }, "move": { "after": 12 } },
219
+ { "select": "all", "patch": [{ "lang": "de" }] }
220
+ ]
221
+ }
222
+ ```
223
+
224
+ `after` names an anchor — an input bit that is neither removed nor moved,
225
+ or `-1` for the document start. `diff` produces such a document from two
226
+ versions of a file (below).
227
+
228
+ #### diff(a: string, b: string, options?: DiffOptions): string
229
+
230
+ Semantic diff of two documents. Both sides are re-canonicalized and
231
+ compared as bit JSON, so tag order, spacing, breakscaping and omitted
232
+ defaults are not differences; every real change is rendered as the
233
+ canonical bitmark an author would type. The inputs may be bitmark or JSON,
234
+ and need not share a format.
235
+
236
+ ```
237
+ @@ bit 1 -> 2 (id mc-014) [.multiple-choice] changed
238
+ instruction[0]
239
+ -[!Pick one]
240
+ +[!Pick exactly one]
241
+ quizzes[0]
242
+ ====
243
+ [-Bonn]
244
+ -[-Berlin]
245
+ +[+Berlin]
246
+ @@ bit 4 (id hero) [.image] removed
247
+ -[.image]
248
+ -[@id:hero]
249
+ -[&image:https://example.com/hero.png]
250
+ ```
251
+
252
+ Bits are paired by `id`, then by `sourceBB` on the same `sourceRL`, then by
253
+ exact content, then by text similarity; a bit whose position changed among
254
+ the kept ones is reported `moved`. Empty output means the documents are
255
+ semantically equal.
256
+
257
+ - `outputFormat` — `"bitmark"` (default; the text above), `"json"` (one
258
+ object per aligned bit, see `diffBits`), or `"patch"` (a patch document
259
+ that turns `a` into `b` — feed it to `transform`)
260
+ - `inputFormat` — applies to both sides (default: `"auto"`, sniffed per side)
261
+ - `context` — unchanged lines kept around each change (default: `3`)
262
+ - `locate` — add source line numbers to bit positions (bitmark inputs; default: `false`)
263
+ - `similarity` — pairing threshold for bits with no id, box or exact match, `0..1` (default: `0.6`; `1` disables)
264
+ - `bboxTolerance` — pixels per coordinate for the `sourceBB` stage (default: `8`)
265
+ - `spacesAroundValues` — in the rendered bitmark lines (default: `0`)
266
+
267
+ The text layout and the JSON shape are stable contracts. An unknown
268
+ property is shown as a JSON line, since the generator cannot render it, and
269
+ cannot be re-applied. Not available on the `bitmark-json` variant
270
+ (`UnsupportedFeatureError`).
271
+
272
+ #### diffBits(a: string, b: string, options?: DiffOptions): BitDiff[]
273
+
274
+ The typed form of `diff`: one `BitDiff` per aligned bit, unchanged ones
275
+ included so the alignment is complete — `status`
276
+ (`unchanged | changed | moved | removed | added`), the `a`/`b` positions,
277
+ `ops` (a patch turning the A bit into the B bit) and `revert`, the parser
278
+ issue codes gained or lost, and the rendered `hunks`.
166
279
 
167
280
  #### countBits(input: string): number / splitBits(input: string): BitSlice[]
168
281
 
@@ -201,7 +314,8 @@ format-natural), nullability (absence as a distinct state — orthogonal to
201
314
  defaults), JSON keys, per-context overrides, and the card set structure. `"deprecated"` lists deprecated bits with their deprecation
202
315
  version and (separately) any migration target.
203
316
 
204
- - `infoType` — `"list"` (default), `"bit"`, `"all"`, or `"deprecated"`
317
+ - `infoType` — `"list"` (default), `"bit"`, `"all"`, `"deprecated"`,
318
+ `"bit-groups"`, `"resource-groups"`, or `"languages"`
205
319
  - `format` — `"text"` (default) or `"json"`
206
320
  - `bit` — filter to a specific bit type (when `infoType` is `"bit"`)
207
321
  - `pretty` — prettify JSON output (default: `false`)
@@ -209,10 +323,151 @@ version and (separately) any migration target.
209
323
  - `includeDeprecated` — include deprecated bits in `"list"`/`"all"` (default: `false`)
210
324
  - `onlyDeprecated` — restrict `"list"`/`"all"` to deprecated bits (default: `false`)
211
325
  - `full` — complete detail for `"bit"`/`"all"` (default: `false` = compact view)
326
+ - `language` — BCP-47 tag(s) for display names: one (`"de"`), several
327
+ (`["de", "fr"]` or `"de,fr"`), or `"all"` — exported as `ALL_LANGUAGES`
328
+ (default: English). More than one adds a `titles` map. See *Display names
329
+ and languages* below
330
+
331
+ `"bit-groups"` / `"resource-groups"` return the search/filter catalogs —
332
+ each group's key, translated title, description, optional aliases and
333
+ `subgroupOf`, and its members. They are available in EVERY build: the group
334
+ catalog and per-bit titles/descriptions are "descriptive" data, which the
335
+ lean wasm variants carry too. Deprecated members are excluded by default and
336
+ MARKED when included with `includeDeprecated: true`; a search index matching
337
+ already-published content needs them, because a migrating bit is re-emitted
338
+ under its target's name.
339
+
340
+ To derive quiz categories, intersect a bit's `bitGroups` with the groups
341
+ carrying `subgroupOf: "quizzes"` — `subgroupOf` is metadata and never implies
342
+ membership, so every member of a subgroup also declares the parent.
343
+
344
+ Only the META fields — TAG descriptions, group-inheritance provenance and raw
345
+ mapping patterns (the `info-meta` cargo feature) — depend on the build. They
346
+ are reported by the native CLI and the wasm `full` variant; `browser-full` and
347
+ `bitmark-json` omit them to stay small. Those are absent structurally: the key
348
+ is missing, never `null` or empty. Everything else `info` returns — including
349
+ bit titles, bit descriptions and the group catalogs — is identical in every
350
+ variant.
351
+
352
+ #### Display names and languages
353
+
354
+ `title` — on bits, bit groups and resource groups — is a DISPLAY name, meant
355
+ to be shown. Pass `language` to get it in another language:
356
+
357
+ ```js
358
+ info({ infoType: "bit-groups", format: "json", language: "de" });
359
+ ```
360
+
361
+ Resolution narrows one subtag at a time (`de-CH` → `de`), then falls back to
362
+ the English title, then to the technical key. A partially translated language
363
+ is therefore safe: untranslated entries stay English rather than blank.
364
+ **Technical identity is never translated** — `name`, `key`, tag names and
365
+ member lists are the same in every language, so you can key off them freely.
366
+
367
+ **Several languages at once.** Building a multilingual index should not mean
368
+ walking every bit once per language, so `language` also takes a list, or
369
+ `"all"` — exported as the `ALL_LANGUAGES` constant, so you can name it rather
370
+ than retype the literal:
371
+
372
+ ```js
373
+ import { info, ALL_LANGUAGES } from "@gmb/bitmark-parser";
374
+
375
+ const groups = JSON.parse(
376
+ info({ infoType: "bit-groups", format: "json", language: ALL_LANGUAGES }),
377
+ );
378
+ groups[0].titles; // { en: "Cloze", de: "Lückentext", … }
212
379
 
213
- The WASM build omits bit/tag descriptions, group-inheritance provenance and
214
- raw mapping patterns (native-CLI-only, via the `info-meta` cargo feature) to
215
- keep the binary small.
380
+ // equivalent, and the idiomatic JS spelling of a list
381
+ info({ infoType: "bit-groups", format: "json", language: ["de", "fr"] });
382
+
383
+ // what "all" expands to
384
+ JSON.parse(info({ infoType: "languages", format: "json" })); // ["de","en",…]
385
+ ```
386
+
387
+ **Getting every name in one call.** `bit-groups` carries each member's
388
+ display name, so one request answers "every bit type, what it is called, and
389
+ which categories it is in":
390
+
391
+ ```js
392
+ const groups = JSON.parse(
393
+ info({ infoType: "bit-groups", format: "json", includeDeprecated: true, language: "all" }),
394
+ );
395
+ groups[0].bitTypes[0]; // { name: "assignment", title: "Assignment", titles: {…} }
396
+ ```
397
+
398
+ That is ~132 KB. Do not reach for `infoType: "all"` to get names — it carries
399
+ the full per-tag detail for every bit (~5 MB) and is the wrong tool for this.
400
+
401
+ More than one language adds a **`titles`** map, and nothing else. It carries `en` when an English title exists, and is otherwise sparse: a requested language with no translation is absent rather than filled with English, so a gap stays visible.
402
+ It is keyed by the tag you ASKED for — request `de-CH` where only `de` exists
403
+ and you read `titles["de-CH"]`.
404
+
405
+ A single tag (or none) renders exactly as it always has, with no `titles`. The
406
+ shape follows your REQUEST rather than which language you picked: `"all"` adds
407
+ `titles` even on a build that resolves nothing but English.
408
+
409
+ `title` is the name in your PRIMARY language — the first one you named. Name
410
+ none and it is English; ask for `"all"` and it is English too, because with
411
+ everything already in `titles` there is no non-arbitrary "first". So `title`
412
+ tracks how you asked: consistent for any consumer that asks the same way every
413
+ time, which in practice is all of them — a UI names one language, an index
414
+ asks for `"all"`.
415
+
416
+ `title` stays optional, as its type says — it is the name in the language you
417
+ asked for, and a few bits have one in some languages but not in English (the
418
+ internal `_comment` and `_error`). Every other key is the same in every
419
+ language.
420
+
421
+ Which languages resolve depends on the build. The native CLI and the wasm
422
+ `full` variant bake the table in; `browser-full` and `bitmark-json` do not,
423
+ because ~76 KB of names is not something a browser should download without
424
+ asking. Those variants — and any build whose translations you want to
425
+ override — take the file at runtime:
426
+
427
+ ```js
428
+ import { init, register, info } from "@gmb/bitmark-parser";
429
+ import translations from "@gmb/bitmark-parser/translations" with { type: "json" };
430
+
431
+ await init({ feature: "browser-full" });
432
+ register({ type: "translations", data: JSON.stringify(translations) });
433
+ info({ infoType: "bit", bit: "cloze", format: "json", language: "de" });
434
+ ```
435
+
436
+ `register` REPLACES rather than merges: a second call supersedes the first
437
+ entirely, and the built-in table (where there is one) stays underneath, so a
438
+ key your file omits still resolves. It applies process-wide and survives an
439
+ `init` variant swap. The `language` option stays per-call, because that is
440
+ the axis that actually varies — one process may serve many.
441
+
442
+ The same file backs all three routes: `@gmb/bitmark-parser/translations`
443
+ (an opt-in subpath, so nothing pays for it unasked), the CLI's
444
+ `--translations <file>` flag, and what the `full` builds bake in. It holds
445
+ non-English names only — English lives in the config `title`, which every
446
+ build carries.
447
+
448
+ **The JSON shape is a stable contract**; a change to it is a semver-major
449
+ release. Language changes VALUES only — the key set is identical for every
450
+ language. Parse it and assert the matching exported type:
451
+
452
+ ```ts
453
+ import { info, type DeprecatedInfo } from "@gmb/bitmark-parser";
454
+
455
+ const deprecated = JSON.parse(
456
+ info({ infoType: "deprecated", format: "json" }),
457
+ ) as DeprecatedInfo;
458
+ ```
459
+
460
+ Result types: `BitListInfo`, `BitInfo` (`CompactBitInfo | FullBitInfo`),
461
+ `AllBitsInfo`, `DeprecatedInfo`, `BitGroupsInfo`, `ResourceGroupsInfo`, and
462
+ `InfoErrorResult` — returned in place of
463
+ a result when `infoType` is `"bit"` and the name is unknown. `info` itself
464
+ returns a `string` because it renders a document (`format: "text"`, `pretty`
465
+ and `indent` only mean anything for one).
466
+
467
+ **The text output is NOT a contract** — it is for humans and may change in
468
+ any release. Never parse it; every field it shows exists in the JSON form.
469
+ The normative definition of the JSON is
470
+ `.zen/specs/API-INF-info-output.tsp`.
216
471
 
217
472
  #### version(): string
218
473
 
@@ -376,9 +631,18 @@ bitmark convert standard.xml --input-format xml-niso-iec --output-format bitmark
376
631
  bitmark canonicalize input.bitmark
377
632
  bitmark canonicalize input.json --mode full
378
633
 
379
- # Apply path patches to each bit
634
+ # Apply a patch document (per-bit patches by index/id, remove, insert, move;
635
+ # a bare array of entries still patches every bit)
380
636
  bitmark transform input.json --patch patches.json # alias: patch
381
637
 
638
+ # Semantic diff of two documents, rendered as bitmark
639
+ bitmark diff old.bitmark new.bitmark # unified-style text
640
+ bitmark diff old.bitmark new.bitmark --stat --exit-code # counts; exit 1 when they differ
641
+ bitmark diff old.bitmark new.json --output-format json # per-bit ops, revert, hunks
642
+ bitmark diff old.bitmark new.bitmark --output-format patch > d.patch
643
+ bitmark transform old.bitmark --patch d.patch --output-format bitmark # reproduces new
644
+ # --context N, --locate, --similarity F, --bbox-tolerance N, --color auto|always|never
645
+
382
646
  # Dump the lexer token stream
383
647
  bitmark lex input.bitmark
384
648
 
@@ -420,9 +684,11 @@ The package includes pre-built browser bundles with the WASM module.
420
684
  </script>
421
685
  ```
422
686
 
423
- The CDN bundle carries both feature modules' JS glue; `init({ feature:
424
- "bitmark-json" })` fetches only the smaller `.wasm`, and a later
425
- `init({ feature: "full" })` upgrades in place (see *WASM feature modules*).
687
+ The CDN bundle carries all three variants' JS glue and fetches only the
688
+ selected variant's `.wasm` (from `dist/browser/wasm/`). A bare `init()` loads
689
+ `browser-full`; `init({ feature: "bitmark-json" })` fetches the smallest
690
+ build, and a later `init({ feature: "full" })` upgrades in place (see *WASM
691
+ variants*).
426
692
 
427
693
  ### Bundler (webpack / vite)
428
694