jssm 5.162.7 → 5.162.8

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 (53) hide show
  1. package/README.md +7 -7
  2. package/custom-elements.json +47 -47
  3. package/dist/cdn/instance.js +5 -3
  4. package/dist/cdn/viz.js +1 -1
  5. package/dist/cli/fsl-export-system-prompt.cjs +27 -24
  6. package/dist/cli/fsl-render.cjs +1 -1
  7. package/dist/cli/fsl.cjs +1 -1
  8. package/dist/cli/lib.cjs +1 -1
  9. package/dist/cli/lib.mjs +1 -1
  10. package/dist/cm6/fsl_language.js +10 -14
  11. package/dist/deno/README.md +7 -7
  12. package/dist/deno/fence.d.ts +0 -1
  13. package/dist/deno/fsl_fence_highlight.d.ts +0 -4
  14. package/dist/deno/fsl_fence_render.d.ts +0 -8
  15. package/dist/deno/fsl_gif.d.ts +0 -8
  16. package/dist/deno/fsl_markdown_fence.d.ts +0 -5
  17. package/dist/deno/fsl_svg_patch.d.ts +0 -6
  18. package/dist/deno/fsl_walk.d.ts +0 -2
  19. package/dist/deno/jssm.d.ts +214 -503
  20. package/dist/deno/jssm.js +1 -1
  21. package/dist/deno/jssm_compiler.d.ts +1 -1
  22. package/dist/deno/jssm_constants.d.ts +0 -3
  23. package/dist/deno/jssm_intern.d.ts +0 -15
  24. package/dist/deno/jssm_theme.d.ts +2 -2
  25. package/dist/deno/jssm_types.d.ts +13 -34
  26. package/dist/deno/jssm_util.d.ts +20 -6
  27. package/dist/deno/jssm_viz.d.ts +5 -51
  28. package/dist/es6/cm6/fsl_language.d.ts +0 -2
  29. package/dist/fence/fence.js +1651 -1833
  30. package/dist/jssm.es5.cjs +1 -1
  31. package/dist/jssm.es5.iife.js +1 -1
  32. package/dist/jssm.es6.mjs +1 -1
  33. package/dist/jssm_viz.cjs +1 -1
  34. package/dist/jssm_viz.iife.cjs +1 -1
  35. package/dist/jssm_viz.mjs +1 -1
  36. package/dist/wc/docs.define.js +0 -5
  37. package/dist/wc/docs.js +79 -43
  38. package/dist/wc/editor.define.js +0 -5
  39. package/dist/wc/editor.js +1 -7
  40. package/dist/wc/instance.define.js +0 -6
  41. package/dist/wc/instance.js +93 -115
  42. package/dist/wc/viz.define.js +0 -6
  43. package/dist/wc/viz.js +22 -36
  44. package/dist/wc/widgets.define.js +0 -5
  45. package/dist/wc/widgets.js +117 -95
  46. package/jssm.cli.d.cts +0 -8
  47. package/jssm.cli.d.ts +0 -8
  48. package/jssm.es5.d.cts +449 -808
  49. package/jssm.es6.d.ts +449 -808
  50. package/jssm.fence.d.ts +213 -508
  51. package/jssm_viz.es5.d.cts +216 -529
  52. package/jssm_viz.es6.d.ts +216 -529
  53. package/package.json +16 -10
@@ -1,6 +1,6 @@
1
1
  import { css, html, LitElement } from 'lit';
2
2
  import { state, property } from 'lit/decorators.js';
3
- import { machine_to_dot, machine_to_svg_string } from 'jssm/viz';
3
+ import { machine_to_svg_string, machine_to_dot } from 'jssm/viz';
4
4
  import { STOCHASTIC_DEFAULT_RUNS, STOCHASTIC_DEFAULT_MAX_STEPS, sm } from 'jssm';
5
5
 
6
6
  /**
@@ -49,11 +49,9 @@ const fslTokens = css `
49
49
  /**
50
50
  * Returns true when `tag_name` is exactly `fsl-<suffix>` or `jssm-<suffix>`
51
51
  * (case-insensitive).
52
- *
53
52
  * @param tag_name - The element tag name to test (e.g. `"FSL-VIZ"`, `"jssm-viz"`).
54
53
  * @param suffix - The suffix to match after the prefix (e.g. `"viz"`).
55
54
  * @returns `true` when `tag_name` is `fsl-<suffix>` or `jssm-<suffix>`.
56
- *
57
55
  * @example
58
56
  * wc_suffix_matches('FSL-VIZ', 'viz'); // true
59
57
  * wc_suffix_matches('jssm-viz', 'viz'); // true
@@ -63,15 +61,12 @@ const fslTokens = css `
63
61
  /**
64
62
  * Returns the nearest ancestor of `el` (or `el` itself) whose tag is
65
63
  * `fsl-<suffix>` or `jssm-<suffix>`, or `null` if none exists.
66
- *
67
64
  * @param el - The element to start the search from.
68
65
  * @param suffix - The suffix to match (e.g. `"instance"`).
69
66
  * @returns The closest matching ancestor element, or `null`.
70
- *
71
67
  * @example
72
68
  * // <fsl-instance><div id="k"></div></fsl-instance>
73
69
  * closest_wc(document.getElementById('k'), 'instance'); // <fsl-instance>
74
- *
75
70
  * @see wc_suffix_matches
76
71
  */
77
72
  function closest_wc(el, suffix) {
@@ -90,7 +85,6 @@ function closest_wc(el, suffix) {
90
85
  */
91
86
  /**
92
87
  * Default fragment key for the single-machine case (back-compat with 5.150).
93
- *
94
88
  * @example
95
89
  * DEFAULT_PERMALINK_KEY; // 'm'
96
90
  */
@@ -98,7 +92,6 @@ const DEFAULT_PERMALINK_KEY = 'm';
98
92
  /**
99
93
  * URL-safe base64 (RFC 4648 §5) of raw bytes: standard base64 with `+`→`-`,
100
94
  * `/`→`_`, and trailing `=` padding stripped.
101
- *
102
95
  * @example
103
96
  * bytes_to_base64url(new TextEncoder().encode("a")); // "YQ"
104
97
  */
@@ -111,7 +104,6 @@ function bytes_to_base64url(bytes) {
111
104
  }
112
105
  /**
113
106
  * DEFLATE `bytes` (raw, headerless) via the platform `CompressionStream`.
114
- *
115
107
  * @example
116
108
  * await deflate_raw(new TextEncoder().encode("aaaaaaaa")); // shorter Uint8Array of raw DEFLATE bytes
117
109
  */
@@ -129,7 +121,6 @@ async function deflate_raw(bytes) {
129
121
  * Encode FSL to a `<scheme><payload>` segment value (the part after `key=`).
130
122
  * DEFLATE is used (scheme `1`) only when it is strictly shorter than the raw
131
123
  * bytes (scheme `0`).
132
- *
133
124
  * @example
134
125
  * await encode_machine("a -> b;"); // "0YSAtPiBiOw"
135
126
  */
@@ -139,8 +130,10 @@ async function encode_machine(fsl) {
139
130
  const deflated = bytes_to_base64url(await deflate_raw(utf8));
140
131
  return deflated.length < raw.length ? `1${deflated}` : `0${raw}`;
141
132
  }
142
- /** `decodeURIComponent` that returns its input untouched on a malformed escape,
143
- * so a hand-mangled fragment never throws out of {@link read_fragment_param}. */
133
+ /**
134
+ * `decodeURIComponent` that returns its input untouched on a malformed escape,
135
+ * so a hand-mangled fragment never throws out of {@link read_fragment_param}.
136
+ */
144
137
  function safe_decode(text) {
145
138
  try {
146
139
  return decodeURIComponent(text);
@@ -166,7 +159,6 @@ function fragment_pairs(hash) {
166
159
  /**
167
160
  * Return a new fragment body (no leading `#`) with `key`'s segment set to
168
161
  * `value`, preserving every other segment and its order; appends if absent.
169
- *
170
162
  * @example
171
163
  * set_fragment_param('#a=0AAA', 'b', '1BBB'); // "a=0AAA&b=1BBB"
172
164
  */
@@ -187,7 +179,6 @@ function set_fragment_param(hash, key, value) {
187
179
  * The fragment key an element owns: its `uhash` attribute if set, else its
188
180
  * `id`, else `null` (does not participate in URL sync). The single source of
189
181
  * this rule, shared by the toolbar export and the sync controller.
190
- *
191
182
  * @example
192
183
  * permalink_key_for(el); // "myId" (when <el id="myId">, no uhash)
193
184
  */
@@ -199,18 +190,15 @@ function permalink_key_for(host) {
199
190
  * A shareable URL for `fsl` under `key`, merging into `currentHash` so sibling
200
191
  * machines' segments survive. Browser-defaulted (`location`) but injectable for
201
192
  * tests.
202
- *
203
193
  * @returns The absolute URL carrying the merged fragment.
204
- *
205
194
  * @example
206
195
  * await permalink_for('a -> b;', 'm', 'https://h/p', ''); // "https://h/p#m=0YSAtPiBiOw"
207
- *
208
196
  * @see fsl_from_permalink
209
197
  */
210
198
  async function permalink_for(fsl, key = DEFAULT_PERMALINK_KEY, href = location.href, currentHash = location.hash) {
211
199
  const segment = await encode_machine(fsl);
212
200
  const fragment = set_fragment_param(currentHash, key, segment);
213
- return `${href.split('#')[0]}#${fragment}`;
201
+ return `${href.split('#', 1)[0]}#${fragment}`;
214
202
  }
215
203
 
216
204
  var __decorate$8 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
@@ -234,11 +222,12 @@ const EXPORT_FORMATS = [
234
222
  { value: 'permalink', label: 'Permalink (URL)' },
235
223
  { value: 'embed', label: 'Embed snippet' },
236
224
  ];
225
+ // The permalink codec lives in its own module (`fsl_permalink.ts`); re-export so
226
+ // existing importers — and the 5.151 toolbar tests — keep resolving these here.
237
227
  /**
238
228
  * A paste-able HTML snippet that renders the given FSL from the CDN builds: an
239
229
  * `<fsl-instance>` reading its source from a `<script type="text/fsl">` child,
240
230
  * with a slotted `<fsl-viz>` for the graph.
241
- *
242
231
  * @example
243
232
  * embed_snippet_for("a -> b;"); // "<script …instance.js …><fsl-instance>…</fsl-instance>"
244
233
  */
@@ -313,7 +302,6 @@ const LAYOUTS = [
313
302
  * Layout / Export / Theme pulldowns (the Layout button shows the current
314
303
  * layout's icon). Standalone (no host) the host-dependent controls disappear.
315
304
  * A trailing slot carries extra buttons.
316
- *
317
305
  * @element fsl-toolbar
318
306
  * @csspart toolbar - The bar container.
319
307
  * @slot - Trailing custom controls.
@@ -342,18 +330,23 @@ class FslToolbar extends LitElement {
342
330
  super.connectedCallback();
343
331
  const host = closest_wc(this, 'instance');
344
332
  this._host = host;
333
+ // eslint-disable-next-line unicorn/require-css-escape -- jsdom provides no CSS global at all and this component must run there; PANELS slot names are static identifiers needing no escaping
345
334
  this._present = host === null ? [] : PANELS.filter(p => host.querySelector(`[slot="${p.slot}"]`) !== null);
346
335
  }
347
- /** Set the theme mode (System/Light/Dark). The host applies the palette + drives
348
- * the editor; the menu stays open so a theme can be picked in the same trip. */
336
+ /**
337
+ * Set the theme mode (System/Light/Dark). The host applies the palette + drives
338
+ * the editor; the menu stays open so a theme can be picked in the same trip.
339
+ */
349
340
  _setMode(mode) {
350
341
  if (this._host !== null) {
351
342
  this._host.theme = mode;
352
343
  }
353
344
  this.requestUpdate();
354
345
  }
355
- /** Select a named theme from the host's registry. The theme-name buttons only
356
- * render when a host exists, so `_host` is non-null here. */
346
+ /**
347
+ * Select a named theme from the host's registry. The theme-name buttons only
348
+ * render when a host exists, so `_host` is non-null here.
349
+ */
357
350
  _setThemeName(name) {
358
351
  this._host.themeName = name;
359
352
  this.requestUpdate();
@@ -364,11 +357,15 @@ class FslToolbar extends LitElement {
364
357
  }
365
358
  this._openMenu = '';
366
359
  }
367
- /** Set the active export destination; the menu stays open so a format can be
368
- * chosen next. */
360
+ /**
361
+ * Set the active export destination; the menu stays open so a format can be
362
+ * chosen next.
363
+ */
369
364
  _setDest(dest) { this._dest = dest; }
370
- /** Emit `fsl-export` with the chosen format's content + the active destination.
371
- * The embedder performs the actual clipboard / file save. */
365
+ /**
366
+ * Emit `fsl-export` with the chosen format's content + the active destination.
367
+ * The embedder performs the actual clipboard / file save.
368
+ */
372
369
  async _export(format) {
373
370
  var _a;
374
371
  const host = this._host;
@@ -378,30 +375,39 @@ class FslToolbar extends LitElement {
378
375
  return;
379
376
  }
380
377
  let content;
381
- if (format === 'dot') {
382
- content = machine_to_dot(host.machine);
383
- }
384
- else if (format === 'json') {
385
- content = JSON.stringify(host.machine.serialize(), null, 2);
386
- }
387
- else if (format === 'svg') {
388
- content = await machine_to_svg_string(host.machine);
389
- }
390
- else if (format === 'permalink') {
391
- content = await permalink_for(host.fsl, (_a = permalink_key_for(host)) !== null && _a !== void 0 ? _a : undefined);
392
- }
393
- else if (format === 'embed') {
394
- content = embed_snippet_for(host.fsl);
395
- }
396
- else {
397
- content = host.fsl;
378
+ switch (format) {
379
+ case 'dot': {
380
+ content = machine_to_dot(host.machine);
381
+ break;
382
+ }
383
+ case 'json': {
384
+ content = JSON.stringify(host.machine.serialize(), null, 2);
385
+ break;
386
+ }
387
+ case 'svg': {
388
+ content = await machine_to_svg_string(host.machine);
389
+ break;
390
+ }
391
+ case 'permalink': {
392
+ content = await permalink_for(host.fsl, (_a = permalink_key_for(host)) !== null && _a !== void 0 ? _a : undefined);
393
+ break;
394
+ }
395
+ case 'embed': {
396
+ content = embed_snippet_for(host.fsl);
397
+ break;
398
+ }
399
+ default: {
400
+ content = host.fsl;
401
+ }
398
402
  }
399
403
  this.dispatchEvent(new CustomEvent('fsl-export', { detail: { format, content, destination }, bubbles: true, composed: true }));
400
404
  }
401
- /** Fire a workspace-action intent (validate / lint) for the consumer to
405
+ /**
406
+ * Fire a workspace-action intent (validate / lint) for the consumer to
402
407
  * fulfill — the toolbar presents the action; the embedder runs it. The
403
408
  * current machine source rides along in the detail as a convenience. The
404
- * buttons only render with a host, so `_host` is non-null here. */
409
+ * buttons only render with a host, so `_host` is non-null here.
410
+ */
405
411
  _fireAction(type) {
406
412
  this.dispatchEvent(new CustomEvent(type, { detail: { fsl: this._host.fsl }, bubbles: true, composed: true }));
407
413
  }
@@ -420,24 +426,30 @@ class FslToolbar extends LitElement {
420
426
  <div class="grp">
421
427
  ${host
422
428
  ? this._present.map(p => html `
423
- <button class="tb icon" aria-pressed=${!host.isPanelHidden(p.slot)} aria-label=${p.label} title=${p.label}
424
- @click=${() => { host.togglePanel(p.slot); this.requestUpdate(); }}>${p.icon}</button>`)
429
+ <button class="tb icon" aria-pressed=${!host.isPanelHidden(p.slot)} aria-label=${p.label} title=${p.label}
430
+ @click=${() => { host.togglePanel(p.slot); this.requestUpdate(); }}>${p.icon}</button>
431
+ `)
425
432
  : ''}
426
433
  </div>
427
434
  ${host ? html `
428
435
  <div class="grp">
429
436
  ${this.noValidate ? '' : html `
430
- <button class="tb icon" aria-label="Validate" title="Validate" @click=${() => this._fireAction('fsl-validate')}>${ICON_VALIDATE}</button>`}
437
+ <button class="tb icon" aria-label="Validate" title="Validate" @click=${() => this._fireAction('fsl-validate')}>${ICON_VALIDATE}</button>
438
+ `}
431
439
  ${this.noLint ? '' : html `
432
- <button class="tb icon" aria-label="Lint" title="Lint" @click=${() => this._fireAction('fsl-lint')}>${ICON_LINT}</button>`}
433
- </div>` : ''}
440
+ <button class="tb icon" aria-label="Lint" title="Lint" @click=${() => this._fireAction('fsl-lint')}>${ICON_LINT}</button>
441
+ `}
442
+ </div>
443
+ ` : ''}
434
444
  <div class="grp">
435
445
  <button class="tb layout" aria-haspopup="true" aria-expanded=${this._openMenu === 'layout'} aria-label="Layout" title="Layout" @click=${() => this._toggleMenu('layout')}>${layoutIcon}<span class="caret">▾</span></button>
436
446
  ${this._openMenu === 'layout' ? html `
437
447
  <div class="menu" role="menu">
438
448
  ${LAYOUTS.map(o => html `
439
- <button role="menuitemradio" aria-checked=${layout === o.value} @click=${() => this._setLayout(o.value)}>${o.icon}${o.label}</button>`)}
440
- </div>` : ''}
449
+ <button role="menuitemradio" aria-checked=${layout === o.value} @click=${() => this._setLayout(o.value)}>${o.icon}${o.label}</button>
450
+ `)}
451
+ </div>
452
+ ` : ''}
441
453
  </div>
442
454
  <div class="grp">
443
455
  <button class="tb icon" aria-haspopup="true" aria-expanded=${this._openMenu === 'export'} aria-label="Export" title="Export" @click=${() => this._toggleMenu('export')}>${ICON_EXPORT}</button>
@@ -445,26 +457,33 @@ class FslToolbar extends LitElement {
445
457
  <div class="menu" role="menu">
446
458
  <button role="menuitemradio" aria-checked=${this._dest === 'clipboard'} @click=${() => this._setDest('clipboard')}>to clipboard</button>
447
459
  ${this.lastDirectory ? html `
448
- <button role="menuitemradio" aria-checked=${this._dest === 'lastdir'} @click=${() => this._setDest('lastdir')}>to ${this.lastDirectory}</button>` : ''}
460
+ <button role="menuitemradio" aria-checked=${this._dest === 'lastdir'} @click=${() => this._setDest('lastdir')}>to ${this.lastDirectory}</button>
461
+ ` : ''}
449
462
  <button role="menuitemradio" aria-checked=${this._dest === 'pick'} @click=${() => this._setDest('pick')}>choose directory…</button>
450
463
  <div class="divider" role="separator"></div>
451
464
  ${EXPORT_FORMATS.map(f => html `
452
- <button role="menuitem" @click=${() => this._export(f.value)}>${f.label}</button>`)}
453
- </div>` : ''}
465
+ <button role="menuitem" @click=${() => this._export(f.value)}>${f.label}</button>
466
+ `)}
467
+ </div>
468
+ ` : ''}
454
469
  </div>
455
470
  <div class="grp">
456
471
  <button class="tb icon" aria-haspopup="true" aria-expanded=${this._openMenu === 'theme'} aria-label="Theme" title="Theme" @click=${() => this._toggleMenu('theme')}>${ICON_THEME}</button>
457
472
  ${this._openMenu === 'theme' ? html `
458
473
  <div class="menu" role="menu">
459
474
  ${THEME_MODES.map(m => html `
460
- <button role="menuitemradio" aria-checked=${mode === m.value} @click=${() => this._setMode(m.value)}>${m.label}</button>`)}
475
+ <button role="menuitemradio" aria-checked=${mode === m.value} @click=${() => this._setMode(m.value)}>${m.label}</button>
476
+ `)}
461
477
  <div class="divider" role="separator"></div>
462
478
  ${themeNames.map(n => html `
463
- <button role="menuitemradio" aria-checked=${themeName === n} @click=${() => this._setThemeName(n)}>${n}</button>`)}
464
- </div>` : ''}
479
+ <button role="menuitemradio" aria-checked=${themeName === n} @click=${() => this._setThemeName(n)}>${n}</button>
480
+ `)}
481
+ </div>
482
+ ` : ''}
465
483
  </div>
466
484
  <slot></slot>
467
- </div>`;
485
+ </div>
486
+ `;
468
487
  }
469
488
  }
470
489
  FslToolbar.styles = css `
@@ -550,10 +569,8 @@ const ACTION_EVENTS = ['fsl-transition', 'fsl-machine-rebuilt'];
550
569
  * appear, and each group is omitted when empty, so a self-loop-only state shows
551
570
  * just its actions and a terminal state shows `no actions available`. Standalone
552
571
  * (no host ancestor) renders empty.
553
- *
554
572
  * @element fsl-actions
555
573
  * @csspart actions - The container.
556
- *
557
574
  * @example
558
575
  * // For `A 'x' -> B; A 'y' => C; A ~> D;` while in A:
559
576
  * // Actions: [ x ] [ y ]
@@ -603,7 +620,7 @@ class FslActions extends LitElement {
603
620
  if (e.from !== current || e.to === current) {
604
621
  continue;
605
622
  } // only non-self exits from here
606
- const to = String(e.to);
623
+ const to = e.to;
607
624
  if (e.main_path) {
608
625
  main.add(to);
609
626
  }
@@ -632,8 +649,10 @@ class FslActions extends LitElement {
632
649
  }
633
650
  else {
634
651
  host.transition(to);
635
- } }}>→ ${to}</button>`)}
636
- </div>`;
652
+ } }}>→ ${to}</button>
653
+ `)}
654
+ </div>
655
+ `;
637
656
  }
638
657
  render() {
639
658
  const host = this._host;
@@ -650,12 +669,15 @@ class FslActions extends LitElement {
650
669
  <div class="group">
651
670
  <div class="label">Actions</div>
652
671
  ${this._actions.map(a => html `
653
- <button class="act" @click=${() => host.do(a)}>${a}</button>`)}
654
- </div>`}
672
+ <button class="act" @click=${() => host.do(a)}>${a}</button>
673
+ `)}
674
+ </div>
675
+ `}
655
676
  ${this._group(host, 'Main', this._main, false)}
656
677
  ${this._group(host, 'Transitions', this._regular, false)}
657
678
  ${this._group(host, 'Forced', this._forced, true)}
658
- </div>`;
679
+ </div>
680
+ `;
659
681
  }
660
682
  }
661
683
  FslActions.styles = css `
@@ -718,7 +740,6 @@ function plural(n, word) {
718
740
  * `fsl-machine-rebuilt`, so the footer survives a live rebuild (#1387). A
719
741
  * default slot carries embedder status. Standalone (no `<fsl-instance>`
720
742
  * ancestor) it renders just the slot.
721
- *
722
743
  * @element fsl-footer
723
744
  * @csspart bar - The footer bar container.
724
745
  * @slot - Trailing custom status, right-aligned.
@@ -773,15 +794,17 @@ class FslFooter extends LitElement {
773
794
  this._host = null;
774
795
  }
775
796
  }
776
- /** Recompute the machine-derived counts: local transitions out of the current
797
+ /**
798
+ * Recompute the machine-derived counts: local transitions out of the current
777
799
  * state, and the global action / action-start / transition totals. Only
778
- * called while a host is attached, so `_host` is non-null here. */
800
+ * called while a host is attached, so `_host` is non-null here.
801
+ */
779
802
  _syncMachine() {
780
803
  var _a;
781
804
  const host = this._host;
782
805
  const current = (_a = host.getAttribute('current-state')) !== null && _a !== void 0 ? _a : '';
783
806
  const edges = host.machine.list_edges();
784
- this._transitions = edges.filter(e => String(e.from) === current).length;
807
+ this._transitions = edges.filter(e => e.from === current).length;
785
808
  const actionEdges = edges.filter(e => typeof e.action === 'string');
786
809
  this._gStarts = actionEdges.length;
787
810
  this._gActions = new Set(actionEdges.map(e => e.action)).size;
@@ -795,7 +818,8 @@ class FslFooter extends LitElement {
795
818
  ${this._complete ? html `<span class="badge">complete</span>` : ''}
796
819
  <span class="spacer"></span>
797
820
  <slot></slot>
798
- </div>`;
821
+ </div>
822
+ `;
799
823
  }
800
824
  }
801
825
  FslFooter.styles = css `
@@ -848,7 +872,6 @@ var __decorate$5 = (undefined && undefined.__decorate) || function (decorators,
848
872
  * `<details>` sections). Presentational and self-contained — it owns no machine
849
873
  * binding. The reflected `open` attribute drives visibility, so embedders can
850
874
  * animate it (e.g. a width transition on the host) purely from CSS.
851
- *
852
875
  * @element fsl-help
853
876
  * @csspart drawer - The drawer container.
854
877
  * @csspart head - The header bar.
@@ -878,7 +901,8 @@ class FslHelp extends LitElement {
878
901
  <button class="close" part="close" @click=${this._onClose} aria-label="Close documentation" title="Close">&times;</button>
879
902
  </div>
880
903
  <div class="body" part="body"><slot></slot></div>
881
- </aside>`;
904
+ </aside>
905
+ `;
882
906
  }
883
907
  }
884
908
  FslHelp.styles = css `
@@ -925,7 +949,6 @@ var __decorate$4 = (undefined && undefined.__decorate) || function (decorators,
925
949
  * transition, #639) and records the host's reflected `current-state`, so it
926
950
  * captures every transition and survives a live machine rebuild without a
927
951
  * machine subscription. Standalone (no host ancestor) renders empty.
928
- *
929
952
  * @element fsl-history
930
953
  * @csspart timeline - The timeline container.
931
954
  */
@@ -966,7 +989,8 @@ class FslHistory extends LitElement {
966
989
  return html `
967
990
  <div class="timeline" part="timeline">
968
991
  ${this._history.map((s, i) => html `${i > 0 ? html `<span class="arrow">→</span>` : ''}<span class="state ${i === last ? 'current' : ''}">${s}</span>`)}
969
- </div>`;
992
+ </div>
993
+ `;
970
994
  }
971
995
  }
972
996
  FslHistory.styles = css `
@@ -1004,10 +1028,8 @@ const JSON_TOKEN_RE = /"(?:\\.|[^"\\])*"|\btrue\b|\bfalse\b|\bnull\b|-?\d+(?:\.\
1004
1028
  * `:`, otherwise a `string`; `true`/`false` are `bool`, `null` is `null`,
1005
1029
  * numbers are `number`, and everything between (braces, commas, whitespace) is
1006
1030
  * `plain`. Driven by the text, not a JSON parse, so it never throws.
1007
- *
1008
1031
  * @param json - A JSON string (typically `JSON.stringify(value, null, 2)`).
1009
1032
  * @returns The tokens in source order; concatenating their `text` reproduces `json`.
1010
- *
1011
1033
  * @example
1012
1034
  * tokenizeJson('{"a": 1}');
1013
1035
  * // [{text:'{',kind:'plain'}, {text:'"a"',kind:'key'}, {text:': ',kind:'plain'},
@@ -1050,7 +1072,6 @@ function tokenizeJson(json) {
1050
1072
  * host's transition / data-change / rebuild DOM events. The panel is bounded and
1051
1073
  * scrolls internally (a self-contained vertical column). Renders `no data` when
1052
1074
  * the machine carries none; standalone (no host) renders empty.
1053
- *
1054
1075
  * @element fsl-data-inspector
1055
1076
  * @csspart inspector - The scrollable container.
1056
1077
  */
@@ -1088,7 +1109,8 @@ class FslDataInspector extends LitElement {
1088
1109
  ${this._data === undefined
1089
1110
  ? html `<span class="empty">no data</span>`
1090
1111
  : html `<pre class="json">${tokenizeJson(JSON.stringify(this._data, null, 2)).map(t => t.kind === 'plain' ? t.text : html `<span class="tok-${t.kind}">${t.text}</span>`)}</pre>`}
1091
- </div>`;
1112
+ </div>
1113
+ `;
1092
1114
  }
1093
1115
  }
1094
1116
  FslDataInspector.styles = css `
@@ -1128,7 +1150,6 @@ const LOGGED_EVENTS = [
1128
1150
  * `<fsl-hook-log>` — a running log of a parent `<fsl-instance>`'s machine
1129
1151
  * events, listening to the host's re-emitted `fsl-*` DOM events (#639). Keeps
1130
1152
  * the most recent {@link MAX_ENTRIES}. Standalone (no host ancestor) is empty.
1131
- *
1132
1153
  * @element fsl-hook-log
1133
1154
  * @csspart log - The log container.
1134
1155
  */
@@ -1166,7 +1187,8 @@ class FslHookLog extends LitElement {
1166
1187
  ${this._log.length === 0
1167
1188
  ? html `<span class="empty">no events</span>`
1168
1189
  : this._log.map(name => html `<div class="entry">${name}</div>`)}
1169
- </div>`;
1190
+ </div>
1191
+ `;
1170
1192
  }
1171
1193
  }
1172
1194
  FslHookLog.styles = css `
@@ -1200,7 +1222,6 @@ var __decorate$1 = (undefined && undefined.__decorate) || function (decorators,
1200
1222
  * {@link FslSimulation.interval} ms and stops automatically when the machine
1201
1223
  * reaches a terminal state (no legal actions). Standalone (no host ancestor)
1202
1224
  * the controls are disabled.
1203
- *
1204
1225
  * @element fsl-simulation
1205
1226
  * @csspart sim - The control row.
1206
1227
  * @attr {number} interval - Auto-step period in milliseconds (default 600).
@@ -1261,7 +1282,8 @@ class FslSimulation extends LitElement {
1261
1282
  <button class="btn" @click=${this._step}>Step</button>
1262
1283
  <button class="btn" @click=${this._toggle}>${this._running ? 'Pause' : 'Play'}</button>
1263
1284
  <span class="count ${this._host === null ? 'idle' : ''}">${this._steps} step${this._steps === 1 ? '' : 's'}</span>
1264
- </div>`;
1285
+ </div>
1286
+ `;
1265
1287
  }
1266
1288
  }
1267
1289
  FslSimulation.styles = css `
@@ -1297,7 +1319,6 @@ __decorate$1([
1297
1319
  * content }`; the embedder decides what to do with it (copy, download, show).
1298
1320
  * Formats: Graphviz `dot` (via `machine_to_dot`), `json` (the machine's
1299
1321
  * `serialize()`), and `fsl` (the source). Standalone is inert.
1300
- *
1301
1322
  * @element fsl-export
1302
1323
  * @csspart export - The button row.
1303
1324
  * @fires {CustomEvent<{format: FslExportFormat, content: string}>} fsl-export
@@ -1334,7 +1355,8 @@ class FslExport extends LitElement {
1334
1355
  <button class="btn" @click=${() => this._emit('dot')}>DOT</button>
1335
1356
  <button class="btn" @click=${() => this._emit('json')}>JSON</button>
1336
1357
  <button class="btn" @click=${() => this._emit('fsl')}>FSL</button>
1337
- </div>`;
1358
+ </div>
1359
+ `;
1338
1360
  }
1339
1361
  }
1340
1362
  FslExport.styles = css `
@@ -1366,7 +1388,6 @@ var __decorate = (undefined && undefined.__decorate) || function (decorators, ta
1366
1388
  * host's `.fsl` source (never touching the live machine) and renders
1367
1389
  * aggregate run statistics in-panel. Standalone (no host) the controls are
1368
1390
  * disabled.
1369
- *
1370
1391
  * @element fsl-stochastic
1371
1392
  * @csspart controls - The control row.
1372
1393
  * @fires fsl-stochastic-complete - CustomEvent<JssmStochasticSummary> after a run.
@@ -1415,7 +1436,6 @@ class FslStochastic extends LitElement {
1415
1436
  *
1416
1437
  * Falls back to immediate (synchronous chunk) scheduling under jsdom where
1417
1438
  * `requestAnimationFrame` is undefined.
1418
- *
1419
1439
  * @example
1420
1440
  * panel.runs = 100;
1421
1441
  * await panel.play(); // resolves when all 100 runs are done
@@ -1534,7 +1554,6 @@ class FslStochastic extends LitElement {
1534
1554
  /**
1535
1555
  * Fold accumulated counters into a rendered summary. Shared by {@link play}
1536
1556
  * for incremental rendering during animation.
1537
- *
1538
1557
  * @param state_visits - Accumulated visit counts per state name.
1539
1558
  * @param edge_traversals - Accumulated traversal counts per edge key.
1540
1559
  * @param path_lengths - Lengths of completed (terminated) paths.
@@ -1562,13 +1581,14 @@ class FslStochastic extends LitElement {
1562
1581
  }
1563
1582
  _bars() {
1564
1583
  const frac = this._summary.state_visit_fraction;
1565
- const rows = [...frac.entries()].sort((a, b) => b[1] - a[1]);
1584
+ const rows = [...frac].sort((a, b) => b[1] - a[1]);
1566
1585
  return html `${rows.map(([name, f]) => html `
1567
1586
  <div class="bar-row">
1568
1587
  <span>${name}</span>
1569
1588
  <span class="track"><span class="bar" style="width:${(f * 100).toFixed(1)}%"></span></span>
1570
1589
  <span>${(f * 100).toFixed(1)}%</span>
1571
- </div>`)}`;
1590
+ </div>
1591
+ `)}`;
1572
1592
  }
1573
1593
  _panes() {
1574
1594
  const s = this._summary;
@@ -1580,7 +1600,8 @@ class FslStochastic extends LitElement {
1580
1600
  <div><strong>State visits</strong></div>
1581
1601
  ${this._bars()}
1582
1602
  ${mc ? html `<div>Reached terminal: ${reached}% · Hit cap: ${capped}%</div>` : html `<div class="muted">steady-state distribution</div>`}
1583
- </div>`;
1603
+ </div>
1604
+ `;
1584
1605
  }
1585
1606
  render() {
1586
1607
  const disabled = this._host === null;
@@ -1599,7 +1620,8 @@ class FslStochastic extends LitElement {
1599
1620
  <button class="btn" ?disabled=${disabled} @click=${this._togglePlay}>${this._playing ? 'Pause' : 'Play'}</button>
1600
1621
  </div>
1601
1622
  ${this._error ? html `<div class="error">${this._error}</div>` : ''}
1602
- ${this._summary && !this._error ? this._panes() : html `<div class="panes muted">No run yet.</div>`}`;
1623
+ ${this._summary && !this._error ? this._panes() : html `<div class="panes muted">No run yet.</div>`}
1624
+ `;
1603
1625
  }
1604
1626
  }
1605
1627
  FslStochastic.styles = css `
package/jssm.cli.d.cts CHANGED
@@ -5,7 +5,6 @@
5
5
  * `--target` enum derive from it, so a new target is declared in exactly one
6
6
  * place. Future targets (mermaid, plantuml, scxml, ascii, fsl) land here in
7
7
  * v0.2+.
8
- *
9
8
  * @example
10
9
  * RENDER_TARGETS.includes('gif' as RenderTarget); // true
11
10
  */
@@ -81,7 +80,6 @@ declare class RasterizationUnsupportedError extends RenderError {
81
80
  * Returns a discriminated union: `kind: 'text'` for SVG / DOT / HTML, and
82
81
  * `kind: 'raster'` for PNG / JPEG / GIF. Use `kind` to narrow before
83
82
  * accessing `content` or `buffer`.
84
- *
85
83
  * @param fsl - FSL source text
86
84
  * @param opts.target - Output format ('svg' | 'dot' | 'png' | 'jpeg' | 'html' | 'gif')
87
85
  * @param opts.width - Fit raster output to this pixel width (raster only)
@@ -93,7 +91,6 @@ declare class RasterizationUnsupportedError extends RenderError {
93
91
  * @returns RenderResult, discriminated by `kind`
94
92
  * @throws RenderError on parse, render, or target-dispatch failures
95
93
  * @throws RasterizationUnsupportedError on raster targets where no backend exists
96
- *
97
94
  * @example
98
95
  * const r = await render(fslText, { target: 'svg' });
99
96
  * if (r.kind === 'text') console.log(r.content);
@@ -115,11 +112,9 @@ type RenderSetItem = RenderSetItemOk | RenderSetItemErr;
115
112
  * Render multiple FSL source strings in parallel, returning one result
116
113
  * per input. Errors are captured per-input rather than aborting the whole
117
114
  * batch: callers can inspect which inputs succeeded and which failed.
118
- *
119
115
  * @param inputs - Array of FSL source strings
120
116
  * @param opts - Render options applied to every input
121
117
  * @returns Array of per-input results, same length and order as `inputs`
122
- *
123
118
  * @example
124
119
  * const results = await renderSet([fsl1, fsl2], { target: 'svg' });
125
120
  * for (const item of results) {
@@ -158,14 +153,11 @@ interface ParseResult<S extends ParseSpec> {
158
153
  * -b boolean short flag
159
154
  * -- terminate flag parsing; remaining args are positional
160
155
  * - positional (stdin sentinel)
161
- *
162
156
  * @param argv - The argument array to parse (e.g. process.argv.slice(2))
163
157
  * @param spec - The flag specification describing accepted flags, their types, and defaults
164
158
  * @returns A ParseResult containing positional args, parsed flag values, and a helpText() generator
165
- *
166
159
  * @throws Error if an unknown flag is seen, an enum value mismatches,
167
160
  * or a numeric flag receives a non-numeric value.
168
- *
169
161
  * @example
170
162
  * ```ts
171
163
  * const spec = {