jssm 5.152.0 → 5.154.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.
@@ -1090,6 +1090,7 @@ function split_ratio(coord, start, size) {
1090
1090
  * @slot hook-log - Slot for the hook-firing log (`<fsl-hook-log>`, #664).
1091
1091
  * @slot effective-properties - Slot for the resolved-properties panel (`<fsl-effective-properties>`, #665).
1092
1092
  * @slot simulation - Slot for the random-walk simulation (`<fsl-simulation>`, #668).
1093
+ * @slot stochastic - Slot for the stochastic analysis panel (`<fsl-stochastic>`, fsl#1384).
1093
1094
  * @slot export - Slot for the export menu (`<fsl-export>`, #667).
1094
1095
  * @slot footer - Footer slot.
1095
1096
  */
@@ -1967,6 +1968,7 @@ class FslInstance extends LitElement {
1967
1968
  ${docked ? '' : html `<section class="hook-log" ?hidden=${h('hook-log')}><slot name="hook-log"></slot></section>`}
1968
1969
  <section class="effective-properties" ?hidden=${h('effective-properties')}><slot name="effective-properties"></slot></section>
1969
1970
  <section class="simulation" ?hidden=${h('simulation')}><slot name="simulation"></slot></section>
1971
+ <section class="stochastic" ?hidden=${h('stochastic')}><slot name="stochastic"></slot></section>
1970
1972
  <section class="export" ?hidden=${h('export')}><slot name="export"></slot></section>
1971
1973
  `;
1972
1974
  }
@@ -1,4 +1,4 @@
1
- import { FslToolbar, FslActions, FslFooter, FslHelp, FslHistory, FslDataInspector, FslHookLog, FslSimulation, FslExport } from './widgets.js';
1
+ import { FslToolbar, FslActions, FslFooter, FslHelp, FslHistory, FslDataInspector, FslHookLog, FslSimulation, FslExport, FslStochastic } from './widgets.js';
2
2
  export * from './widgets.js';
3
3
 
4
4
  /**
@@ -50,7 +50,7 @@ function define_canonical(canonical_tag, CanonicalClass) {
50
50
 
51
51
  /**
52
52
  * Bundle entry: registers the entire light fsl-* widget suite (toolbar, actions,
53
- * footer, help, history, data-inspector, hook-log, simulation, export) in one import.
53
+ * footer, help, history, data-inspector, hook-log, simulation, export, stochastic) in one import.
54
54
  * Canonical `fsl-*` tags only — no deprecated `jssm-*` synonyms. Registration
55
55
  * is idempotent, so this composes safely with the per-widget `*.define` modules.
56
56
  */
@@ -63,3 +63,4 @@ define_canonical('fsl-data-inspector', FslDataInspector);
63
63
  define_canonical('fsl-hook-log', FslHookLog);
64
64
  define_canonical('fsl-simulation', FslSimulation);
65
65
  define_canonical('fsl-export', FslExport);
66
+ define_canonical('fsl-stochastic', FslStochastic);
@@ -1,6 +1,7 @@
1
1
  import { css, html, LitElement } from 'lit';
2
2
  import { state, property } from 'lit/decorators.js';
3
3
  import { machine_to_dot, machine_to_svg_string } from 'jssm/viz';
4
+ import { STOCHASTIC_DEFAULT_RUNS, STOCHASTIC_DEFAULT_MAX_STEPS, sm } from 'jssm';
4
5
 
5
6
  /**
6
7
  * Shared FSL appearance contract — the `--fsl-*` design-token vocabulary.
@@ -193,7 +194,7 @@ async function permalink_for(fsl, key = DEFAULT_PERMALINK_KEY, href = location.h
193
194
  return `${href.split('#')[0]}#${fragment}`;
194
195
  }
195
196
 
196
- var __decorate$7 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
197
+ var __decorate$8 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
197
198
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
198
199
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
199
200
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -496,23 +497,23 @@ FslToolbar.styles = css `
496
497
  .spacer { flex: 1; }
497
498
  ${fslTokens}
498
499
  `;
499
- __decorate$7([
500
+ __decorate$8([
500
501
  state()
501
502
  ], FslToolbar.prototype, "_openMenu", void 0);
502
- __decorate$7([
503
+ __decorate$8([
503
504
  state()
504
505
  ], FslToolbar.prototype, "_dest", void 0);
505
- __decorate$7([
506
+ __decorate$8([
506
507
  property({ attribute: false })
507
508
  ], FslToolbar.prototype, "lastDirectory", void 0);
508
- __decorate$7([
509
+ __decorate$8([
509
510
  property({ type: Boolean, attribute: 'no-validate' })
510
511
  ], FslToolbar.prototype, "noValidate", void 0);
511
- __decorate$7([
512
+ __decorate$8([
512
513
  property({ type: Boolean, attribute: 'no-lint' })
513
514
  ], FslToolbar.prototype, "noLint", void 0);
514
515
 
515
- var __decorate$6 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
516
+ var __decorate$7 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
516
517
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
517
518
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
518
519
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -662,20 +663,20 @@ FslActions.styles = css `
662
663
  .empty { color: var(--_fsl-muted); font-style: italic; }
663
664
  ${fslTokens}
664
665
  `;
665
- __decorate$6([
666
+ __decorate$7([
666
667
  state()
667
668
  ], FslActions.prototype, "_actions", void 0);
668
- __decorate$6([
669
+ __decorate$7([
669
670
  state()
670
671
  ], FslActions.prototype, "_main", void 0);
671
- __decorate$6([
672
+ __decorate$7([
672
673
  state()
673
674
  ], FslActions.prototype, "_regular", void 0);
674
- __decorate$6([
675
+ __decorate$7([
675
676
  state()
676
677
  ], FslActions.prototype, "_forced", void 0);
677
678
 
678
- var __decorate$5 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
679
+ var __decorate$6 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
679
680
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
680
681
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
681
682
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -791,32 +792,32 @@ FslFooter.styles = css `
791
792
  .spacer { flex: 1; }
792
793
  ${fslTokens}
793
794
  `;
794
- __decorate$5([
795
+ __decorate$6([
795
796
  state()
796
797
  ], FslFooter.prototype, "_state", void 0);
797
- __decorate$5([
798
+ __decorate$6([
798
799
  state()
799
800
  ], FslFooter.prototype, "_actions", void 0);
800
- __decorate$5([
801
+ __decorate$6([
801
802
  state()
802
803
  ], FslFooter.prototype, "_transitions", void 0);
803
- __decorate$5([
804
+ __decorate$6([
804
805
  state()
805
806
  ], FslFooter.prototype, "_terminal", void 0);
806
- __decorate$5([
807
+ __decorate$6([
807
808
  state()
808
809
  ], FslFooter.prototype, "_complete", void 0);
809
- __decorate$5([
810
+ __decorate$6([
810
811
  state()
811
812
  ], FslFooter.prototype, "_gActions", void 0);
812
- __decorate$5([
813
+ __decorate$6([
813
814
  state()
814
815
  ], FslFooter.prototype, "_gStarts", void 0);
815
- __decorate$5([
816
+ __decorate$6([
816
817
  state()
817
818
  ], FslFooter.prototype, "_gTransitions", void 0);
818
819
 
819
- var __decorate$4 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
820
+ var __decorate$5 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
820
821
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
821
822
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
822
823
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -885,14 +886,14 @@ FslHelp.styles = css `
885
886
  .body { overflow-y: auto; padding: 0.5rem 0.9rem; font-size: 0.86rem; line-height: 1.5; }
886
887
  ${fslTokens}
887
888
  `;
888
- __decorate$4([
889
+ __decorate$5([
889
890
  property({ type: Boolean, reflect: true })
890
891
  ], FslHelp.prototype, "open", void 0);
891
- __decorate$4([
892
+ __decorate$5([
892
893
  property({ type: String })
893
894
  ], FslHelp.prototype, "heading", void 0);
894
895
 
895
- var __decorate$3 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
896
+ var __decorate$4 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
896
897
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
897
898
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
898
899
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -965,11 +966,11 @@ FslHistory.styles = css `
965
966
  .empty { color: var(--_fsl-muted); font-style: italic; padding: 0.5rem 0.7rem; }
966
967
  ${fslTokens}
967
968
  `;
968
- __decorate$3([
969
+ __decorate$4([
969
970
  state()
970
971
  ], FslHistory.prototype, "_history", void 0);
971
972
 
972
- var __decorate$2 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
973
+ var __decorate$3 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
973
974
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
974
975
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
975
976
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -1089,11 +1090,11 @@ FslDataInspector.styles = css `
1089
1090
  .tok-bool, .tok-null { color: var(--fsl-color-json-atom, #c2185b); }
1090
1091
  ${fslTokens}
1091
1092
  `;
1092
- __decorate$2([
1093
+ __decorate$3([
1093
1094
  state()
1094
1095
  ], FslDataInspector.prototype, "_data", void 0);
1095
1096
 
1096
- var __decorate$1 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1097
+ var __decorate$2 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1097
1098
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1098
1099
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1099
1100
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -1163,11 +1164,11 @@ FslHookLog.styles = css `
1163
1164
  .empty { color: var(--_fsl-muted); font-style: italic; }
1164
1165
  ${fslTokens}
1165
1166
  `;
1166
- __decorate$1([
1167
+ __decorate$2([
1167
1168
  state()
1168
1169
  ], FslHookLog.prototype, "_log", void 0);
1169
1170
 
1170
- var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1171
+ var __decorate$1 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1171
1172
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1172
1173
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1173
1174
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -1261,13 +1262,13 @@ FslSimulation.styles = css `
1261
1262
  .count.idle { font-style: italic; }
1262
1263
  ${fslTokens}
1263
1264
  `;
1264
- __decorate([
1265
+ __decorate$1([
1265
1266
  property({ type: Number })
1266
1267
  ], FslSimulation.prototype, "interval", void 0);
1267
- __decorate([
1268
+ __decorate$1([
1268
1269
  state()
1269
1270
  ], FslSimulation.prototype, "_running", void 0);
1270
- __decorate([
1271
+ __decorate$1([
1271
1272
  state()
1272
1273
  ], FslSimulation.prototype, "_steps", void 0);
1273
1274
 
@@ -1334,4 +1335,300 @@ FslExport.styles = css `
1334
1335
  ${fslTokens}
1335
1336
  `;
1336
1337
 
1337
- export { FslActions, FslDataInspector, FslExport, FslFooter, FslHelp, FslHistory, FslHookLog, FslSimulation, FslToolbar };
1338
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1339
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1340
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1341
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1342
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1343
+ };
1344
+ /**
1345
+ * `<fsl-stochastic>` — a statistical/Monte-Carlo explorer for a parent
1346
+ * `<fsl-instance>` (fsl#1384). Builds its own throwaway machine from the
1347
+ * host's `.fsl` source (never touching the live machine) and renders
1348
+ * aggregate run statistics in-panel. Standalone (no host) the controls are
1349
+ * disabled.
1350
+ *
1351
+ * @element fsl-stochastic
1352
+ * @csspart controls - The control row.
1353
+ * @fires fsl-stochastic-complete - CustomEvent<JssmStochasticSummary> after a run.
1354
+ */
1355
+ class FslStochastic extends LitElement {
1356
+ constructor() {
1357
+ super(...arguments);
1358
+ /** Run-count (montecarlo). Defaults from editor_config().stochastic_run_count. */
1359
+ this.runs = STOCHASTIC_DEFAULT_RUNS;
1360
+ /** Per-run step cap (montecarlo) / walk length (steady_state). */
1361
+ this.maxSteps = STOCHASTIC_DEFAULT_MAX_STEPS;
1362
+ /** Seed string ('' = time-based). Kept as string so the field can be blank. */
1363
+ this.seed = '';
1364
+ /** Run mode. */
1365
+ this.mode = 'montecarlo';
1366
+ this._summary = null;
1367
+ this._error = null;
1368
+ this._host = null;
1369
+ this._playing = false;
1370
+ /** Execute a batch synchronously and render the aggregates. */
1371
+ this.run = () => {
1372
+ if (this._host === null) {
1373
+ return;
1374
+ }
1375
+ let machine;
1376
+ try {
1377
+ machine = sm `${this._host.fsl}`;
1378
+ }
1379
+ catch (_a) {
1380
+ this._error = 'Cannot run on invalid FSL source.';
1381
+ this._summary = null;
1382
+ return;
1383
+ }
1384
+ this._error = null;
1385
+ const seedNum = this.seed.trim() === '' ? undefined : Number(this.seed);
1386
+ this._summary = machine.stochastic_summary({
1387
+ mode: this.mode, runs: this.runs, max_steps: this.maxSteps, seed: seedNum,
1388
+ });
1389
+ this.dispatchEvent(new CustomEvent('fsl-stochastic-complete', {
1390
+ detail: this._summary, bubbles: true, composed: true,
1391
+ }));
1392
+ };
1393
+ /**
1394
+ * Animate the batch: accumulate runs incrementally via `requestAnimationFrame`,
1395
+ * redrawing as they land. Resolves when the batch completes or is paused-to-stop.
1396
+ *
1397
+ * Falls back to immediate (synchronous chunk) scheduling under jsdom where
1398
+ * `requestAnimationFrame` is undefined.
1399
+ *
1400
+ * @example
1401
+ * panel.runs = 100;
1402
+ * await panel.play(); // resolves when all 100 runs are done
1403
+ */
1404
+ this.play = () => new Promise((resolve) => {
1405
+ if (this._host === null) {
1406
+ resolve();
1407
+ return;
1408
+ }
1409
+ let machine;
1410
+ try {
1411
+ machine = sm `${this._host.fsl}`;
1412
+ }
1413
+ catch (_a) {
1414
+ this._error = 'Cannot run on invalid FSL source.';
1415
+ this._summary = null;
1416
+ resolve();
1417
+ return;
1418
+ }
1419
+ this._error = null;
1420
+ if (this.seed.trim() !== '') {
1421
+ machine.rng_seed = Number(this.seed);
1422
+ }
1423
+ const effective_seed = machine.rng_seed;
1424
+ const state_visits = new Map();
1425
+ const edges = new Map();
1426
+ const path_lengths = [];
1427
+ let reached = 0;
1428
+ let capped = 0;
1429
+ let runs = 0;
1430
+ const iter = machine.stochastic_runs({ mode: this.mode, runs: this.runs, max_steps: this.maxSteps });
1431
+ this._playing = true;
1432
+ const CHUNK = 50;
1433
+ const schedule = (fn) => {
1434
+ if (typeof requestAnimationFrame === 'function') {
1435
+ requestAnimationFrame(fn);
1436
+ }
1437
+ else {
1438
+ fn();
1439
+ }
1440
+ };
1441
+ const tick = () => {
1442
+ var _a, _b;
1443
+ if (!this._playing) {
1444
+ resolve();
1445
+ return;
1446
+ }
1447
+ for (let i = 0; i < CHUNK; i++) {
1448
+ const next = iter.next();
1449
+ if (next.done) {
1450
+ this._commit(state_visits, edges, path_lengths, reached, capped, runs, effective_seed);
1451
+ this._playing = false;
1452
+ resolve();
1453
+ return;
1454
+ }
1455
+ const r = next.value;
1456
+ runs += 1;
1457
+ for (const s of r.states) {
1458
+ state_visits.set(s, ((_a = state_visits.get(s)) !== null && _a !== void 0 ? _a : 0) + 1);
1459
+ }
1460
+ for (const e of r.edges) {
1461
+ edges.set(e, ((_b = edges.get(e)) !== null && _b !== void 0 ? _b : 0) + 1);
1462
+ }
1463
+ if (this.mode === 'montecarlo') {
1464
+ if (r.terminated) {
1465
+ reached += 1;
1466
+ path_lengths.push(r.length);
1467
+ }
1468
+ else {
1469
+ capped += 1;
1470
+ }
1471
+ }
1472
+ }
1473
+ this._commit(state_visits, edges, path_lengths, reached, capped, runs, effective_seed);
1474
+ schedule(tick);
1475
+ };
1476
+ schedule(tick);
1477
+ });
1478
+ /** Toggle between playing and paused. Starts a new {@link play} batch when idle. */
1479
+ this._togglePlay = () => { if (this._playing) {
1480
+ this._playing = false;
1481
+ }
1482
+ else {
1483
+ void this.play();
1484
+ } };
1485
+ this._onMode = (e) => { this.mode = e.target.value; };
1486
+ this._onRuns = (e) => { this.runs = Number(e.target.value); };
1487
+ this._onMax = (e) => { this.maxSteps = Number(e.target.value); };
1488
+ this._onSeed = (e) => { this.seed = e.target.value; };
1489
+ }
1490
+ connectedCallback() {
1491
+ super.connectedCallback();
1492
+ this._host = closest_wc(this, 'instance');
1493
+ }
1494
+ disconnectedCallback() {
1495
+ super.disconnectedCallback();
1496
+ this._host = null;
1497
+ }
1498
+ /**
1499
+ * Read `editor_config().stochastic_run_count` after the first render so the
1500
+ * host's machine is guaranteed to be built (machine construction happens in
1501
+ * the host's connectedCallback, which may fire after the panel's own
1502
+ * connectedCallback in some environments).
1503
+ */
1504
+ firstUpdated(_changedProperties) {
1505
+ var _a, _b, _c, _d;
1506
+ super.firstUpdated(_changedProperties);
1507
+ try {
1508
+ const declared = (_d = (_c = (_b = (_a = this._host) === null || _a === void 0 ? void 0 : _a.machine) === null || _b === void 0 ? void 0 : _b.editor_config) === null || _c === void 0 ? void 0 : _c.call(_b)) === null || _d === void 0 ? void 0 : _d.stochastic_run_count;
1509
+ if (typeof declared === 'number') {
1510
+ this.runs = declared;
1511
+ }
1512
+ }
1513
+ catch ( /* machine not ready — leave default */_e) { /* machine not ready — leave default */ }
1514
+ }
1515
+ /**
1516
+ * Fold accumulated counters into a rendered summary. Shared by {@link play}
1517
+ * for incremental rendering during animation.
1518
+ *
1519
+ * @param state_visits - Accumulated visit counts per state name.
1520
+ * @param edge_traversals - Accumulated traversal counts per edge key.
1521
+ * @param path_lengths - Lengths of completed (terminated) paths.
1522
+ * @param terminal_reached - Count of runs that reached a terminal state.
1523
+ * @param capped - Count of runs that hit the step cap.
1524
+ * @param runs - Total runs processed so far.
1525
+ * @param seed - The effective RNG seed used for this batch.
1526
+ */
1527
+ _commit(state_visits, edge_traversals, path_lengths, terminal_reached, capped, runs, seed) {
1528
+ const total = [...state_visits.values()].reduce((a, b) => a + b, 0);
1529
+ const frac = new Map();
1530
+ for (const [s, c] of state_visits) {
1531
+ frac.set(s, c / total);
1532
+ }
1533
+ const summary = {
1534
+ mode: this.mode, runs, seed,
1535
+ state_visits, state_visit_fraction: frac, edge_traversals,
1536
+ };
1537
+ if (this.mode === 'montecarlo') {
1538
+ summary.path_lengths = path_lengths;
1539
+ summary.terminal_reached = terminal_reached;
1540
+ summary.capped = capped;
1541
+ }
1542
+ this._summary = summary;
1543
+ }
1544
+ _bars() {
1545
+ const frac = this._summary.state_visit_fraction;
1546
+ const rows = [...frac.entries()].sort((a, b) => b[1] - a[1]);
1547
+ return html `${rows.map(([name, f]) => html `
1548
+ <div class="bar-row">
1549
+ <span>${name}</span>
1550
+ <span class="track"><span class="bar" style="width:${(f * 100).toFixed(1)}%"></span></span>
1551
+ <span>${(f * 100).toFixed(1)}%</span>
1552
+ </div>`)}`;
1553
+ }
1554
+ _panes() {
1555
+ const s = this._summary;
1556
+ const mc = s.mode === 'montecarlo';
1557
+ const reached = mc && s.runs > 0 ? Math.round((s.terminal_reached / s.runs) * 100) : 0;
1558
+ const capped = mc && s.runs > 0 ? Math.round((s.capped / s.runs) * 100) : 0;
1559
+ return html `
1560
+ <div class="panes">
1561
+ <div><strong>State visits</strong></div>
1562
+ ${this._bars()}
1563
+ ${mc ? html `<div>Reached terminal: ${reached}% · Hit cap: ${capped}%</div>` : html `<div class="muted">steady-state distribution</div>`}
1564
+ </div>`;
1565
+ }
1566
+ render() {
1567
+ const disabled = this._host === null;
1568
+ return html `
1569
+ <div class="controls" part="controls">
1570
+ <select @change=${this._onMode} .value=${this.mode}>
1571
+ <option value="montecarlo">Monte Carlo</option>
1572
+ <option value="steady_state">Steady-state</option>
1573
+ </select>
1574
+ ${this.mode === 'montecarlo'
1575
+ ? html `<label>runs <input type="number" .value=${String(this.runs)} @change=${this._onRuns}></label>`
1576
+ : ''}
1577
+ <label>max steps <input type="number" .value=${String(this.maxSteps)} @change=${this._onMax}></label>
1578
+ <label>seed <input type="text" .value=${this.seed} @change=${this._onSeed}></label>
1579
+ <button class="run" ?disabled=${disabled} @click=${this.run}>Run</button>
1580
+ <button class="btn" ?disabled=${disabled} @click=${this._togglePlay}>${this._playing ? 'Pause' : 'Play'}</button>
1581
+ </div>
1582
+ ${this._error ? html `<div class="error">${this._error}</div>` : ''}
1583
+ ${this._summary && !this._error ? this._panes() : html `<div class="panes muted">No run yet.</div>`}`;
1584
+ }
1585
+ }
1586
+ FslStochastic.styles = css `
1587
+ :host { display: block; font: 0.8rem var(--_fsl-font); color: var(--_fsl-text); }
1588
+ .controls {
1589
+ display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
1590
+ padding: 0.4rem 0.6rem; background: var(--_fsl-surface);
1591
+ border-top: 1px solid var(--_fsl-border);
1592
+ }
1593
+ .btn, .run {
1594
+ height: 1.8rem; padding: 0 0.7rem; cursor: pointer; border-radius: 4px;
1595
+ border: 1px solid var(--_fsl-border); background: var(--_fsl-surface);
1596
+ color: var(--_fsl-text); font: 600 0.8rem var(--_fsl-font);
1597
+ }
1598
+ .btn:disabled, .run:disabled { opacity: 0.5; cursor: not-allowed; }
1599
+ input { width: 4.5rem; height: 1.6rem; background: var(--_fsl-surface);
1600
+ color: var(--_fsl-text); border: 1px solid var(--_fsl-border); border-radius: 4px; }
1601
+ .panes { padding: 0.4rem 0.6rem; display: grid; gap: 0.5rem; }
1602
+ .bar-row { display: grid; grid-template-columns: 4rem 1fr 3rem; align-items: center; gap: 0.4rem; }
1603
+ .track { background: color-mix(in srgb, var(--_fsl-text) 8%, var(--_fsl-surface)); border-radius: 3px; height: 0.9rem; }
1604
+ .bar { background: var(--_fsl-accent); height: 0.9rem; border-radius: 3px; }
1605
+ .muted { color: var(--_fsl-muted); }
1606
+ .error { color: var(--_fsl-danger, #c0392b); padding: 0.4rem 0.6rem; }
1607
+ ${fslTokens}
1608
+ `;
1609
+ __decorate([
1610
+ property({ type: Number })
1611
+ ], FslStochastic.prototype, "runs", void 0);
1612
+ __decorate([
1613
+ property({ type: Number })
1614
+ ], FslStochastic.prototype, "maxSteps", void 0);
1615
+ __decorate([
1616
+ property({ type: String })
1617
+ ], FslStochastic.prototype, "seed", void 0);
1618
+ __decorate([
1619
+ property({ type: String })
1620
+ ], FslStochastic.prototype, "mode", void 0);
1621
+ __decorate([
1622
+ state()
1623
+ ], FslStochastic.prototype, "_summary", void 0);
1624
+ __decorate([
1625
+ state()
1626
+ ], FslStochastic.prototype, "_error", void 0);
1627
+ __decorate([
1628
+ state()
1629
+ ], FslStochastic.prototype, "_host", void 0);
1630
+ __decorate([
1631
+ state()
1632
+ ], FslStochastic.prototype, "_playing", void 0);
1633
+
1634
+ export { FslActions, FslDataInspector, FslExport, FslFooter, FslHelp, FslHistory, FslHookLog, FslSimulation, FslStochastic, FslToolbar };