jssm 5.151.0 → 5.151.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/wc/docs.js CHANGED
@@ -122,6 +122,685 @@ const DOCS_PAGES = [
122
122
  ],
123
123
  "body": "\n# Welcome to FSL\n\nFSL is a small language for finite state machines. Most machines are one line.\n\n```fsl {teaches: transitions, run: true}\nRed 'go' -> Green 'go' -> Yellow 'go' -> Red;\n```\n"
124
124
  },
125
+ {
126
+ "id": "tut-api-arrow-introspection",
127
+ "section": "tutorials",
128
+ "title": "API: arrow introspection",
129
+ "order": 86,
130
+ "teaches": [
131
+ "api-arrow-introspection"
132
+ ],
133
+ "mentions": [
134
+ "api-machine",
135
+ "arrow-flavors"
136
+ ],
137
+ "indexTerms": [
138
+ "arrow_direction",
139
+ "arrow_left_kind",
140
+ "arrow_right_kind",
141
+ "API"
142
+ ],
143
+ "body": "\n# API: arrow introspection\n\n`arrow_direction`, `arrow_left_kind`, and `arrow_right_kind` decode an arrow token into its direction and per-side kind — useful when building tools that reason about transition semantics.\n\n```js\nimport { arrow_direction, arrow_right_kind } from 'jssm';\n\narrow_direction('<->'); // 'both'\narrow_right_kind('=>'); // 'main'\n```\n\nThe arrows they decode are the ones you write between states:\n\n```fsl {teaches: api-arrow-introspection, run: true}\na <-> b;\nb => c;\n```\n"
144
+ },
145
+ {
146
+ "id": "tut-api-constants",
147
+ "section": "tutorials",
148
+ "title": "API: constants and vocabularies",
149
+ "order": 84,
150
+ "teaches": [
151
+ "api-constants"
152
+ ],
153
+ "mentions": [
154
+ "api-machine",
155
+ "colors",
156
+ "shapes"
157
+ ],
158
+ "indexTerms": [
159
+ "named_colors",
160
+ "gviz_shapes",
161
+ "constants",
162
+ "FslDirections",
163
+ "API"
164
+ ],
165
+ "body": "\n# API: constants and vocabularies\n\nThe library exports the vocabularies the grammar accepts, so editors and tools never have to hard-code them: `named_colors`, `gviz_shapes`, `shapes`, `FslDirections`, and the `constants` bundle.\n\n```js\nimport { named_colors, gviz_shapes, FslDirections } from 'jssm';\n\nnamed_colors.includes('ForestGreen'); // true\ngviz_shapes.includes('doublecircle'); // true\n```\n\nThese are the exact values that style a machine:\n\n```fsl {teaches: api-constants, run: true}\nstate Go : { color: ForestGreen; shape: doublecircle; };\nStop 'go' -> Go;\n```\n"
166
+ },
167
+ {
168
+ "id": "tut-api-hook-helpers",
169
+ "section": "tutorials",
170
+ "title": "API: hook result helpers",
171
+ "order": 88,
172
+ "teaches": [
173
+ "api-hook-helpers"
174
+ ],
175
+ "mentions": [
176
+ "api-machine",
177
+ "hooks"
178
+ ],
179
+ "indexTerms": [
180
+ "is_hook_rejection",
181
+ "hook result",
182
+ "abstract_hook_step",
183
+ "API"
184
+ ],
185
+ "body": "\n# API: hook result helpers\n\nWhen you attach hooks from JavaScript, the result helpers — `is_hook_rejection`, `is_hook_complex_result`, `abstract_hook_step` — classify what a hook returned so you can react to a rejection or a complex result uniformly.\n\n```js\nimport { sm, is_hook_rejection } from 'jssm';\n\nconst m = sm`a 'go' -> b;`;\nm.hook('a', 'b', () => false); // a hook that rejects the transition\nis_hook_rejection(false); // true\n```\n\nHooks attach to the boundaries of a machine like this one:\n\n```fsl {teaches: api-hook-helpers, run: true}\non exit a do 'log';\na 'go' -> b;\n```\n"
186
+ },
187
+ {
188
+ "id": "tut-api-language-service",
189
+ "section": "tutorials",
190
+ "title": "API: the language service",
191
+ "order": 92,
192
+ "teaches": [
193
+ "api-language-service"
194
+ ],
195
+ "mentions": [
196
+ "api-machine",
197
+ "editor-language-support"
198
+ ],
199
+ "indexTerms": [
200
+ "fslCompletions",
201
+ "fslDiagnostics",
202
+ "fslSemanticSpans",
203
+ "language service",
204
+ "API"
205
+ ],
206
+ "body": "\n# API: the language service\n\nFor building editors, the language-service exports turn FSL text into editor primitives: `fslCompletions` (context-aware suggestions), `fslDiagnostics` (parse/compile errors), and `fslSemanticSpans` (token spans for highlighting).\n\n```js\nimport { fslDiagnostics, fslCompletions } from 'jssm';\n\nfslDiagnostics('a -> '); // reports the parse error\nfslCompletions('machine_', 8); // suggests machine_name, machine_author, …\n```\n\nThey operate on raw FSL source, the same text you would otherwise compile:\n\n```fsl {teaches: api-language-service, run: true}\nmachine_name : \"Sample\";\na 'go' -> b;\n```\n"
207
+ },
208
+ {
209
+ "id": "tut-api-machine",
210
+ "section": "tutorials",
211
+ "title": "API: the Machine and its factories",
212
+ "order": 80,
213
+ "teaches": [
214
+ "api-machine"
215
+ ],
216
+ "mentions": [
217
+ "transitions",
218
+ "states"
219
+ ],
220
+ "indexTerms": [
221
+ "sm",
222
+ "from",
223
+ "compile",
224
+ "Machine",
225
+ "transition",
226
+ "API"
227
+ ],
228
+ "body": "\n# API: the Machine and its factories\n\nIn JavaScript, the quickest way to build a machine is the `sm` template tag. It compiles an FSL string into a live `Machine` you can drive with `.transition()`.\n\n```js\nimport { sm } from 'jssm';\n\nconst traffic = sm`Red 'go' -> Green 'go' -> Yellow 'go' -> Red;`;\ntraffic.state; // 'Red'\ntraffic.transition('go'); // true; now in 'Green'\n```\n\nThe machine this compiles from is just ordinary FSL:\n\n```fsl {teaches: api-machine, run: true}\nRed 'go' -> Green 'go' -> Yellow 'go' -> Red;\n```\n\n`sm` is the terse path; `from(...)` and `compile(...)` give you the same `Machine` with more control over options, and `deserialize` rebuilds one from saved state.\n"
229
+ },
230
+ {
231
+ "id": "tut-api-utilities",
232
+ "section": "tutorials",
233
+ "title": "API: utility exports",
234
+ "order": 90,
235
+ "teaches": [
236
+ "api-utilities"
237
+ ],
238
+ "mentions": [
239
+ "api-machine",
240
+ "weighted-arrows"
241
+ ],
242
+ "indexTerms": [
243
+ "seq",
244
+ "unique",
245
+ "weighted_rand_select",
246
+ "sleep",
247
+ "utility",
248
+ "API"
249
+ ],
250
+ "body": "\n# API: utility exports\n\nA handful of small utilities ride along with the package — `seq`, `unique`, `find_repeated`, `weighted_rand_select`, `sleep`, and friends. They back the stochastic tooling and are handy on their own.\n\n```js\nimport { seq, unique, weighted_rand_select } from 'jssm';\n\nseq(3); // [0, 1, 2]\nunique([1, 1, 2, 3, 3]); // [1, 2, 3]\nweighted_rand_select([[0.7, 'win'], [0.3, 'lose']]);\n```\n\n`weighted_rand_select` is the engine behind probabilistic transitions:\n\n```fsl {teaches: api-utilities, run: true}\nIdle -> 70% Win;\nIdle -> 30% Lose;\n```\n"
251
+ },
252
+ {
253
+ "id": "tut-api-version-info",
254
+ "section": "tutorials",
255
+ "title": "API: version and build info",
256
+ "order": 82,
257
+ "teaches": [
258
+ "api-version-info"
259
+ ],
260
+ "mentions": [
261
+ "api-machine"
262
+ ],
263
+ "indexTerms": [
264
+ "version",
265
+ "build_time",
266
+ "compareVersions",
267
+ "API"
268
+ ],
269
+ "body": "\n# API: version and build info\n\nThe package exports its own `version` and `build_time`, plus `compareVersions` for ordering SemVer strings.\n\n```js\nimport { version, build_time, compareVersions } from 'jssm';\n\nversion; // e.g. '5.149.2'\ncompareVersions('5.2.0', '5.10.0'); // -1 (5.2.0 is older)\n```\n\nThis is the same machinery `fsl_version` ranges lean on:\n\n```fsl {teaches: api-version-info, run: true}\nfsl_version : 5.0.0;\na 'go' -> b;\n```\n"
270
+ },
271
+ {
272
+ "id": "tut-arrange",
273
+ "section": "tutorials",
274
+ "title": "Layout hints (arrange)",
275
+ "order": 60,
276
+ "teaches": [
277
+ "arrange"
278
+ ],
279
+ "mentions": [
280
+ "states"
281
+ ],
282
+ "indexTerms": [
283
+ "arrange",
284
+ "layout",
285
+ "rank",
286
+ "position"
287
+ ],
288
+ "body": "\n# Layout hints (arrange)\n\n`arrange [ … ];` nudges the layout engine to place a set of states on the same rank (row/column). Use `arrange-start` / `arrange-end` to pin states to the first or last rank.\n\n```fsl {teaches: arrange, run: true}\narrange [a b];\na -> b;\nc -> d;\narrange [c d];\n```\n\nArrange is purely presentational — it changes how the diagram is drawn, never the machine's behaviour.\n"
289
+ },
290
+ {
291
+ "id": "tut-arrow-decorations",
292
+ "section": "tutorials",
293
+ "title": "Per-arrow decorations",
294
+ "order": 68,
295
+ "teaches": [
296
+ "arrow-decorations"
297
+ ],
298
+ "mentions": [
299
+ "transitions",
300
+ "colors"
301
+ ],
302
+ "indexTerms": [
303
+ "edge-color",
304
+ "arc_label",
305
+ "head_label",
306
+ "tail_label",
307
+ "per-arrow"
308
+ ],
309
+ "body": "\n# Per-arrow decorations\n\nA `{ … }` block placed **between an arrow and its target** decorates that one edge — its color, line style, or labels (`arc_label`, `head_label`, `tail_label`).\n\n```fsl {teaches: arrow-decorations, run: true}\na -> {edge-color: SteelBlue;} b;\nb -> {arc_label: retry;} a;\n```\n\nThis is the per-edge counterpart to the `transition: {}` default block: use the block for machine-wide edge defaults, a decoration for a single distinctive edge. (`edge_color` with an underscore is accepted as a legacy alias of `edge-color`.)\n"
310
+ },
311
+ {
312
+ "id": "tut-arrow-flavors",
313
+ "section": "tutorials",
314
+ "title": "Arrow flavors",
315
+ "order": 64,
316
+ "teaches": [
317
+ "arrow-flavors"
318
+ ],
319
+ "mentions": [
320
+ "transitions"
321
+ ],
322
+ "indexTerms": [
323
+ "arrow",
324
+ "fat arrow",
325
+ "tilde arrow",
326
+ "main",
327
+ "forced",
328
+ "=>",
329
+ "~>"
330
+ ],
331
+ "body": "\n# Arrow flavors\n\nBeyond the plain legal arrow `->`, FSL has arrows that carry *intent*: `=>` is the **main** path (the expected route), and `~>` is a **forced** transition (the machine takes it on its own). Bidirectional forms (`<->`, `<=>`, `<~>`) declare both directions at once.\n\n```fsl {teaches: arrow-flavors, run: true}\nRed 'tick' => Green 'tick' => Yellow 'tick' => Red;\nDoor 'panic' ~> Open;\n```\n\nUnicode glyphs (`→`, `⇒`, `↔`) are accepted as synonyms for the ASCII forms, if you prefer them.\n"
332
+ },
333
+ {
334
+ "id": "tut-cli-dispatcher",
335
+ "section": "tutorials",
336
+ "title": "CLI: the fsl command",
337
+ "order": 130,
338
+ "teaches": [
339
+ "cli-dispatcher"
340
+ ],
341
+ "mentions": [
342
+ "cli-render"
343
+ ],
344
+ "indexTerms": [
345
+ "cli",
346
+ "fsl",
347
+ "dispatcher",
348
+ "--help",
349
+ "--version"
350
+ ],
351
+ "body": "\n# CLI: the fsl command\n\nThe `fsl` command is the entry point (with `jssm` as an alias). It dispatches subcommands and supports the usual `--help` / `--version` flags.\n\n```sh\nfsl --version\nfsl --help\nfsl render machine.fsl\n```\n\nThe files it works on contain ordinary FSL:\n\n```fsl {teaches: cli-dispatcher, run: true}\nmachine_name : \"From a file\";\na 'go' -> b;\n```\n"
352
+ },
353
+ {
354
+ "id": "tut-cli-export-prompt",
355
+ "section": "tutorials",
356
+ "title": "CLI: fsl export-system-prompt",
357
+ "order": 136,
358
+ "teaches": [
359
+ "cli-export-prompt"
360
+ ],
361
+ "mentions": [
362
+ "cli-dispatcher"
363
+ ],
364
+ "indexTerms": [
365
+ "system prompt",
366
+ "llms.txt",
367
+ "export prompt"
368
+ ],
369
+ "body": "\n# CLI: fsl export-system-prompt\n\n`fsl-export-system-prompt` prints an `llms.txt`-style FSL syntax guide for feeding to a large language model, so an agent can generate idiomatic FSL.\n\n```sh\nfsl-export-system-prompt > fsl-llms.txt\n```\n\nThe prompt teaches an LLM to produce machines like:\n\n```fsl {teaches: cli-export-prompt, run: true}\nmachine_name : \"Generated\";\nIdle 'start' -> Running 'stop' -> Idle;\n```\n"
370
+ },
371
+ {
372
+ "id": "tut-cli-render-targets",
373
+ "section": "tutorials",
374
+ "title": "CLI: render output formats",
375
+ "order": 134,
376
+ "teaches": [
377
+ "cli-render-targets"
378
+ ],
379
+ "mentions": [
380
+ "cli-render"
381
+ ],
382
+ "indexTerms": [
383
+ "svg",
384
+ "png",
385
+ "jpeg",
386
+ "dot",
387
+ "html",
388
+ "format",
389
+ "export"
390
+ ],
391
+ "body": "\n# CLI: render output formats\n\n`fsl render` can emit several formats via `--target`: `svg` (default), `png`, `jpeg`, `dot`, and `html`.\n\n```sh\nfsl render machine.fsl --target png -o machine.png\nfsl render machine.fsl --target dot -o machine.dot\n```\n\nThe same machine renders to any of them:\n\n```fsl {teaches: cli-render-targets, run: true}\na 'go' -> b 'go' -> c;\n```\n"
392
+ },
393
+ {
394
+ "id": "tut-cli-render",
395
+ "section": "tutorials",
396
+ "title": "CLI: fsl render",
397
+ "order": 132,
398
+ "teaches": [
399
+ "cli-render"
400
+ ],
401
+ "mentions": [
402
+ "cli-dispatcher",
403
+ "viz-render"
404
+ ],
405
+ "indexTerms": [
406
+ "render",
407
+ "cli render",
408
+ "fsl-render"
409
+ ],
410
+ "body": "\n# CLI: fsl render\n\n`fsl render` turns an `.fsl` file into a diagram from the command line (the `fsl-render` binary does the same standalone).\n\n```sh\nfsl render traffic.fsl -o traffic.svg\n```\n\nGiven a machine file like:\n\n```fsl {teaches: cli-render, run: true}\nRed 'go' -> Green 'go' -> Yellow 'go' -> Red;\n```\n\n…it writes out the rendered diagram — handy in build scripts and CI.\n"
411
+ },
412
+ {
413
+ "id": "tut-colors",
414
+ "section": "tutorials",
415
+ "title": "Colors",
416
+ "order": 40,
417
+ "teaches": [
418
+ "colors"
419
+ ],
420
+ "mentions": [
421
+ "state-styling"
422
+ ],
423
+ "indexTerms": [
424
+ "color",
425
+ "svg color",
426
+ "hex",
427
+ "background-color"
428
+ ],
429
+ "body": "\n# Colors\n\nAnywhere a color is expected you can use a **PascalCase SVG color name** (the canonical form) or a `#rrggbb` **hex** value. The lowercase form (`forestgreen`) also parses, but PascalCase is preferred.\n\n```fsl {teaches: colors, run: true}\nstate Go : { color: ForestGreen; };\nstate Stop : { color: #b91c1c; };\nStop 'go' -> Go 'stop' -> Stop;\n```\n\nThe same color vocabulary is used for `color`, `background-color`, `border-color`, and the graph defaults.\n"
430
+ },
431
+ {
432
+ "id": "tut-comments",
433
+ "section": "tutorials",
434
+ "title": "Comments",
435
+ "order": 4,
436
+ "teaches": [
437
+ "comments"
438
+ ],
439
+ "mentions": [],
440
+ "indexTerms": [
441
+ "comment",
442
+ "line comment",
443
+ "block comment"
444
+ ],
445
+ "body": "\n# Comments\n\nFSL has line comments (`//` to end of line) and block comments (`/* … */`), just like C or JavaScript.\n\n```fsl {teaches: comments, run: true}\n// a one-line note\na 'go' -> b;\n/* a block comment\n spanning lines */\nb 'go' -> a;\n```\n\nComments are ignored by the parser — use them freely to annotate intent.\n"
446
+ },
447
+ {
448
+ "id": "tut-config-blocks",
449
+ "section": "tutorials",
450
+ "title": "Configuration blocks",
451
+ "order": 50,
452
+ "teaches": [
453
+ "config-blocks"
454
+ ],
455
+ "mentions": [
456
+ "state-styling"
457
+ ],
458
+ "indexTerms": [
459
+ "config",
460
+ "state defaults",
461
+ "transition defaults",
462
+ "graph"
463
+ ],
464
+ "body": "\n# Configuration blocks\n\nA `keyword : { … };` block sets **defaults** for a whole class of things at once — every state, every transition (edge), or the graph as a whole — instead of styling each one inline.\n\n```fsl {teaches: config-blocks, run: true}\nstate : {\n background-color : WhiteSmoke;\n};\ntransition : {\n color : SlateGray;\n};\na 'go' -> b 'go' -> a;\n```\n\nThe block forms are `state`, `start_state`, `end_state`, `active_state`, `terminal_state`, `hooked_state`, `transition`, and `graph`. They accept the same style vocabulary as a per-state block.\n"
465
+ },
466
+ {
467
+ "id": "tut-corners",
468
+ "section": "tutorials",
469
+ "title": "Corner styles",
470
+ "order": 72,
471
+ "teaches": [
472
+ "corners"
473
+ ],
474
+ "mentions": [
475
+ "state-styling"
476
+ ],
477
+ "indexTerms": [
478
+ "corners",
479
+ "rounded",
480
+ "regular",
481
+ "lined"
482
+ ],
483
+ "body": "\n# Corner styles\n\nThe `corners` key softens or squares a state's box — `regular` (sharp), `rounded`, or `lined`.\n\n```fsl {teaches: corners, run: true}\nstate Draft : { corners: rounded; };\nDraft 'publish' -> Live;\n```\n\n`rounded` corners pair well with a `box` shape for a softer, card-like node.\n"
484
+ },
485
+ {
486
+ "id": "tut-directions",
487
+ "section": "tutorials",
488
+ "title": "Directions",
489
+ "order": 62,
490
+ "teaches": [
491
+ "directions"
492
+ ],
493
+ "mentions": [
494
+ "arrange",
495
+ "machine-attributes"
496
+ ],
497
+ "indexTerms": [
498
+ "direction",
499
+ "flow",
500
+ "up",
501
+ "down",
502
+ "left",
503
+ "right"
504
+ ],
505
+ "body": "\n# Directions\n\nA direction — `up`, `down`, `left`, or `right` — sets the **flow** of the diagram, i.e. which way the graph grows.\n\n```fsl {teaches: directions, run: true}\nflow: right;\na 'go' -> b 'go' -> c 'go' -> d;\n```\n\n`flow: down` (the default) stacks states top-to-bottom; `flow: right` lays them left-to-right, which often reads better for linear pipelines.\n"
506
+ },
507
+ {
508
+ "id": "tut-document-structure",
509
+ "section": "tutorials",
510
+ "title": "Document structure",
511
+ "order": 2,
512
+ "teaches": [
513
+ "doc-structure"
514
+ ],
515
+ "mentions": [
516
+ "transitions"
517
+ ],
518
+ "indexTerms": [
519
+ "document",
520
+ "statement",
521
+ "term",
522
+ "semicolon"
523
+ ],
524
+ "body": "\n# Document structure\n\nAn FSL file is a flat list of **statements**, each ended with a semicolon. Order rarely matters — transitions, state declarations, and machine attributes can appear in any sequence.\n\n```fsl {teaches: doc-structure, run: true}\nmachine_name : \"Tiny\";\na 'go' -> b;\nb 'go' -> a;\n```\n\nWhitespace and blank lines are free. That is the whole shape of a document: statements, separated by semicolons.\n"
525
+ },
526
+ {
527
+ "id": "tut-editor-highlighting",
528
+ "section": "tutorials",
529
+ "title": "Editor: syntax highlighting",
530
+ "order": 112,
531
+ "teaches": [
532
+ "editor-highlighting"
533
+ ],
534
+ "mentions": [
535
+ "editor-language-support"
536
+ ],
537
+ "indexTerms": [
538
+ "highlight",
539
+ "syntax color",
540
+ "deprecated marker",
541
+ "editor"
542
+ ],
543
+ "body": "\n# Editor: syntax highlighting\n\n`fslHighlightStyle` is the highlight style the language support uses; `fslDeprecated` is a token modifier that marks deprecated keywords so an editor can grey them out.\n\n```js\nimport { fslHighlightStyle } from 'jssm/cm6';\nimport { syntaxHighlighting } from '@codemirror/language';\n\nconst extensions = [ syntaxHighlighting(fslHighlightStyle) ];\n```\n\nThe highlighter colours the parts of a machine differently — keywords, state names, values:\n\n```fsl {teaches: editor-highlighting, run: true}\nstate Go : { shape: doublecircle; };\nStop 'go' -> Go;\n```\n"
544
+ },
545
+ {
546
+ "id": "tut-editor-keyword-sets",
547
+ "section": "tutorials",
548
+ "title": "Editor: keyword classification sets",
549
+ "order": 114,
550
+ "teaches": [
551
+ "editor-keyword-sets"
552
+ ],
553
+ "mentions": [
554
+ "editor-highlighting"
555
+ ],
556
+ "indexTerms": [
557
+ "keywords",
558
+ "structural",
559
+ "property",
560
+ "enum",
561
+ "editor"
562
+ ],
563
+ "body": "\n# Editor: keyword classification sets\n\nThe language support exports the keyword sets it uses to classify tokens — `STRUCTURAL_KEYWORDS`, `PROPERTY_KEYWORDS`, `ENUM_KEYWORDS`, and `DEPRECATED_KEYWORDS` — so tools can reuse the same vocabulary.\n\n```js\nimport { STRUCTURAL_KEYWORDS, ENUM_KEYWORDS } from 'jssm/cm6';\n\nSTRUCTURAL_KEYWORDS.has('state'); // true\nENUM_KEYWORDS.has('doublecircle'); // true\n```\n\nThese are the words an editor highlights specially in a machine:\n\n```fsl {teaches: editor-keyword-sets, run: true}\nstate Final : { shape: doublecircle; };\na 'go' -> Final;\n```\n"
564
+ },
565
+ {
566
+ "id": "tut-editor-language-support",
567
+ "section": "tutorials",
568
+ "title": "Editor: CodeMirror 6 language support",
569
+ "order": 110,
570
+ "teaches": [
571
+ "editor-language-support"
572
+ ],
573
+ "mentions": [
574
+ "doc-structure"
575
+ ],
576
+ "indexTerms": [
577
+ "codemirror",
578
+ "cm6",
579
+ "language support",
580
+ "editor"
581
+ ],
582
+ "body": "\n# Editor: CodeMirror 6 language support\n\nThe `jssm/cm6` entry provides FSL language support for CodeMirror 6 — `fsl()` returns a `LanguageSupport` extension you drop into an editor.\n\n```js\nimport { EditorView } from '@codemirror/view';\nimport { fsl } from 'jssm/cm6';\n\nnew EditorView({\n doc: `a 'go' -> b;`,\n extensions: [fsl()],\n parent: document.body,\n});\n```\n\nIt highlights the FSL you type — declarations, attributes, and values:\n\n```fsl {teaches: editor-language-support, run: true}\nmachine_name : \"Demo\";\na 'go' -> b;\n```\n"
583
+ },
584
+ {
585
+ "id": "tut-groups",
586
+ "section": "tutorials",
587
+ "title": "Groups and named lists",
588
+ "order": 44,
589
+ "teaches": [
590
+ "groups"
591
+ ],
592
+ "mentions": [
593
+ "states"
594
+ ],
595
+ "indexTerms": [
596
+ "group",
597
+ "named list",
598
+ "&",
599
+ "set",
600
+ "reusable"
601
+ ],
602
+ "body": "\n# Groups and named lists\n\nA **named list** with `&name : [ … ];` gives a reusable set of states a single handle. It groups states visually and lets you refer to the set by name.\n\n```fsl {teaches: groups, run: true}\n&warm : [Red Orange Yellow];\nRed 'next' -> Orange 'next' -> Yellow 'next' -> Red;\n```\n\nMembers are separated by spaces inside the brackets. Groups are how you draw clusters and (in later features) attach shared behaviour.\n"
603
+ },
604
+ {
605
+ "id": "tut-hooks",
606
+ "section": "tutorials",
607
+ "title": "Boundary hooks",
608
+ "order": 46,
609
+ "teaches": [
610
+ "hooks"
611
+ ],
612
+ "mentions": [
613
+ "states",
614
+ "groups"
615
+ ],
616
+ "indexTerms": [
617
+ "hook",
618
+ "on enter",
619
+ "on exit",
620
+ "boundary",
621
+ "callback"
622
+ ],
623
+ "body": "\n# Boundary hooks\n\nA **boundary hook** runs an action when the machine enters or exits a state (or a group). The form is `on enter|exit <state> do '<action>';`.\n\n```fsl {teaches: hooks, run: true}\non enter Locked do 'beep';\nUnlocked 'lock' -> Locked 'unlock' -> Unlocked;\n```\n\nHooks fire on the *boundary* — the transition into or out of the named state — which is how you wire side effects to a machine without entangling them in the transitions themselves.\n"
624
+ },
625
+ {
626
+ "id": "tut-labels-quoting",
627
+ "section": "tutorials",
628
+ "title": "Labels and quoting",
629
+ "order": 8,
630
+ "teaches": [
631
+ "labels-quoting"
632
+ ],
633
+ "mentions": [
634
+ "states",
635
+ "transitions"
636
+ ],
637
+ "indexTerms": [
638
+ "label",
639
+ "string",
640
+ "atom",
641
+ "quote",
642
+ "action"
643
+ ],
644
+ "body": "\n# Labels and quoting\n\nState names and action names are **labels**. A bare label (an *atom*) needs no quotes when it is a simple identifier — `Red`, `idle_2`. Anything with spaces or punctuation must be quoted, and the two quote styles mean different things:\n\n- **Single quotes** mark **action labels** — `'insert coin'`.\n- **Double quotes** mark **string literals** — used for attributes like `machine_name`.\n\n```fsl {teaches: labels-quoting, run: true}\nmachine_name : \"Vending Machine\";\nIdle 'insert coin' -> Paid;\nPaid 'refund' -> Idle;\n```\n\nThe quote styles are not interchangeable: single = action, double = string.\n"
645
+ },
646
+ {
647
+ "id": "tut-line-styles",
648
+ "section": "tutorials",
649
+ "title": "Line styles",
650
+ "order": 56,
651
+ "teaches": [
652
+ "line-styles"
653
+ ],
654
+ "mentions": [
655
+ "state-styling"
656
+ ],
657
+ "indexTerms": [
658
+ "line-style",
659
+ "dashed",
660
+ "dotted",
661
+ "solid",
662
+ "bold"
663
+ ],
664
+ "body": "\n# Line styles\n\nThe `line-style` key controls how a border or edge is stroked — `solid` (the default), `dashed`, `dotted`, or `bold`.\n\n```fsl {teaches: line-styles, run: true}\nstate Pending : { line-style: dashed; };\nIdle 'submit' -> Pending 'confirm' -> Done;\n```\n\nOn a state it styles the node border; in a `transition: {}` block it styles edges — handy for visually distinguishing tentative or fallback paths.\n"
665
+ },
666
+ {
667
+ "id": "tut-literal-values",
668
+ "section": "tutorials",
669
+ "title": "Literal values",
670
+ "order": 48,
671
+ "teaches": [
672
+ "literal-values"
673
+ ],
674
+ "mentions": [
675
+ "single-value-configs"
676
+ ],
677
+ "indexTerms": [
678
+ "true",
679
+ "false",
680
+ "null",
681
+ "undefined",
682
+ "boolean"
683
+ ],
684
+ "body": "\n# Literal values\n\nA few configuration keys take literal values rather than labels: booleans (`true` / `false`), `null`, and `undefined`. They appear in the on/off-style config keys.\n\n```fsl {teaches: literal-values, run: true}\nallows_override : true;\nallow_islands : false;\na 'go' -> b;\n```\n\nThese are the same literals you would recognise from JavaScript — `true`/`false` toggle a feature, and `undefined` leaves it at its default.\n"
685
+ },
686
+ {
687
+ "id": "tut-machine-attributes",
688
+ "section": "tutorials",
689
+ "title": "Machine attributes",
690
+ "order": 25,
691
+ "teaches": [
692
+ "machine-attributes"
693
+ ],
694
+ "mentions": [
695
+ "semver",
696
+ "labels-quoting"
697
+ ],
698
+ "indexTerms": [
699
+ "machine_name",
700
+ "machine_author",
701
+ "fsl_version",
702
+ "theme",
703
+ "metadata"
704
+ ],
705
+ "body": "\n# Machine attributes\n\nTop-level `key : value;` lines attach **metadata** to the machine — its name, author, version, license, theme, and more.\n\n```fsl {teaches: machine-attributes, run: true}\nmachine_name : \"Traffic Light\";\nmachine_author : \"Jane Doe\";\nmachine_version : 1.2.0;\nfsl_version : 5.0.0;\n\nRed 'tick' -> Green 'tick' -> Yellow 'tick' -> Red;\n```\n\nMost attributes are optional. `machine_name` appears in visualizations; `fsl_version` declares the language version the machine targets (see version ranges).\n"
706
+ },
707
+ {
708
+ "id": "tut-prompt-examples",
709
+ "section": "tutorials",
710
+ "title": "LLM prompt: worked examples",
711
+ "order": 142,
712
+ "teaches": [
713
+ "prompt-examples"
714
+ ],
715
+ "mentions": [
716
+ "prompt-overview"
717
+ ],
718
+ "indexTerms": [
719
+ "example",
720
+ "basic machine",
721
+ "actions"
722
+ ],
723
+ "body": "\n# LLM prompt: worked examples\n\nThe Syntax Guide section carries worked examples an LLM can pattern-match: a Basic Machine, Actions and Multiple Targets, and Explicit State Declarations. The explicit-state example shows styled `state` blocks:\n\n```fsl {teaches: prompt-examples, run: true}\nstate Red : {\n background-color : red;\n text-color : white;\n};\n\nstate Green : {\n background-color : green;\n};\n\nRed -> Green;\n```\n\nThese are the same patterns the rest of this curriculum teaches — the prompt distils them for a machine reader.\n"
724
+ },
725
+ {
726
+ "id": "tut-prompt-overview",
727
+ "section": "tutorials",
728
+ "title": "LLM prompt: overview",
729
+ "order": 140,
730
+ "teaches": [
731
+ "prompt-overview"
732
+ ],
733
+ "mentions": [
734
+ "cli-export-prompt"
735
+ ],
736
+ "indexTerms": [
737
+ "llms.txt",
738
+ "system prompt",
739
+ "agent",
740
+ "concepts"
741
+ ],
742
+ "body": "\n# LLM prompt: overview\n\nThe exported system prompt is organized into sections: **Core Concepts** (machines, states, transition types, actions, probabilities, attributes), a **Syntax Guide**, and **Agent Directives** (rules like \"always output valid FSL\", \"use single quotes for actions\").\n\n```sh\nfsl-export-system-prompt | less\n```\n\nIt is built to make an LLM produce correct FSL the first time — for example:\n\n```fsl {teaches: prompt-overview, run: true}\nmachine_name : \"Simple Traffic Light\";\nRed 'timer' => Green 'timer' => Yellow 'timer' => Red;\n```\n"
743
+ },
744
+ {
745
+ "id": "tut-shapes",
746
+ "section": "tutorials",
747
+ "title": "State shapes",
748
+ "order": 70,
749
+ "teaches": [
750
+ "shapes"
751
+ ],
752
+ "mentions": [
753
+ "state-styling"
754
+ ],
755
+ "indexTerms": [
756
+ "shape",
757
+ "circle",
758
+ "doublecircle",
759
+ "box",
760
+ "ellipse"
761
+ ],
762
+ "body": "\n# State shapes\n\nThe `shape` key draws a state as one of Graphviz's node shapes — `circle`, `doublecircle`, `box`, `ellipse`, `diamond`, and dozens more.\n\n```fsl {teaches: shapes, run: true}\nstate Start : { shape: circle; };\nstate Final : { shape: doublecircle; };\nStart 'run' -> Final;\n```\n\nA common idiom: `doublecircle` for accepting/final states, plain `circle` or `box` for the rest — an at-a-glance convention borrowed from automata diagrams.\n"
763
+ },
764
+ {
765
+ "id": "tut-single-value-configs",
766
+ "section": "tutorials",
767
+ "title": "Single-value configuration",
768
+ "order": 52,
769
+ "teaches": [
770
+ "single-value-configs"
771
+ ],
772
+ "mentions": [
773
+ "config-blocks"
774
+ ],
775
+ "indexTerms": [
776
+ "graph_layout",
777
+ "allow_islands",
778
+ "allows_override",
779
+ "failed_outputs"
780
+ ],
781
+ "body": "\n# Single-value configuration\n\nSome settings are a single `key : value;` rather than a block — graph layout, island rules, override permission, and similar machine-wide switches.\n\n```fsl {teaches: single-value-configs, run: true}\ngraph_layout : circo;\nallow_islands : false;\na 'go' -> b;\n```\n\n`graph_layout` chooses a Graphviz engine (`dot`, `circo`, `fdp`, `neato`, `twopi`); `allow_islands` controls whether disconnected components are permitted.\n"
782
+ },
783
+ {
784
+ "id": "tut-state-styling",
785
+ "section": "tutorials",
786
+ "title": "State styling",
787
+ "order": 42,
788
+ "teaches": [
789
+ "state-styling"
790
+ ],
791
+ "mentions": [
792
+ "colors",
793
+ "shapes"
794
+ ],
795
+ "indexTerms": [
796
+ "style",
797
+ "background-color",
798
+ "border-color",
799
+ "shape",
800
+ "state"
801
+ ],
802
+ "body": "\n# State styling\n\nA `state` declaration can carry style keys that control how the node is drawn: `color`, `text-color`, `background-color`, `border-color`, `shape`, `corners`, `line-style`, `image`, and `url`.\n\n```fsl {teaches: state-styling, run: true}\nstate Active : {\n background-color : LightYellow;\n border-color : GoldenRod;\n shape : doublecircle;\n};\nIdle 'start' -> Active 'stop' -> Idle;\n```\n\nThese are the same keys the autocomplete offers inside a `{ }` block, and the same vocabulary the `state: {}` default-config block accepts.\n"
803
+ },
125
804
  {
126
805
  "id": "tut-states-and-styling",
127
806
  "section": "tutorials",
@@ -182,6 +861,253 @@ const DOCS_PAGES = [
182
861
  "action"
183
862
  ],
184
863
  "body": "\n# Transitions\n\nA transition is the core of FSL: a **source state**, an arrow, and a **target state**. States are inferred from the arrows, so you never declare them just to use them.\n\n```fsl {teaches: transitions, run: true}\nOff 'flip' -> On 'flip' -> Off;\n```\n\nThe text in single quotes is an **action** — the named input that fires the transition. You can chain transitions on one line, as above, or write them one per line. The plain `->` arrow means a *legal* transition; later tutorials cover the other arrow kinds.\n"
864
+ },
865
+ {
866
+ "id": "tut-typed-properties",
867
+ "section": "tutorials",
868
+ "title": "State properties",
869
+ "order": 76,
870
+ "teaches": [
871
+ "typed-properties"
872
+ ],
873
+ "mentions": [
874
+ "machine-attributes"
875
+ ],
876
+ "indexTerms": [
877
+ "property",
878
+ "state property",
879
+ "extended state",
880
+ "value"
881
+ ],
882
+ "body": "\n# State properties\n\nA state can carry **properties** — named values attached to it — with `property: <name> <value>;` inside its declaration. Add `required` to mark one mandatory.\n\n```fsl {teaches: typed-properties, run: true}\nstate Account : { property: balance 100; };\nOpen 'deposit' -> Account;\n```\n\nProperties are how a state carries data beyond its name — the seed of FSL's extended-state model.\n"
883
+ },
884
+ {
885
+ "id": "tut-urls",
886
+ "section": "tutorials",
887
+ "title": "URLs",
888
+ "order": 74,
889
+ "teaches": [
890
+ "urls"
891
+ ],
892
+ "mentions": [
893
+ "state-styling"
894
+ ],
895
+ "indexTerms": [
896
+ "url",
897
+ "link",
898
+ "http",
899
+ "click-through"
900
+ ],
901
+ "body": "\n# URLs\n\nA state can carry a `url` (a double-quoted string). In a rendered SVG it becomes a click-through link on the node — handy for wiring states to docs or dashboards.\n\n```fsl {teaches: urls, run: true}\nstate Docs : { url: \"https://fsl.tools\"; };\nHome 'help' -> Docs;\n```\n\nThe URL passes through to Graphviz's `URL=` attribute and surfaces as an `xlink:href` on the SVG node.\n"
902
+ },
903
+ {
904
+ "id": "tut-semver",
905
+ "section": "tutorials",
906
+ "title": "Version ranges (SemVer)",
907
+ "order": 54,
908
+ "teaches": [
909
+ "semver"
910
+ ],
911
+ "mentions": [
912
+ "machine-attributes"
913
+ ],
914
+ "indexTerms": [
915
+ "semver",
916
+ "version",
917
+ "fsl_version",
918
+ "range"
919
+ ],
920
+ "body": "\n# Version ranges (SemVer)\n\n`fsl_version` declares which FSL language version a machine targets, as a SemVer value. A plain version pins exactly; the comparison operators (`<`, `>`, `<=`, `>=`) express a range.\n\n```fsl {teaches: semver, run: true}\nfsl_version : 5.2.0;\na 'go' -> b;\n```\n\nPinning `fsl_version` lets tooling warn when a machine relies on syntax newer than the runtime it is loaded into.\n"
921
+ },
922
+ {
923
+ "id": "tut-viz-config",
924
+ "section": "tutorials",
925
+ "title": "Viz: render configuration",
926
+ "order": 104,
927
+ "teaches": [
928
+ "viz-config"
929
+ ],
930
+ "mentions": [
931
+ "viz-render"
932
+ ],
933
+ "indexTerms": [
934
+ "configure",
935
+ "render options",
936
+ "groups",
937
+ "viz"
938
+ ],
939
+ "body": "\n# Viz: render configuration\n\n`configure` sets global render defaults, and the `VizRenderOpts` / `RenderGroups` types let you tune a single render — engine choice, group rendering, and more.\n\n```js\nimport { configure, fsl_to_svg_string } from 'jssm/viz';\n\nconfigure({ /* global defaults */ });\nconst svg = await fsl_to_svg_string(`a -> b;`, { /* VizRenderOpts */ });\n```\n\nRender options shape how a machine like this is drawn without changing the FSL:\n\n```fsl {teaches: viz-config, run: true}\ngraph_layout : circo;\na 'go' -> b;\n```\n"
940
+ },
941
+ {
942
+ "id": "tut-viz-dot",
943
+ "section": "tutorials",
944
+ "title": "Viz: DOT output",
945
+ "order": 102,
946
+ "teaches": [
947
+ "viz-dot"
948
+ ],
949
+ "mentions": [
950
+ "viz-render"
951
+ ],
952
+ "indexTerms": [
953
+ "dot",
954
+ "graphviz",
955
+ "viz"
956
+ ],
957
+ "body": "\n# Viz: DOT output\n\nIf you want the Graphviz **DOT** source rather than rendered SVG — to feed another tool or render server-side — use `fsl_to_dot` / `machine_to_dot`, or `dot_to_svg` to finish the job yourself.\n\n```js\nimport { fsl_to_dot } from 'jssm/viz';\n\nconst dot = fsl_to_dot(`a 'go' -> b;`); // 'digraph { … }'\n```\n\nThe DOT is generated from the same machine you would otherwise render:\n\n```fsl {teaches: viz-dot, run: true}\na 'go' -> b 'go' -> c;\n```\n"
958
+ },
959
+ {
960
+ "id": "tut-viz-render",
961
+ "section": "tutorials",
962
+ "title": "Viz: rendering to SVG",
963
+ "order": 100,
964
+ "teaches": [
965
+ "viz-render"
966
+ ],
967
+ "mentions": [
968
+ "api-machine"
969
+ ],
970
+ "indexTerms": [
971
+ "svg",
972
+ "render",
973
+ "visualize",
974
+ "diagram",
975
+ "viz"
976
+ ],
977
+ "body": "\n# Viz: rendering to SVG\n\nThe `jssm/viz` entry turns a machine (or FSL string) into an SVG diagram: `fsl_to_svg_string`, `fsl_to_svg_element`, `machine_to_svg_string`, `machine_to_svg_element`.\n\n```js\nimport { fsl_to_svg_string } from 'jssm/viz';\n\nconst svg = await fsl_to_svg_string(`Red 'go' -> Green 'go' -> Red;`);\ndocument.body.insertAdjacentHTML('beforeend', svg);\n```\n\nAny machine renders — the styling you write in FSL flows straight through to the SVG:\n\n```fsl {teaches: viz-render, run: true}\nstate Go : { color: ForestGreen; };\nStop 'go' -> Go 'stop' -> Stop;\n```\n"
978
+ },
979
+ {
980
+ "id": "tut-viz-version-info",
981
+ "section": "tutorials",
982
+ "title": "Viz: version and build info",
983
+ "order": 106,
984
+ "teaches": [
985
+ "viz-version-info"
986
+ ],
987
+ "mentions": [
988
+ "viz-render"
989
+ ],
990
+ "indexTerms": [
991
+ "version",
992
+ "build_time",
993
+ "viz"
994
+ ],
995
+ "body": "\n# Viz: version and build info\n\nThe viz module reports its own `version` and `build_time`, independent of the core package — useful when the renderer is loaded from a CDN separately from the core.\n\n```js\nimport { version, build_time } from 'jssm/viz';\n\nversion; // the viz build's version\nbuild_time; // when it was built\n```\n\nIt renders machines like any other:\n\n```fsl {teaches: viz-version-info, run: true}\na 'go' -> b;\n```\n"
996
+ },
997
+ {
998
+ "id": "tut-wc-bind",
999
+ "section": "tutorials",
1000
+ "title": "Web component: <fsl-bind>",
1001
+ "order": 124,
1002
+ "teaches": [
1003
+ "wc-bind"
1004
+ ],
1005
+ "mentions": [
1006
+ "wc-instance"
1007
+ ],
1008
+ "indexTerms": [
1009
+ "fsl-bind",
1010
+ "binding",
1011
+ "data"
1012
+ ],
1013
+ "body": "\n# Web component: &lt;fsl-bind&gt;\n\n`<fsl-bind>` wires DOM to a hosted machine — reflecting the current state into the page and dispatching actions from it, so plain HTML can drive and read a machine.\n\n```html\n<fsl-instance fsl=\"Off 'flip' -> On 'flip' -> Off;\">\n <fsl-bind>\n <button data-fsl-action=\"flip\">Toggle</button>\n </fsl-bind>\n</fsl-instance>\n```\n\nBehind it is a normal machine:\n\n```fsl {teaches: wc-bind, run: true}\nOff 'flip' -> On 'flip' -> Off;\n```\n"
1014
+ },
1015
+ {
1016
+ "id": "tut-wc-editor-suite",
1017
+ "section": "tutorials",
1018
+ "title": "Web component: the editor suite",
1019
+ "order": 128,
1020
+ "teaches": [
1021
+ "wc-editor-suite"
1022
+ ],
1023
+ "mentions": [
1024
+ "wc-instance",
1025
+ "editor-language-support"
1026
+ ],
1027
+ "indexTerms": [
1028
+ "fsl-editor",
1029
+ "fsl-help",
1030
+ "fsl-docs",
1031
+ "toolbar",
1032
+ "web component suite"
1033
+ ],
1034
+ "body": "\n# Web component: the editor suite\n\nThe `fsl-*` editor suite composes a full editing surface: `<fsl-editor>` (a CodeMirror FSL editor), `<fsl-toolbar>`, `<fsl-footer>`, `<fsl-help>` (a docs drawer), and `<fsl-docs>` (this very curriculum, as a component), among others.\n\n```html\n<fsl-editor fsl=\"a 'go' -> b;\"></fsl-editor>\n<fsl-help open>\n <fsl-docs></fsl-docs>\n</fsl-help>\n```\n\n`<fsl-editor>` edits machines like:\n\n```fsl {teaches: wc-editor-suite, run: true}\nmachine_name : \"Edit me\";\na 'go' -> b 'go' -> a;\n```\n"
1035
+ },
1036
+ {
1037
+ "id": "tut-wc-instance",
1038
+ "section": "tutorials",
1039
+ "title": "Web component: <fsl-instance>",
1040
+ "order": 122,
1041
+ "teaches": [
1042
+ "wc-instance"
1043
+ ],
1044
+ "mentions": [
1045
+ "wc-viz",
1046
+ "api-machine"
1047
+ ],
1048
+ "indexTerms": [
1049
+ "fsl-instance",
1050
+ "interactive",
1051
+ "live machine"
1052
+ ],
1053
+ "body": "\n# Web component: &lt;fsl-instance&gt;\n\n`<fsl-instance>` hosts a *live* machine. Nested viz/panel components bind to it automatically, and it drives transitions — an interactive machine on the page.\n\n```html\n<fsl-instance fsl=\"Off 'flip' -> On 'flip' -> Off;\">\n <fsl-viz></fsl-viz>\n</fsl-instance>\n```\n\nThe hosted machine is ordinary FSL:\n\n```fsl {teaches: wc-instance, run: true}\nOff 'flip' -> On 'flip' -> Off;\n```\n"
1054
+ },
1055
+ {
1056
+ "id": "tut-wc-panels",
1057
+ "section": "tutorials",
1058
+ "title": "Web component: inspector panels",
1059
+ "order": 126,
1060
+ "teaches": [
1061
+ "wc-panels"
1062
+ ],
1063
+ "mentions": [
1064
+ "wc-instance"
1065
+ ],
1066
+ "indexTerms": [
1067
+ "fsl-info-panel",
1068
+ "fsl-effective-properties",
1069
+ "inspector",
1070
+ "panel"
1071
+ ],
1072
+ "body": "\n# Web component: inspector panels\n\nThe inspector panels read a hosted machine and show its internals: `<fsl-info-panel>` (current state, available actions) and `<fsl-effective-properties>` (the resolved properties of the active state).\n\n```html\n<fsl-instance fsl=\"Idle 'start' -> Running 'stop' -> Idle;\">\n <fsl-info-panel></fsl-info-panel>\n <fsl-effective-properties></fsl-effective-properties>\n</fsl-instance>\n```\n\nThey inspect a machine such as:\n\n```fsl {teaches: wc-panels, run: true}\nIdle 'start' -> Running 'stop' -> Idle;\n```\n"
1073
+ },
1074
+ {
1075
+ "id": "tut-wc-viz",
1076
+ "section": "tutorials",
1077
+ "title": "Web component: <fsl-viz>",
1078
+ "order": 120,
1079
+ "teaches": [
1080
+ "wc-viz"
1081
+ ],
1082
+ "mentions": [
1083
+ "viz-render"
1084
+ ],
1085
+ "indexTerms": [
1086
+ "fsl-viz",
1087
+ "web component",
1088
+ "custom element",
1089
+ "diagram"
1090
+ ],
1091
+ "body": "\n# Web component: &lt;fsl-viz&gt;\n\n`<fsl-viz>` renders a machine as a diagram with zero JavaScript — set its `fsl` attribute and it draws.\n\n```html\n<script type=\"module\" src=\"https://unpkg.com/jssm/dist/cdn/viz.js\"></script>\n\n<fsl-viz fsl=\"Red 'go' -> Green 'go' -> Red;\"></fsl-viz>\n```\n\nWhatever FSL you give it renders, styling and all:\n\n```fsl {teaches: wc-viz, run: true}\nstate Go : { color: ForestGreen; };\nStop 'go' -> Go;\n```\n\n(The legacy `jssm-viz` tag still works but is deprecated — prefer `fsl-viz`.)\n"
1092
+ },
1093
+ {
1094
+ "id": "tut-weighted-arrows",
1095
+ "section": "tutorials",
1096
+ "title": "Weighted / probabilistic arrows",
1097
+ "order": 66,
1098
+ "teaches": [
1099
+ "weighted-arrows"
1100
+ ],
1101
+ "mentions": [
1102
+ "transitions"
1103
+ ],
1104
+ "indexTerms": [
1105
+ "probability",
1106
+ "weight",
1107
+ "percent",
1108
+ "random"
1109
+ ],
1110
+ "body": "\n# Weighted / probabilistic arrows\n\nA transition can carry a **probability** with `N%`. When several transitions share a source, the weights bias a random walk over the machine.\n\n```fsl {teaches: weighted-arrows, run: true}\nIdle -> 70% Win;\nIdle -> 30% Lose;\n```\n\nProbabilities power FSL's stochastic tooling — random walks, sampling, and Monte-Carlo-style exploration of a machine's reachable states.\n"
185
1111
  }
186
1112
  ];
187
1113
  const DOCS_FEATURES = [
@@ -842,6 +1768,120 @@ function parseFenceInfo(info) {
842
1768
  return { lang, attrs };
843
1769
  }
844
1770
  const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1771
+ /** Structural / attribute keywords highlighted in fsl code fences. */
1772
+ const FSL_KEYWORDS = new Set([
1773
+ 'state', 'start_state', 'end_state', 'active_state', 'terminal_state', 'hooked_state',
1774
+ 'transition', 'graph', 'on', 'enter', 'exit', 'do', 'arrange', 'arrange-start', 'arrange-end',
1775
+ 'machine_name', 'machine_author', 'machine_license', 'machine_version', 'machine_comment',
1776
+ 'fsl_version', 'flow', 'graph_layout', 'allow_islands', 'allows_override', 'required',
1777
+ 'true', 'false', 'null', 'undefined',
1778
+ ]);
1779
+ /**
1780
+ * Attribute keys whose value is a color, mirroring the editor's `COLOR_KEYS`
1781
+ * (language_service). The value token following one of these (plus its `:`) is
1782
+ * tagged `color` and rendered with a swatch, matching the editor overlay.
1783
+ */
1784
+ const FSL_COLOR_KEYS = new Set([
1785
+ 'color', 'text-color', 'background-color', 'border-color', 'edge-color',
1786
+ ]);
1787
+ /** Whether `text` is a bare FSL identifier (so it can be an attribute key). */
1788
+ const isIdentifier = (text) => /^[A-Za-z_][A-Za-z0-9_-]*$/.test(text);
1789
+ /**
1790
+ * Tokenize FSL source into `{cls, text}` runs for syntax highlighting. A pure,
1791
+ * regex-driven scanner — never parses, so it cannot throw on malformed input.
1792
+ * `cls` is null for uncategorized text (punctuation, identifiers, whitespace).
1793
+ *
1794
+ * Beyond the lexical classes it tracks one bit of structural context: an
1795
+ * identifier immediately before a `:` is retro-tagged `key` (an attribute key,
1796
+ * unless it is already a `keyword`), and the value token after a color key's
1797
+ * colon — or any hex literal — is tagged `color`. The context never spans a
1798
+ * `;`, so a value can't leak past its statement.
1799
+ *
1800
+ * @example
1801
+ * tokenizeFsl('s : { background-color: pink; }')
1802
+ * .filter(t => t.cls).map(t => [t.cls, t.text]);
1803
+ * // includes ['key','background-color'] and ['color','pink']
1804
+ */
1805
+ function tokenizeFsl(src) {
1806
+ const toks = [];
1807
+ let p = 0;
1808
+ let expectColorValue = false; // the next value token is the value of a color key
1809
+ while (p < src.length) {
1810
+ const rest = src.slice(p);
1811
+ let m;
1812
+ if ((m = /^\/\/[^\n]*/.exec(rest))) {
1813
+ toks.push({ cls: 'comment', text: m[0] });
1814
+ }
1815
+ else if ((m = /^\/\*[\s\S]*?\*\//.exec(rest))) {
1816
+ toks.push({ cls: 'comment', text: m[0] });
1817
+ }
1818
+ else if ((m = /^"[^"]*"/.exec(rest))) {
1819
+ toks.push({ cls: 'string', text: m[0] });
1820
+ }
1821
+ else if ((m = /^'[^']*'/.exec(rest))) {
1822
+ toks.push({ cls: 'action', text: m[0] });
1823
+ }
1824
+ else if ((m = /^(?:<?[-=~]+>|[←→↔⇒⇐⇔↦])/.exec(rest))) {
1825
+ toks.push({ cls: 'arrow', text: m[0] });
1826
+ }
1827
+ else if ((m = /^#[0-9A-Fa-f]{3,8}\b/.exec(rest))) {
1828
+ toks.push({ cls: 'color', text: m[0] });
1829
+ expectColorValue = false;
1830
+ }
1831
+ else if ((m = /^\d+(?:\.\d+)*%?/.exec(rest))) {
1832
+ toks.push({ cls: 'number', text: m[0] });
1833
+ }
1834
+ else if ((m = /^[A-Za-z_][A-Za-z0-9_-]*/.exec(rest))) {
1835
+ const id = m[0];
1836
+ const cls = FSL_KEYWORDS.has(id) ? 'keyword' : (expectColorValue ? 'color' : null);
1837
+ expectColorValue = false; // any identifier consumes the pending value slot
1838
+ toks.push({ cls, text: id });
1839
+ }
1840
+ else {
1841
+ const ch = src[p];
1842
+ if (ch === ':') {
1843
+ for (let j = toks.length - 1; j >= 0; j--) {
1844
+ if (/^\s+$/.test(toks[j].text)) {
1845
+ continue;
1846
+ } // skip whitespace before the colon
1847
+ if (toks[j].cls === null && isIdentifier(toks[j].text)) {
1848
+ toks[j].cls = 'key';
1849
+ if (FSL_COLOR_KEYS.has(toks[j].text)) {
1850
+ expectColorValue = true;
1851
+ }
1852
+ }
1853
+ break; // only the immediately-preceding token
1854
+ }
1855
+ }
1856
+ else if (ch === ';') {
1857
+ expectColorValue = false; // a value can't cross a statement end
1858
+ }
1859
+ toks.push({ cls: null, text: ch });
1860
+ }
1861
+ p += m ? m[0].length : 1;
1862
+ }
1863
+ return toks;
1864
+ }
1865
+ /**
1866
+ * Highlight FSL source to an HTML string of `<span class="fsl-tok-…">` runs.
1867
+ * A `color` token is preceded by an inline `<span class="fsl-swatch">` whose
1868
+ * background is the literal color text (a CSS-valid named color or hex), giving
1869
+ * the docs the same swatch the editor overlay shows. Color text is a hex or
1870
+ * identifier run, so it is a safe `background:` value.
1871
+ */
1872
+ function highlightFsl(src) {
1873
+ return tokenizeFsl(src)
1874
+ .map(t => {
1875
+ if (!t.cls) {
1876
+ return esc(t.text);
1877
+ }
1878
+ if (t.cls === 'color') {
1879
+ return `<span class="fsl-tok-color"><span class="fsl-swatch" style="background:${esc(t.text)}"></span>${esc(t.text)}</span>`;
1880
+ }
1881
+ return `<span class="fsl-tok-${t.cls}">${esc(t.text)}</span>`;
1882
+ })
1883
+ .join('');
1884
+ }
845
1885
  function inline(s) {
846
1886
  return esc(s)
847
1887
  .replace(/`([^`]+)`/g, (_m, c) => `<code>${c}</code>`)
@@ -866,14 +1906,14 @@ function renderMarkdown(md) {
866
1906
  i++;
867
1907
  }
868
1908
  i++;
869
- const code = esc(buf.join('\n'));
1909
+ const raw = buf.join('\n');
870
1910
  if (lang === 'fsl') {
871
1911
  const teaches = attrs.teaches ? ` data-teaches="${esc(String(attrs.teaches))}"` : '';
872
1912
  const run = attrs.run === true ? ' data-run="true"' : '';
873
- out.push(`<pre data-fsl-example${teaches}${run}><code>${code}</code></pre>`);
1913
+ out.push(`<pre data-fsl-example${teaches}${run}><code>${highlightFsl(raw)}</code></pre>`);
874
1914
  }
875
1915
  else {
876
- out.push(`<pre><code>${code}</code></pre>`);
1916
+ out.push(`<pre><code>${esc(raw)}</code></pre>`);
877
1917
  }
878
1918
  continue;
879
1919
  }
@@ -1106,6 +2146,18 @@ FslDocs.styles = css `
1106
2146
  .nav a:hover { background: rgba(127,127,127,0.14); }
1107
2147
  .docs-page { font-size: 0.82rem; line-height: 1.55; }
1108
2148
  .docs-page pre { background: var(--_fsl-surface-alt, rgba(127,127,127,0.1)); padding: 0.5rem 0.6rem; border-radius: 6px; overflow-x: auto; }
2149
+ .docs-page pre code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.78rem; }
2150
+ .docs-page .fsl-tok-comment { color: var(--fsl-tok-comment, #7d8590); font-style: italic; }
2151
+ .docs-page .fsl-tok-string { color: var(--fsl-tok-string, #2e9e5b); }
2152
+ .docs-page .fsl-tok-action { color: var(--fsl-tok-action, #c2710c); }
2153
+ .docs-page .fsl-tok-arrow { color: var(--fsl-tok-arrow, var(--_fsl-accent, #6a4cd6)); font-weight: 600; }
2154
+ .docs-page .fsl-tok-number { color: var(--fsl-tok-number, #3b82f6); }
2155
+ .docs-page .fsl-tok-keyword { color: var(--fsl-tok-keyword, #a371f7); font-weight: 600; }
2156
+ .docs-page .fsl-tok-key { color: var(--fsl-tok-key, #0e7490); }
2157
+ .docs-page .fsl-swatch {
2158
+ display: inline-block; width: 0.72em; height: 0.72em; margin-right: 0.3em; vertical-align: baseline;
2159
+ border: 1px solid var(--_fsl-border, rgba(127,127,127,0.5)); border-radius: 2px;
2160
+ }
1109
2161
  .docs-load-example { display: block; margin-top: 0.4rem; font: inherit; font-size: 0.7rem; cursor: pointer; }
1110
2162
  .search-input { width: 100%; box-sizing: border-box; padding: 0.35rem 0.5rem; margin: 0.25rem 0 0.5rem;
1111
2163
  background: var(--_fsl-surface); color: var(--_fsl-text); border: 1px solid var(--_fsl-border); border-radius: 4px; }