jssm 5.157.18 → 5.158.0

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.
@@ -18,10 +18,10 @@ Please edit the file it's derived from, instead: `./src/md/readme_base.md`
18
18
 
19
19
 
20
20
 
21
- * Generated for version 5.157.18 at 7/3/2026, 1:55:46 AM
21
+ * Generated for version 5.158.0 at 7/3/2026, 11:48:59 AM
22
22
 
23
23
  -->
24
- # jssm 5.157.18
24
+ # jssm 5.158.0
25
25
 
26
26
  [**Try the live editor**](https://stonecypher.github.io/jssm-viz-demo/graph_explorer.html) ·
27
27
  [Documentation](https://stonecypher.github.io/jssm/docs/) ·
@@ -179,6 +179,27 @@ if (result.kind === 'text') console.log(result.content);
179
179
 
180
180
 
181
181
 
182
+ <br/>
183
+
184
+ ## Static fence rendering (`jssm/fence`)
185
+
186
+ `jssm/fence` renders FSL code fences to static, editor-parity-highlighted HTML — no browser, no editor, no live machine required — for Markdown pipelines, static site generators, and doc tooling.
187
+
188
+ ```typescript
189
+ import { transform_markdown } from 'jssm/fence';
190
+
191
+ const html = await transform_markdown(markdown);
192
+ // every fsl/jssm fence becomes rendered, highlighted HTML
193
+ // everything else in the document passes through untouched
194
+ console.log(html);
195
+ ```
196
+
197
+ `render_fence_gif` walks a machine's main path (or every edge, if there is no main path) into a looping animated GIF, one Graphviz layout patched per frame: 70cs per-frame delay, loops forever, and a 64-frame walk cap by default, all overridable via options.
198
+
199
+ PNG, JPEG, and GIF output rasterize through the same optional backend as the CLI — install it with `npm install @resvg/resvg-wasm`.
200
+
201
+
202
+
182
203
  <br/>
183
204
 
184
205
  ## Web Components
@@ -312,7 +333,7 @@ That decision shows up everywhere downstream:
312
333
  or run `npm run benny` against your own machine.
313
334
 
314
335
  - **More thoroughly tested than any other JavaScript state-machine
315
- library.** 7,902 tests at 100.0% line coverage
336
+ library.** 7,998 tests at 100.0% line coverage
316
337
  ([report](https://coveralls.io/github/StoneCypher/jssm)), plus
317
338
  fuzz testing via `fast-check`, with parser test data across ten natural
318
339
  languages and Emoji.
@@ -445,11 +466,11 @@ If your contribution is missing here, please open an issue.
445
466
 
446
467
  <br/>
447
468
 
448
- ***7,902 tests***, run 82,944 times.
469
+ ***7,998 tests***, run 83,040 times.
449
470
 
450
- - 7,144 specs with 100.0% coverage
451
- - 758 fuzz tests with 52.7% coverage
452
- - 10,013 TypeScript lines - 0.8 tests per line, 8.3 generated tests per line
471
+ - 7,240 specs with 100.0% coverage
472
+ - 758 fuzz tests with 49.4% coverage
473
+ - 10,615 TypeScript lines - 0.8 tests per line, 7.8 generated tests per line
453
474
 
454
475
  [![Actions Status](https://github.com/StoneCypher/jssm/workflows/Node%20CI/badge.svg)](https://github.com/StoneCypher/jssm/actions)
455
476
  [![NPM version](https://img.shields.io/npm/v/jssm.svg)](https://www.npmjs.com/package/jssm)
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Bundle entry for the `jssm/fence` subpath: host-agnostic static rendering
3
+ * of FSL markdown fences — HTML parts stacks with editor-parity
4
+ * highlighting, animated walk GIFs, and a whole-document Markdown
5
+ * transformer — plus the reusable primitives behind them (GIF89a encoder,
6
+ * walk planner, SVG patching).
7
+ *
8
+ * @see render_fence_html
9
+ * @see transform_markdown
10
+ */
11
+ export { render_fence_html, render_fence_gif, transform_markdown } from './fsl_fence_render.js';
12
+ export type { FenceRenderOptions, GifRenderOptions } from './fsl_fence_render.js';
13
+ export { encode_gif, quantize, lzw_encode } from './fsl_gif.js';
14
+ export type { GifFrame, GifOptions, Quantized } from './fsl_gif.js';
15
+ export { plan_walk } from './fsl_walk.js';
16
+ export { highlight_fsl_runs, highlight_fsl_html } from './fsl_fence_highlight.js';
17
+ export type { HighlightRun } from './fsl_fence_highlight.js';
18
+ export { extract_state_fills, patch_state_fill } from './fsl_svg_patch.js';
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Two-layer static FSL highlighter: the SAME `fslLanguage` stream grammar and
3
+ * `fslSemanticSpans` parser overlay the CodeMirror editor uses, merged into
4
+ * flat runs and (optionally) serialized to HTML. There is no third
5
+ * tokenizer — this is the parity guarantee between the editor and any
6
+ * static render (markdown fences, the cookbook, docs).
7
+ */
8
+ /** One classified slice of highlighted FSL source. */
9
+ export interface HighlightRun {
10
+ text: string;
11
+ /** Space-separated classes: `fsl-tok-*` token layer, `fsl-sem-*` semantic layer. */
12
+ classes: string;
13
+ /** The state name, present exactly on semantic state-name runs. */
14
+ state?: string;
15
+ }
16
+ /**
17
+ * Tokenize FSL source through the SAME two layers the editor uses — the
18
+ * `fslLanguage` stream grammar for token classes and `fslSemanticSpans` for
19
+ * parser-derived roles — merged into flat runs whose concatenated text
20
+ * reproduces the source byte-for-byte. Semantic classes overlay token
21
+ * classes (both are kept).
22
+ *
23
+ * This is the parity guarantee: there is no third tokenizer, so static
24
+ * output can never disagree with the editor.
25
+ *
26
+ * @param source FSL source text to classify.
27
+ * @returns Flat, gap-free, order-preserving runs; `runs.map(r => r.text).join('')` is `source`.
28
+ *
29
+ * @example
30
+ * highlight_fsl_runs('Red -> Green;').find(r => r.state === 'Red');
31
+ * // { text: 'Red', classes: 'fsl-tok-variableName fsl-sem-state', state: 'Red' }
32
+ */
33
+ export declare function highlight_fsl_runs(source: string): HighlightRun[];
34
+ /**
35
+ * Render FSL source as highlighted HTML (`<pre class="fsl-code">` inner
36
+ * content) using {@link highlight_fsl_runs}. Semantic state-name spans get
37
+ * `data-state`; when `inline_colors` (default true) and the state appears in
38
+ * `state_colors`, an inline `style="color:…"` ties the code block's state
39
+ * names to the diagram's node colors with zero host CSS.
40
+ *
41
+ * @param source FSL source text to render.
42
+ * @param opts.state_colors Maps a state name to its diagram fill color (e.g. from `extract_state_fills`).
43
+ * @param opts.inline_colors Whether to emit inline `style="color:…"` for matched states. Defaults to `true`.
44
+ * @returns HTML markup for the highlighted source; unclassed runs are emitted as escaped text with no wrapping span.
45
+ *
46
+ * @example
47
+ * highlight_fsl_html('Red -> Green;', { state_colors: new Map([['Red', '#a00']]) });
48
+ * // '…<span class="… fsl-sem-state" data-state="Red" style="color:#a00">Red</span>…'
49
+ */
50
+ export declare function highlight_fsl_html(source: string, opts?: {
51
+ state_colors?: ReadonlyMap<string, string>;
52
+ inline_colors?: boolean;
53
+ }): string;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * The descriptor interpreter for the FSL Markdown fence convention: turns a
3
+ * parsed {@link FenceDescriptor} plus FSL source into static HTML, and walks
4
+ * a whole Markdown document replacing every `fsl`/`jssm` fence in place.
5
+ * This is the integration point for the five pieces built ahead of it — the
6
+ * fence-info parser, the viz pipeline, the rasterizer, the SVG fill
7
+ * extractor, and the editor-parity highlighter.
8
+ *
9
+ * @see notes/superpowers/specs/2026-06-23-fsl-markdown-fence-convention-design.md
10
+ */
11
+ /** Options shared by the static fence renderers. */
12
+ export interface FenceRenderOptions {
13
+ /** Inline state colors in code spans (default true). @see highlight_fsl_html */
14
+ inline_colors?: boolean;
15
+ }
16
+ /**
17
+ * Render one FSL markdown fence to static HTML per the fence convention:
18
+ * parts stack top-down in the order written, sized by width/height, with
19
+ * editor-parity code highlighting whose state names carry the diagram's own
20
+ * node colors. Invalid FSL renders a visible error box — this function
21
+ * never throws for bad machine source.
22
+ *
23
+ * @param source - The FSL machine source (fence body).
24
+ * @param info - The fence info string (e.g. `'fsl image code width=300'`).
25
+ * @param opts.inline_colors - Whether code spans carry inline diagram colors (default true).
26
+ * @returns The rendered `<div class="fsl-fence">…</div>` markup.
27
+ *
28
+ * @example
29
+ * await render_fence_html('Red => Green => Red;', 'fsl');
30
+ * // '<div class="fsl-fence" …><svg…/svg><pre class="fsl-code">…</pre></div>'
31
+ */
32
+ export declare function render_fence_html(source: string, info: string, opts?: FenceRenderOptions): Promise<string>;
33
+ /**
34
+ * Replace every fsl/jssm fenced code block in a Markdown string with its
35
+ * rendered static HTML; all other content passes through byte-identical.
36
+ * Each fence is isolated — a broken machine becomes its own error box and
37
+ * the rest of the document still renders. Backtick fences of length ≥3
38
+ * are recognized; tilde fences are out of scope (v1, spec §9).
39
+ *
40
+ * @param markdown - The full Markdown document source.
41
+ * @param opts.inline_colors - Whether code spans carry inline diagram colors (default true).
42
+ * @returns The document with every `fsl`/`jssm` fence replaced by rendered HTML.
43
+ *
44
+ * @example
45
+ * await transform_markdown('# Doc\n\n```fsl\na -> b;\n```\n');
46
+ * // '# Doc\n\n<div class="fsl-fence">…</div>\n'
47
+ */
48
+ export declare function transform_markdown(markdown: string, opts?: FenceRenderOptions): Promise<string>;
49
+ /** Options for {@link render_fence_gif}. */
50
+ export interface GifRenderOptions {
51
+ /** Per-frame delay, centiseconds. Default 70 (~0.7s). */
52
+ delay_cs?: number;
53
+ /** Netscape loop count; 0 = forever (default). */
54
+ loop?: number;
55
+ /** Walk-length ceiling; longer walks truncate. Default 64. */
56
+ max_frames?: number;
57
+ /** Raster zoom percentage; 100 = 3× natural (the CLI raster convention). Default 100. */
58
+ scale?: number;
59
+ /** Fill painted on the walked state each frame. Default '#ff9930'. */
60
+ highlight_fill?: string;
61
+ }
62
+ /**
63
+ * Render an FSL machine as a looping animated GIF that walks its states:
64
+ * main-path (`=>`) states in order when a main path exists, else an
65
+ * every-edge tour. Graphviz lays the machine out ONCE; each frame patches
66
+ * one state's fill in the SVG string and rasterizes — identical geometry
67
+ * across frames, no layout jitter.
68
+ *
69
+ * @param source - The FSL machine source.
70
+ * @param opts.delay_cs - Per-frame delay in centiseconds (default 70).
71
+ * @param opts.loop - Netscape loop count, 0 = forever (default 0).
72
+ * @param opts.max_frames - Walk-length ceiling; longer walks truncate (default 64).
73
+ * @param opts.scale - Raster zoom percentage, 100 = 3× natural size (default 100).
74
+ * @param opts.highlight_fill - Fill painted on the walked state (default '#ff9930').
75
+ * @returns The encoded GIF89a bytes.
76
+ *
77
+ * @throws {JssmError} on invalid FSL (programmatic callers want exceptions;
78
+ * the HTML renderers catch and box instead).
79
+ *
80
+ * @example
81
+ * const gif = await render_fence_gif('Red => Green => Yellow => Red;');
82
+ * // Uint8Array starting "GIF89a", three frames, looping forever
83
+ *
84
+ * @see plan_walk
85
+ * @see encode_gif
86
+ */
87
+ export declare function render_fence_gif(source: string, opts?: GifRenderOptions): Promise<Uint8Array>;
@@ -0,0 +1,77 @@
1
+ /** Result of {@link quantize}: an RGB palette plus one palette index per input pixel. */
2
+ export interface Quantized {
3
+ /** RGB triples, `3 · palette_count` bytes. */
4
+ palette: Uint8Array;
5
+ /** Number of colors actually used (≤ the requested maximum). */
6
+ palette_count: number;
7
+ /** One palette index per input pixel. */
8
+ indices: Uint8Array;
9
+ }
10
+ /**
11
+ * Reduce an RGBA8888 buffer to an indexed-color image with at most
12
+ * `max_colors` colors, for GIF encoding. Alpha is composited over white
13
+ * (GIF v1 output carries no transparency). When the input already has
14
+ * `max_colors` or fewer distinct colors they are preserved exactly;
15
+ * otherwise a median-cut partition supplies the palette and each pixel maps
16
+ * to its box's weighted-average color.
17
+ *
18
+ * @param rgba - Straight RGBA bytes; length must be a multiple of 4.
19
+ * @param max_colors - Palette ceiling, 2..256.
20
+ *
21
+ * @throws {JssmError} when `rgba.length` is not a multiple of 4.
22
+ * @throws {JssmError} when `max_colors` is outside 2..256 — above 256 the
23
+ * palette index no longer fits the `Uint8Array` indices this module packs
24
+ * into GIF codes (silent index corruption instead of a clear failure);
25
+ * below 2 there is no palette to quantize into.
26
+ *
27
+ * @example
28
+ * const q = quantize(new Uint8Array([255,0,0,255, 0,255,0,255]));
29
+ * q.palette_count; // 2
30
+ */
31
+ export declare function quantize(rgba: Uint8Array, max_colors?: number): Quantized;
32
+ /**
33
+ * GIF-variant LZW compression: emits a leading clear code, grows code width
34
+ * from `min_code_size + 1` up to the format's 12-bit ceiling, resets the
35
+ * dictionary when full, terminates with EOI, and packs codes LSB-first.
36
+ * Returns raw compressed bytes; the caller wraps them in GIF data sub-blocks.
37
+ *
38
+ * @param indices - Palette indices, each `< 2^min_code_size`.
39
+ * @param min_code_size - Bits needed for the palette (2..8 for GIF).
40
+ *
41
+ * @example
42
+ * lzw_encode(new Uint8Array([0, 0, 1]), 2); // Uint8Array of packed codes
43
+ */
44
+ export declare function lzw_encode(indices: Uint8Array, min_code_size: number): Uint8Array;
45
+ /** One animation frame for {@link encode_gif}: straight RGBA8888 pixels. */
46
+ export interface GifFrame {
47
+ rgba: Uint8Array;
48
+ width: number;
49
+ height: number;
50
+ }
51
+ /** Options for {@link encode_gif}. */
52
+ export interface GifOptions {
53
+ /** Per-frame delay in centiseconds (GIF's native unit). Default 70 (~0.7s). */
54
+ delay_cs?: number;
55
+ /** Netscape loop count; 0 = loop forever (the default). */
56
+ loop?: number;
57
+ }
58
+ /**
59
+ * Encode RGBA frames as a looping animated GIF89a. A single global color
60
+ * table is quantized over the UNION of all frames' pixels (≤256 colors,
61
+ * median-cut when over); each frame then maps nearest-neighbor into that
62
+ * palette, so every frame is pixel-exact while the union stays within 256
63
+ * distinct colors. Frames must share dimensions. No transparency,
64
+ * full-frame disposal — simple and correct first.
65
+ *
66
+ * @param frames - At least one frame; all with identical width/height and
67
+ * `rgba.length === 4 · width · height`.
68
+ *
69
+ * @throws {JssmError} on zero frames, mismatched dimensions, or an rgba
70
+ * buffer whose length contradicts its stated dimensions.
71
+ *
72
+ * @example
73
+ * const red = { rgba: new Uint8Array([255,0,0,255]), width: 1, height: 1 };
74
+ * const gif = encode_gif([red], { delay_cs: 50 });
75
+ * gif.slice(0, 6); // "GIF89a" bytes
76
+ */
77
+ export declare function encode_gif(frames: GifFrame[], opts?: GifOptions): Uint8Array;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Read each state's current fill color out of a graphviz-rendered machine
3
+ * SVG, keyed by state name. States whose shape carries no `fill` attribute
4
+ * are omitted.
5
+ *
6
+ * @param svg - SVG markup from the jssm viz pipeline (`fsl_to_svg_string`).
7
+ *
8
+ * @example
9
+ * extract_state_fills(await fsl_to_svg_string('A -> B;')); // Map { 'A' => '#…', 'B' => '#…' }
10
+ *
11
+ * @see patch_state_fill
12
+ */
13
+ export declare function extract_state_fills(svg: string): Map<string, string>;
14
+ /**
15
+ * Return a copy of the SVG with the named state's first shape fill replaced.
16
+ * The unmatched-state case returns the input unchanged (walk truncation and
17
+ * render races surface as a missing highlight, never a throw).
18
+ *
19
+ * @param svg - SVG markup from the jssm viz pipeline.
20
+ * @param state - State name as written in FSL (unescaped).
21
+ * @param fill - Any SVG paint value, e.g. `'#ff9930'`.
22
+ *
23
+ * @example
24
+ * patch_state_fill(svg, 'Red', '#ff9930'); // Red's node now renders orange
25
+ *
26
+ * @see extract_state_fills
27
+ */
28
+ export declare function patch_state_fill(svg: string, state: string, fill: string): string;
@@ -0,0 +1,21 @@
1
+ import type { Machine } from './jssm.js';
2
+ /**
3
+ * Plan the frame sequence for an animated machine walk, as state names.
4
+ *
5
+ * With main-path edges (FSL `=>`, `main_path === true`): start at the
6
+ * machine's current state — the start state for a freshly-constructed
7
+ * machine, which is what the fence pipeline always passes — and follow
8
+ * main-path edges in declaration order, stopping at the first revisited
9
+ * state (the animation loops, so the cycle closes visually). Without any:
10
+ * tour every edge in declaration order, emitting each edge's endpoints and
11
+ * collapsing consecutive duplicates.
12
+ *
13
+ * This is presentation, not simulation — a tour's consecutive entries need
14
+ * not be legal transitions, and no machine state is mutated.
15
+ *
16
+ * @example
17
+ * plan_walk(sm`Red => Green => Yellow => Red;`); // ['Red', 'Green', 'Yellow']
18
+ *
19
+ * @see encode_gif
20
+ */
21
+ export declare function plan_walk(machine: Machine<unknown>): string[];