jssm 5.162.11 → 5.162.13
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/README.md +8 -7
- package/custom-elements.json +52 -10
- package/dist/cdn/instance.js +1 -1
- package/dist/cdn/viz.js +17 -1
- package/dist/cli/fsl-export-system-prompt.cjs +1 -1
- package/dist/cli/fsl-render.cjs +1 -1
- package/dist/cli/fsl.cjs +1 -1
- package/dist/cli/lib.cjs +1 -1
- package/dist/cli/lib.mjs +1 -1
- package/dist/deno/README.md +8 -7
- package/dist/deno/fsl_markdown_fence.d.ts +20 -3
- package/dist/deno/jssm.d.ts +14 -1
- package/dist/deno/jssm.js +1 -1
- package/dist/deno/jssm_compiler.d.ts +21 -4
- package/dist/deno/jssm_types.d.ts +27 -1
- package/dist/deno/jssm_viz.d.ts +66 -9
- package/dist/fence/fence.js +83 -32
- package/dist/jssm.es5.cjs +1 -1
- package/dist/jssm.es5.iife.js +1 -1
- package/dist/jssm.es6.mjs +1 -1
- package/dist/jssm_viz.cjs +1 -1
- package/dist/jssm_viz.iife.cjs +1 -1
- package/dist/jssm_viz.mjs +1 -1
- package/dist/wc/instance.js +69 -23
- package/dist/wc/viz.js +54 -9
- package/dist/wc/widgets.define.js +4 -2
- package/dist/wc/widgets.js +196 -43
- package/jssm.es5.d.cts +80 -8
- package/jssm.es6.d.ts +80 -8
- package/jssm.fence.d.ts +13 -1
- package/jssm_viz.es5.d.cts +79 -10
- package/jssm_viz.es6.d.ts +79 -10
- package/package.json +2 -1
package/dist/wc/instance.js
CHANGED
|
@@ -661,6 +661,8 @@ function permalink_key_for(host) {
|
|
|
661
661
|
|
|
662
662
|
/** Debounce before a live edit is written to the URL fragment. */
|
|
663
663
|
const PERMALINK_WRITE_DEBOUNCE_MS = 300;
|
|
664
|
+
/** Marks that this connection has not observed its URL segment yet. */
|
|
665
|
+
const UNOBSERVED_SEGMENT = Symbol('unobserved permalink segment');
|
|
664
666
|
/**
|
|
665
667
|
* Binds an `<fsl-instance>` to a segment of the URL fragment: restores from it
|
|
666
668
|
* on connect and writes back (debounced, via `history.replaceState`) whenever
|
|
@@ -668,8 +670,12 @@ const PERMALINK_WRITE_DEBOUNCE_MS = 300;
|
|
|
668
670
|
* returns `null`), so an `fsl-instance` without an `id`/`uhash` never touches
|
|
669
671
|
* `location`.
|
|
670
672
|
*
|
|
671
|
-
* Echo guard: `
|
|
672
|
-
*
|
|
673
|
+
* Echo guard: `_observed` holds this connection's current URL segment, including
|
|
674
|
+
* explicit absence, so unrelated hash changes and restore→rebuild→write cycles
|
|
675
|
+
* are no-ops. Async operations share a revision: every observed segment change
|
|
676
|
+
* or source write invalidates all older work, and disconnecting invalidates all
|
|
677
|
+
* work. This gives cross-direction latest-operation-wins semantics without
|
|
678
|
+
* allowing unrelated hash changes to cancel valid writes.
|
|
673
679
|
* @example
|
|
674
680
|
* // In an element's constructor:
|
|
675
681
|
* new FslPermalinkSync(this); // reads <el id="k">'s #k=… on connect, writes it on edit
|
|
@@ -677,13 +683,17 @@ const PERMALINK_WRITE_DEBOUNCE_MS = 300;
|
|
|
677
683
|
class FslPermalinkSync {
|
|
678
684
|
constructor(host) {
|
|
679
685
|
this.key = null;
|
|
680
|
-
this.
|
|
686
|
+
this._observed = UNOBSERVED_SEGMENT;
|
|
687
|
+
this._connected = false;
|
|
688
|
+
this._revision = 0;
|
|
681
689
|
this._onRebuilt = () => { this._scheduleWrite(); };
|
|
682
690
|
this._onHashChange = () => { void this._restore(); };
|
|
683
691
|
this.host = host;
|
|
684
692
|
host.addController(this);
|
|
685
693
|
}
|
|
694
|
+
/** Observe the keyed URL segment and attach synchronization listeners. */
|
|
686
695
|
hostConnected() {
|
|
696
|
+
this._connected = true;
|
|
687
697
|
this.key = permalink_key_for(this.host);
|
|
688
698
|
if (this.key === null) {
|
|
689
699
|
return;
|
|
@@ -692,33 +702,48 @@ class FslPermalinkSync {
|
|
|
692
702
|
this.host.addEventListener('fsl-machine-rebuilt', this._onRebuilt);
|
|
693
703
|
addEventListener('hashchange', this._onHashChange);
|
|
694
704
|
}
|
|
705
|
+
/** Invalidate work, forget observations, detach listeners, and cancel timers. */
|
|
695
706
|
hostDisconnected() {
|
|
696
|
-
|
|
697
|
-
|
|
707
|
+
this._connected = false;
|
|
708
|
+
this._revision += 1;
|
|
709
|
+
this._observed = UNOBSERVED_SEGMENT;
|
|
710
|
+
if (this.key !== null) {
|
|
711
|
+
this.host.removeEventListener('fsl-machine-rebuilt', this._onRebuilt);
|
|
712
|
+
removeEventListener('hashchange', this._onHashChange);
|
|
698
713
|
}
|
|
699
|
-
this.host.removeEventListener('fsl-machine-rebuilt', this._onRebuilt);
|
|
700
|
-
removeEventListener('hashchange', this._onHashChange);
|
|
701
714
|
if (this._timer !== undefined) {
|
|
702
715
|
clearTimeout(this._timer);
|
|
703
716
|
this._timer = undefined;
|
|
704
717
|
}
|
|
705
718
|
}
|
|
706
|
-
/**
|
|
719
|
+
/**
|
|
720
|
+
* Observe this instance's newest segment and push it into the host. A changed
|
|
721
|
+
* observation supersedes older restores and writes; an unchanged observation
|
|
722
|
+
* leaves them valid. Declared source is overridden only when this decode is
|
|
723
|
+
* still the latest operation and the actual fragment is unchanged after
|
|
724
|
+
* awaiting it, even when a corresponding hashchange handler is still queued.
|
|
725
|
+
*/
|
|
707
726
|
async _restore() {
|
|
708
|
-
const
|
|
709
|
-
|
|
727
|
+
const key = this.key;
|
|
728
|
+
const segment = read_fragment_param(location.hash, key);
|
|
729
|
+
if (segment === this._observed) {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
this._observed = segment;
|
|
733
|
+
this._revision += 1;
|
|
734
|
+
if (segment === null) {
|
|
710
735
|
return;
|
|
711
736
|
}
|
|
737
|
+
const revision = this._revision;
|
|
712
738
|
try {
|
|
713
739
|
const fsl = await decode_machine(segment);
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
740
|
+
const currentSegment = read_fragment_param(location.hash, key);
|
|
741
|
+
if (!this._connected ||
|
|
742
|
+
revision !== this._revision ||
|
|
743
|
+
key !== this.key ||
|
|
744
|
+
currentSegment !== segment) {
|
|
719
745
|
return;
|
|
720
746
|
}
|
|
721
|
-
this._last = segment;
|
|
722
747
|
this.host.fsl = fsl;
|
|
723
748
|
}
|
|
724
749
|
catch (_a) {
|
|
@@ -726,22 +751,43 @@ class FslPermalinkSync {
|
|
|
726
751
|
// declared source intact. A bad URL never bricks the page.
|
|
727
752
|
}
|
|
728
753
|
}
|
|
754
|
+
/** Debounce the newest rebuild and immediately supersede older restores or writes. */
|
|
729
755
|
_scheduleWrite() {
|
|
756
|
+
const revision = this._revision += 1;
|
|
730
757
|
if (this._timer !== undefined) {
|
|
731
758
|
clearTimeout(this._timer);
|
|
732
759
|
}
|
|
733
|
-
this._timer = setTimeout(() => {
|
|
760
|
+
this._timer = setTimeout(() => {
|
|
761
|
+
this._timer = undefined;
|
|
762
|
+
void this._write(revision);
|
|
763
|
+
}, PERMALINK_WRITE_DEBOUNCE_MS);
|
|
734
764
|
}
|
|
735
|
-
/**
|
|
736
|
-
|
|
765
|
+
/**
|
|
766
|
+
* Encode the scheduled source and merge it into the fragment only when no
|
|
767
|
+
* newer source write, observed segment change, or disconnect superseded it.
|
|
768
|
+
* The actual fragment must also match the observation captured before the
|
|
769
|
+
* await, covering URL changes whose hashchange handler is still queued. A
|
|
770
|
+
* successful write becomes the current observation, preventing echo work.
|
|
771
|
+
*/
|
|
772
|
+
async _write(revision) {
|
|
773
|
+
const key = this.key;
|
|
774
|
+
const fsl = this.host.fsl;
|
|
775
|
+
const observed = this._observed;
|
|
737
776
|
try {
|
|
738
|
-
const segment = await encode_machine(
|
|
739
|
-
|
|
777
|
+
const segment = await encode_machine(fsl);
|
|
778
|
+
const currentSegment = read_fragment_param(location.hash, key);
|
|
779
|
+
if (!this._connected ||
|
|
780
|
+
revision !== this._revision ||
|
|
781
|
+
key !== this.key ||
|
|
782
|
+
currentSegment !== observed) {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
if (segment === observed) {
|
|
740
786
|
return;
|
|
741
787
|
}
|
|
742
|
-
|
|
743
|
-
const fragment = set_fragment_param(location.hash, this.key, segment);
|
|
788
|
+
const fragment = set_fragment_param(location.hash, key, segment);
|
|
744
789
|
history.replaceState(history.state, '', `#${fragment}`);
|
|
790
|
+
this._observed = segment;
|
|
745
791
|
}
|
|
746
792
|
catch (_a) {
|
|
747
793
|
// No compression support: skip the write rather than throw.
|
package/dist/wc/viz.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { css, LitElement, html } from 'lit';
|
|
2
2
|
import { property, state } from 'lit/decorators.js';
|
|
3
3
|
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
4
|
-
import { machine_to_svg_string, fsl_to_svg_string } from 'jssm/viz';
|
|
4
|
+
import { machine_to_svg_string, fsl_to_svg_string, slug_for } from 'jssm/viz';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Shared helpers for the dual-prefix (`fsl-` canonical, `jssm-` synonym)
|
|
@@ -137,6 +137,7 @@ function normalize_viz_error(e) {
|
|
|
137
137
|
* supplying it emits a `console.warn` for developer feedback.
|
|
138
138
|
* @element fsl-viz
|
|
139
139
|
* @cssproperty [--jssm-viz-min-height=100px] - Minimum height of the rendered SVG container.
|
|
140
|
+
* @cssproperty [--jssm-viz-max-height=none] - Maximum height of the control; the rendered SVG stays bounded (aspect preserved, letterboxed) within it. Equivalent to setting `max-height` on the host from outside, without shadow surgery.
|
|
140
141
|
* @fires {CustomEvent<{ message: string; location?: unknown }>} viz-error - Fires when the FSL source fails to parse or render.
|
|
141
142
|
*/
|
|
142
143
|
class FslViz extends LitElement {
|
|
@@ -377,15 +378,27 @@ class FslViz extends LitElement {
|
|
|
377
378
|
* Programmatically highlights one execution trace (a path of state names)
|
|
378
379
|
* through the rendered graph, optionally fading everything off the path.
|
|
379
380
|
*
|
|
380
|
-
* Matches nodes by their Graphviz `<title>`
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
381
|
+
* Matches nodes by their Graphviz `<title>` and edges by the `from->to`
|
|
382
|
+
* title Graphviz emits, applying inline style overrides so the highlight
|
|
383
|
+
* composes over — and is reversible against — the default rendering (see
|
|
384
|
+
* {@link clearHighlights}, which this calls first). No-ops when detached,
|
|
385
|
+
* before the first render, or given an empty trace.
|
|
386
|
+
*
|
|
387
|
+
* Because dot generation slugs state names into node identifiers (fsl#1935
|
|
388
|
+
* — `'Wrong Pin'` renders with `<title>wrong-pin</title>`), each trace name
|
|
389
|
+
* is matched in **both** its raw form and its slugged form, using the same
|
|
390
|
+
* `slug_for` the dot generator uses. Display names (`'Red'`, `'Wrong Pin'`,
|
|
391
|
+
* `'Röd'`) and already-slug-form names (`'red'`, `'wrong-pin'`) therefore
|
|
392
|
+
* both work. Names whose slug is empty (e.g. `'!!!'`, which renders under
|
|
393
|
+
* an indexed `node-N` title) are only matchable by passing that literal
|
|
394
|
+
* `node-N` title.
|
|
385
395
|
*
|
|
386
396
|
* ```typescript
|
|
387
397
|
* // Highlight a -> b -> c in green, without dimming the rest:
|
|
388
398
|
* viz.highlightTrace(['a', 'b', 'c'], { color: '#2e7d32', fadeOthers: false });
|
|
399
|
+
*
|
|
400
|
+
* // Display-form names match their slugged titles:
|
|
401
|
+
* viz.highlightTrace(['Wrong Pin', 'Alarm']); // titles wrong-pin, alarm
|
|
389
402
|
* ```
|
|
390
403
|
* @param trace Ordered state names describing the path (e.g.
|
|
391
404
|
* `['A', 'B', 'C']`). Consecutive pairs select the edges
|
|
@@ -408,10 +421,26 @@ class FslViz extends LitElement {
|
|
|
408
421
|
const fadeOthers = options.fadeOthers !== false; // default: true — undefined must fade
|
|
409
422
|
const targetNodes = new Set();
|
|
410
423
|
const targetEdges = new Set();
|
|
411
|
-
|
|
412
|
-
|
|
424
|
+
// fsl#1935: dot generation slugs state names for node identity (see
|
|
425
|
+
// slug_for in jssm_viz.ts), so rendered <title>s carry 'wrong-pin', not
|
|
426
|
+
// 'Wrong Pin'. Match each trace name in both raw and slugged forms; for
|
|
427
|
+
// names already in slug form the two coincide, preserving old behavior.
|
|
428
|
+
const title_forms_of = (name) => {
|
|
429
|
+
const slug = slug_for(name);
|
|
430
|
+
return (slug !== '' && slug !== name) ? [name, slug] : [name];
|
|
431
|
+
};
|
|
432
|
+
for (const [i, name] of trace.entries()) {
|
|
433
|
+
const from_forms = title_forms_of(name);
|
|
434
|
+
for (const form of from_forms) {
|
|
435
|
+
targetNodes.add(form);
|
|
436
|
+
}
|
|
413
437
|
if (i < trace.length - 1) {
|
|
414
|
-
|
|
438
|
+
const to_forms = title_forms_of(trace[i + 1]);
|
|
439
|
+
for (const from_form of from_forms) {
|
|
440
|
+
for (const to_form of to_forms) {
|
|
441
|
+
targetEdges.add(`${from_form}->${to_form}`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
415
444
|
}
|
|
416
445
|
}
|
|
417
446
|
const allNodes = container.querySelectorAll('.node');
|
|
@@ -461,10 +490,18 @@ FslViz.styles = css `
|
|
|
461
490
|
:host {
|
|
462
491
|
display: block;
|
|
463
492
|
min-height: var(--jssm-viz-min-height, 100px);
|
|
493
|
+
/* #1934: embedder sizing seam — cap the control via the custom property
|
|
494
|
+
(or plain external max-height on the host) without shadow surgery. */
|
|
495
|
+
max-height: var(--jssm-viz-max-height, none);
|
|
464
496
|
}
|
|
465
497
|
.container {
|
|
466
498
|
width: 100%;
|
|
467
499
|
height: 100%;
|
|
500
|
+
/* #1934: when the host's height is auto (content-driven under an
|
|
501
|
+
external max-height cap), the percentage heights below collapse to
|
|
502
|
+
auto and stop constraining anything. Inheriting the host's computed
|
|
503
|
+
max-height re-threads the cap down to the svg. */
|
|
504
|
+
max-height: inherit;
|
|
468
505
|
display: flex;
|
|
469
506
|
align-items: center;
|
|
470
507
|
justify-content: center;
|
|
@@ -475,6 +512,14 @@ FslViz.styles = css `
|
|
|
475
512
|
the 90% box centered in the flex container yields the 5% inset. */
|
|
476
513
|
width: 90%;
|
|
477
514
|
height: 90%;
|
|
515
|
+
/* #1934: with an auto-height host the 90% height collapses and the svg
|
|
516
|
+
falls back to ratio-derived (or intrinsic pt) sizing, which can run
|
|
517
|
+
far past an external max-height on the host. The inherited cap keeps
|
|
518
|
+
that fallback bounded; Graphviz SVGs carry viewBox + the default
|
|
519
|
+
preserveAspectRatio, so the capped viewport letterboxes cleanly
|
|
520
|
+
instead of distorting. */
|
|
521
|
+
max-width: 100%;
|
|
522
|
+
max-height: inherit;
|
|
478
523
|
}
|
|
479
524
|
|
|
480
525
|
/* Smoothly animate the inline style overrides applied by highlightTrace()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FslToolbar, FslActions, FslFooter, FslHelp, FslHistory, FslDataInspector, FslHookLog, FslSimulation, FslExport, FslStochastic } from './widgets.js';
|
|
1
|
+
import { FslToolbar, FslActions, FslFooter, FslHelp, FslHistory, FslDataInspector, FslHookLog, FslSimulation, FslExport, FslStochastic, FslInfoPanel } from './widgets.js';
|
|
2
2
|
export * from './widgets.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -45,7 +45,8 @@ function define_canonical(canonical_tag, CanonicalClass) {
|
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* Bundle entry: registers the entire light fsl-* widget suite (toolbar, actions,
|
|
48
|
-
* footer, help, history, data-inspector, hook-log, simulation, export, stochastic
|
|
48
|
+
* footer, help, history, data-inspector, hook-log, simulation, export, stochastic,
|
|
49
|
+
* info-panel) in one import.
|
|
49
50
|
* Canonical `fsl-*` tags only — no deprecated `jssm-*` synonyms. Registration
|
|
50
51
|
* is idempotent, so this composes safely with the per-widget `*.define` modules.
|
|
51
52
|
*/
|
|
@@ -59,3 +60,4 @@ define_canonical('fsl-hook-log', FslHookLog);
|
|
|
59
60
|
define_canonical('fsl-simulation', FslSimulation);
|
|
60
61
|
define_canonical('fsl-export', FslExport);
|
|
61
62
|
define_canonical('fsl-stochastic', FslStochastic);
|
|
63
|
+
define_canonical('fsl-info-panel', FslInfoPanel);
|