@ponchia/ui 0.6.5 → 0.6.7

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 (108) hide show
  1. package/CHANGELOG.md +170 -0
  2. package/README.md +43 -23
  3. package/behaviors/carousel.d.ts.map +1 -1
  4. package/behaviors/carousel.js +3 -0
  5. package/behaviors/dialog.d.ts.map +1 -1
  6. package/behaviors/dialog.js +14 -8
  7. package/behaviors/forms.d.ts.map +1 -1
  8. package/behaviors/forms.js +11 -5
  9. package/behaviors/index.d.ts +2 -0
  10. package/behaviors/index.d.ts.map +1 -1
  11. package/behaviors/index.js +2 -0
  12. package/behaviors/internal.d.ts +2 -1
  13. package/behaviors/internal.d.ts.map +1 -1
  14. package/behaviors/internal.js +23 -3
  15. package/behaviors/legend.d.ts.map +1 -1
  16. package/behaviors/legend.js +41 -9
  17. package/behaviors/splitter.d.ts +26 -0
  18. package/behaviors/splitter.d.ts.map +1 -0
  19. package/behaviors/splitter.js +200 -0
  20. package/behaviors/table.js +3 -3
  21. package/behaviors/theme.js +2 -2
  22. package/classes/classes.json +230 -4
  23. package/classes/index.d.ts +49 -1
  24. package/classes/index.js +56 -1
  25. package/classes/vscode.css-custom-data.json +1 -1
  26. package/css/analytical.css +3 -1
  27. package/css/app.css +4 -4
  28. package/css/clamp.css +92 -0
  29. package/css/figure.css +102 -0
  30. package/css/highlights.css +50 -0
  31. package/css/interval.css +90 -0
  32. package/css/primitives.css +2 -3
  33. package/css/report-kit.css +38 -0
  34. package/css/report.css +51 -4
  35. package/css/sidenote.css +12 -2
  36. package/css/site.css +2 -1
  37. package/css/sources.css +5 -0
  38. package/css/state.css +120 -1
  39. package/css/table.css +4 -0
  40. package/css/tokens.css +9 -9
  41. package/css/workbench.css +101 -8
  42. package/dist/bronto.css +1 -1
  43. package/dist/css/analytical.css +1 -1
  44. package/dist/css/app.css +1 -1
  45. package/dist/css/clamp.css +1 -0
  46. package/dist/css/figure.css +1 -0
  47. package/dist/css/highlights.css +1 -0
  48. package/dist/css/interval.css +1 -0
  49. package/dist/css/primitives.css +1 -1
  50. package/dist/css/report-kit.css +1 -0
  51. package/dist/css/report.css +1 -1
  52. package/dist/css/sidenote.css +1 -1
  53. package/dist/css/site.css +1 -1
  54. package/dist/css/sources.css +1 -1
  55. package/dist/css/state.css +1 -1
  56. package/dist/css/table.css +1 -1
  57. package/dist/css/tokens.css +1 -1
  58. package/dist/css/workbench.css +1 -1
  59. package/docs/adr/0002-scope-and-2026-baseline.md +1 -1
  60. package/docs/architecture.md +67 -43
  61. package/docs/clamp.md +49 -0
  62. package/docs/contrast.md +34 -24
  63. package/docs/d2.md +37 -0
  64. package/docs/figure.md +71 -0
  65. package/docs/frontier-primitives.md +48 -23
  66. package/docs/highlights.md +52 -0
  67. package/docs/interop/tailwind.md +148 -0
  68. package/docs/interval.md +55 -0
  69. package/docs/legends.md +3 -2
  70. package/docs/mermaid.md +6 -0
  71. package/docs/migrations/0.2-to-0.3.md +80 -0
  72. package/docs/migrations/0.3-to-0.4.md +48 -0
  73. package/docs/migrations/0.4-to-0.5.md +96 -0
  74. package/docs/migrations/0.5-to-0.6.md +82 -0
  75. package/docs/package-contract.md +40 -2
  76. package/docs/reference.md +79 -6
  77. package/docs/reporting.md +132 -56
  78. package/docs/sidenote.md +7 -1
  79. package/docs/sources.md +1 -1
  80. package/docs/stability.md +5 -3
  81. package/docs/state.md +67 -10
  82. package/docs/theming.md +10 -2
  83. package/docs/usage.md +31 -11
  84. package/docs/workbench.md +59 -18
  85. package/llms.txt +82 -14
  86. package/package.json +68 -6
  87. package/qwik/index.d.ts +1 -0
  88. package/qwik/index.d.ts.map +1 -1
  89. package/qwik/index.js +26 -21
  90. package/react/index.d.ts +1 -0
  91. package/react/index.d.ts.map +1 -1
  92. package/react/index.js +4 -1
  93. package/schemas/report-claims.v1.schema.json +137 -0
  94. package/solid/index.d.ts +2 -0
  95. package/solid/index.d.ts.map +1 -1
  96. package/solid/index.js +3 -0
  97. package/svelte/index.d.ts +88 -0
  98. package/svelte/index.d.ts.map +1 -0
  99. package/svelte/index.js +166 -0
  100. package/tailwind.css +87 -0
  101. package/tokens/figma.variables.json +2241 -0
  102. package/tokens/index.js +1 -1
  103. package/tokens/index.json +2 -2
  104. package/tokens/resolved.json +3 -3
  105. package/tokens/tokens.dtcg.json +1 -1
  106. package/vue/index.d.ts +79 -0
  107. package/vue/index.d.ts.map +1 -0
  108. package/vue/index.js +197 -0
package/docs/reporting.md CHANGED
@@ -1,12 +1,19 @@
1
1
  # Static reports
2
2
 
3
3
  `@ponchia/ui` can dress static, LLM-authored HTML reports without a component
4
- runtime. Load the normal bundle, then opt in to the report layer, chart palette,
5
- and annotation layer only when the report needs them.
4
+ runtime. Load the normal bundle, then either opt in to the complete report kit
5
+ or import only the leaves a narrow report actually uses.
6
6
 
7
7
  In a bundled app, package specifiers are fine because Vite or another bundler
8
8
  rewrites them:
9
9
 
10
+ ```css
11
+ @import '@ponchia/ui';
12
+ @import '@ponchia/ui/css/report-kit.css';
13
+ ```
14
+
15
+ For tighter payload control, import leaves one by one:
16
+
10
17
  ```css
11
18
  @import '@ponchia/ui';
12
19
  @import '@ponchia/ui/css/report.css';
@@ -16,9 +23,11 @@ rewrites them:
16
23
  ```
17
24
 
18
25
  **`dist/bronto.css` is the standard component set only — it does NOT contain
19
- the report, chart, annotation, or legend layers.** Those are opt-in leaves
20
- under `dist/css/`; a report links the default bundle *and* each leaf it uses.
21
- Forgetting them is the most common way an LLM-emitted report renders unstyled.
26
+ the report, chart, annotation, source, trust, or code/diff layers.**
27
+ `report-kit.css` is the one-file opt-in path for complete static reports; the
28
+ individual leaves remain available under `dist/css/` when a report uses only a
29
+ small subset. Forgetting the opt-in CSS is the most common way an LLM-emitted
30
+ report renders unstyled.
22
31
 
23
32
  For standalone browser HTML, use real stylesheet URLs. Package specifiers like
24
33
  `@ponchia/ui/css/report.css` do not resolve in a saved `.html` file — and note
@@ -27,6 +36,14 @@ the path is `dist/css/`, the built leaf, not the source `css/`:
27
36
  ```html
28
37
  <!-- installed locally -->
29
38
  <link rel="stylesheet" href="./node_modules/@ponchia/ui/dist/bronto.css" />
39
+ <link rel="stylesheet" href="./node_modules/@ponchia/ui/dist/css/report-kit.css" />
40
+ ```
41
+
42
+ Or, if you are intentionally keeping a report minimal, link only the leaves it
43
+ uses:
44
+
45
+ ```html
46
+ <link rel="stylesheet" href="./node_modules/@ponchia/ui/dist/bronto.css" />
30
47
  <link rel="stylesheet" href="./node_modules/@ponchia/ui/dist/css/report.css" />
31
48
  <link rel="stylesheet" href="./node_modules/@ponchia/ui/dist/css/dataviz.css" />
32
49
  <link rel="stylesheet" href="./node_modules/@ponchia/ui/dist/css/annotations.css" />
@@ -37,11 +54,18 @@ No install? Link the same files from a CDN. Pin the version — pre-1.0, breakin
37
54
  changes ship in the minor (see [stability.md](./stability.md)):
38
55
 
39
56
  ```html
40
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.5/dist/bronto.css" />
41
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.5/dist/css/report.css" />
42
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.5/dist/css/dataviz.css" />
43
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.5/dist/css/annotations.css" />
44
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.5/dist/css/legend.css" />
57
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.7/dist/bronto.css" />
58
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.7/dist/css/report-kit.css" />
59
+ ```
60
+
61
+ Leaf-by-leaf CDN imports use the same `dist/css/` paths:
62
+
63
+ ```html
64
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.7/dist/bronto.css" />
65
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.7/dist/css/report.css" />
66
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.7/dist/css/dataviz.css" />
67
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.7/dist/css/annotations.css" />
68
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.7/dist/css/legend.css" />
45
69
  ```
46
70
 
47
71
  The CDN serves the package's own `fonts/` next to the CSS, so font URLs resolve
@@ -59,19 +83,37 @@ sanitize that content before rendering it and do not initialize
59
83
  `css/report.css` gives you the document grammar (covers, sections, findings,
60
84
  evidence). The _content_ inside those sections is where the rest of the
61
85
  analytical layer earns its place. Each one is an opt-in import that stays out of
62
- the default bundle — add the leaves a given report actually needs, or pull the
63
- whole set with `@ponchia/ui/css/analytical.css`. Reach for:
86
+ the default bundle — add the leaves a given report actually needs.
87
+ `@ponchia/ui/css/analytical.css` is a convenience roll-up of the **nine
88
+ figure/evidence-layer leaves only** (figure, annotations, legends, marks,
89
+ connectors, spotlight, crosshair, selection, highlights) — sources, generated,
90
+ state, interval, clamp, and the prose/evidence leaves below are NOT in it and
91
+ must be linked individually. Reach for:
64
92
 
65
93
  | Layer | Import | Reach for it when… |
66
94
  | --- | --- | --- |
95
+ | **Figure stage** (`.ui-figure*`) | `css/figure.css` | A chart, diagram, screenshot, or annotated SVG needs a stable media box, overlay slot, right-side key, and fallback-data slot. It composes with `ui-report__figure`; it does not render charts or own scales. See [figure.md](./figure.md). |
67
96
  | **Marks** (`.ui-mark`, `.ui-bracket-note`) | `css/marks.css` | You want to emphasise a phrase _in prose_ — a highlight on the finding, an underline on a risk, or a bracket around an evidence/caveat passage. The inline counterpart to annotations. See [marks.md](./marks.md). |
68
97
  | **Sources / provenance** (`.ui-citation`, `.ui-source-card`, `.ui-source-list`, `.ui-provenance`) | `css/sources.css` | The report makes claims a reader will question — "where did this come from?". A CSS-only trust layer whose cross-cutting state modifier (`.ui-src--verified`, plus reviewed / generated / unverified / stale / conflict) sets a rationed tone, always paired with a written label, never colour alone. See [sources.md](./sources.md). |
98
+ | **Interval** (`.ui-interval*`) | `css/interval.css` | Evidence is a low/high estimate, confidence window, target band, or uncertain reading. The host normalises `--lo`, `--hi`, and optional `--v`; Bronto only paints the range and point. See [interval.md](./interval.md). |
99
+ | **Clamp** (`.ui-clamp*`) | `css/clamp.css` | A source excerpt, claim basis, or caveat should scan as a bounded text block but remain reachable through explicit "Show more" / "Show less" labels and print expansion. See [clamp.md](./clamp.md). |
100
+ | **Highlights** (`.ui-highlights`) | `css/highlights.css` | The host registers CSS Custom Highlight API ranges for cited evidence, search hits, or the current match without wrapping DOM text nodes. See [highlights.md](./highlights.md). |
69
101
  | **Annotations** (`.ui-annotation*`) | `css/annotations.css` | A figure needs an explicit callout — a peak, a limit, a watched region — or a small decorative margin mark. SVG only. See [annotations.md](./annotations.md) and the [off-chart + scaling notes](./annotations.md#using-annotations-off-chart) before you size one. |
70
102
  | **Legends / data keys** (`.ui-legend*`) | `css/legend.css` | A chart figure needs a colour key. WCAG 1.4.1 by construction. See [legends.md](./legends.md). |
71
103
  | **Mermaid theme** (`@ponchia/ui/mermaid`) | _(JS/JSON, no CSS)_ | The report embeds a [Mermaid](https://mermaid.js.org) diagram (flowchart, sequence, pie…) and you want it on-brand instead of generic. A resolved `base` theme projected from the same tokens as `charts.json`; annotate the rendered SVG with the annotation layer. See [mermaid.md](./mermaid.md). |
72
104
  | **D2 theme** (`@ponchia/ui/d2`) | _(JS/JSON, no CSS)_ | The report embeds a [D2](https://d2lang.com) diagram and you want it on-brand. Resolved theme-override slots (monochrome base + one rationed accent) projected from the same tokens; annotate the rendered SVG. See [d2.md](./d2.md). |
73
105
  | **Generated-content trust** (`.ui-generated`, `.ui-origin-label`, `.ui-reasoning`, `.ui-tool-log`) | `css/generated.css` | The report (or a section of it) is AI/system-authored and should _say so_ — an origin label plus quiet, collapsible reasoning / tool-call logs. Pairs with the sources layer. See [generated.md](./generated.md). |
74
106
  | **Lifecycle / system state** (`.ui-state`, `.ui-syncbar`) | `css/state.css` | A status report needs to show the state a thing is in — saving / queued / stale / conflict / reviewed — as a labelled object, not a bare coloured dot. See [state.md](./state.md). |
107
+ | **Spark** (`.ui-spark*`) | `css/spark.css` | A trend belongs _inside a sentence or table cell_ — a word-sized inline microchart, the inline counterpart to `ui-delta`/`ui-num`/`ui-stat`. See [spark.md](./spark.md). |
108
+ | **Bullet graph** (`.ui-bullet*`) | `css/bullet.css` | A measure needs "inside budget? vs target?" at a glance — the canonical SLO / error-budget figure that `ui-meter` structurally cannot express. See [bullet.md](./bullet.md). |
109
+ | **Diff** (`.ui-diff*`) | `css/diff.css` | The report shows what _changed_ — code review, changelogs, version history, config diffs. Marks call out a sentence; diff calls out a line. See [diff.md](./diff.md). |
110
+ | **Code** (`.ui-code*`) | `css/code.css` | Code-as-evidence: fenced snippets with an optional gutter and add/remove/highlight line states, sharing diff's change vocabulary. On-brand syntax colours via the [Shiki theme](./code.md). See [code.md](./code.md). |
111
+ | **Sidenotes** (`.ui-sidenote`, `.ui-marginnote`) | `css/sidenote.css` | Evidence, caveats, and provenance asides that belong _beside_ the prose, Tufte-style, instead of interrupting it. See [sidenote.md](./sidenote.md). |
112
+ | **Textref** (`.ui-textref`) | `css/textref.css` | A citation should deep-link to the _exact quoted sentence_ (URL text fragments, on-brand `::target-text` paint) — the inline counterpart to the source-card layer. See [textref.md](./textref.md). |
113
+ | **Term / glossary** (`.ui-term`, `.ui-glossary`) | `css/term.css` | Jargon should explain itself inline (native popover definition) and gather into an end-of-report glossary. See [term.md](./term.md). |
114
+ | **Contents rail** (`.ui-toc*`) | `css/toc.css` | A long report needs a sticky table of contents with the in-view section highlighted; degrades to a plain anchored list with zero JS. Distinct from the in-flow `ui-report__toc` block. See [toc.md](./toc.md). |
115
+ | **Tree** (`.ui-tree*`) | `css/tree.css` | Nested structure — file trees, object graphs, nested provenance — as a depth-indented outline on native `<details>`. See [tree.md](./tree.md). |
116
+ | **Dot surfaces** (`.ui-waffle`, `.ui-activity`, `.ui-level`, `.ui-dotgauge`, `.ui-readout`, …) | _(in the core bundle)_ | A count, rate, level, or gauge wants the library's signature dot-matrix expression — waffle units, activity strips, dot gauges, readouts. See [dots.md](./dots.md). |
75
117
 
76
118
  These compose with the report-native primitives already called out in
77
119
  [Composition rules](#composition-rules): `ui-statgrid`, `ui-alert`, `ui-table`,
@@ -187,7 +229,8 @@ fetching, and claim wording.
187
229
  audit reports. Add `ui-report__action-title`, `__action-owner`,
188
230
  `__action-due`, `__action-priority`, `__action-criteria`, and
189
231
  `__action-source` when the action is a real workflow item. The status text
190
- must be written out; do not rely on tone alone.
232
+ must be written out; do not rely on tone alone. The status occupies the side
233
+ rail on wide screens; the other action fields stay in the main text column.
191
234
 
192
235
  Decision brief pattern:
193
236
 
@@ -335,11 +378,11 @@ Evidence ledger pattern:
335
378
  deltas — they are not free-standing utilities.) Keep raw Markdown tables
336
379
  inside `ui-prose`; use `.ui-table` for curated evidence tables. If a
337
380
  `ui-report__evidence` block contains only a `ui-table-wrap`, the report layer
338
- removes the inner frame so evidence tables do not look double-boxed.
339
- Report tables preserve words by default so identifiers and headings do not
340
- split into unreadable fragments in PDF. Add `ui-table--break-anywhere` only
341
- for machine-token tables where avoiding horizontal overflow matters more than
342
- preserving words.
381
+ removes the inner frame so evidence tables do not look double-boxed. Report
382
+ tables preserve words by default so identifiers and headings do not split into
383
+ unreadable fragments in PDF. Add `ui-table--break-anywhere` only for
384
+ machine-token tables where fixed, shrink-to-fit columns and forced wrapping
385
+ matter more than preserving words.
343
386
  - Every `<figure>` should include a `figcaption` using `ui-report__caption`.
344
387
  - Do not use raw color values. Theme with `--accent`; use status tones for
345
388
  status; use chart tokens only in chart figures.
@@ -460,12 +503,14 @@ from one of two routes:
460
503
  painting marks from the `--chart-N` palette so the figure prints exactly and
461
504
  carries no runtime.
462
505
 
463
- Whichever route, the figure frame is the same: wrap it in `ui-report__figure`,
464
- caption it with `ui-report__caption`, give it the standalone, portable
465
- `.ui-legend` data key (`@ponchia/ui/css/legend.css` see
466
- [legends.md](./legends.md)), and pair every colour with a direct label, a
467
- pattern, **and** a fallback `ui-table` so the figure survives mono print and
468
- colour-vision deficiency.
506
+ Whichever route, the figure frame is the same: wrap it in
507
+ `ui-report__figure ui-figure`, caption it with
508
+ `ui-report__caption ui-figure__caption`, put the rendered chart in
509
+ `ui-figure__stage`, give it the standalone, portable `.ui-legend` data key
510
+ (`@ponchia/ui/css/legend.css` see [legends.md](./legends.md)), and pair every
511
+ colour with a direct label, a pattern, **and** a fallback `ui-table` in
512
+ `ui-figure__data` so the figure survives mono print and colour-vision
513
+ deficiency.
469
514
 
470
515
  A Vega-Lite figure. The live mount is **`ui-screen-only`** and the fallback
471
516
  `ui-table` carries the data into print — a live chart bakes the on-screen theme
@@ -473,14 +518,16 @@ into its SVG/canvas at render time, so printing it would emit a dark-baked chart
473
518
  on white paper. Print the table; keep the chart for screen:
474
519
 
475
520
  ```html
476
- <figure class="ui-report__figure" role="group" aria-labelledby="chart-title">
477
- <figcaption id="chart-title" class="ui-report__caption">
521
+ <figure class="ui-report__figure ui-figure" role="group" aria-labelledby="chart-title">
522
+ <figcaption id="chart-title" class="ui-report__caption ui-figure__caption">
478
523
  Fig 1 - Weekly focus split
479
524
  </figcaption>
480
- <div id="focus-chart" class="ui-screen-only" style="min-block-size: 240px">
481
- <noscript>Chart needs JavaScript — the data is in the table below.</noscript>
525
+ <div class="ui-figure__stage ui-screen-only" style="--figure-min-block: 240px">
526
+ <div id="focus-chart" class="ui-figure__media">
527
+ <noscript>Chart needs JavaScript — the data is in the table below.</noscript>
528
+ </div>
482
529
  </div>
483
- <div class="ui-table-wrap">
530
+ <div class="ui-figure__data ui-table-wrap">
484
531
  <table class="ui-table ui-table--dense">
485
532
  <caption>Chart source data</caption>
486
533
  <thead>
@@ -530,35 +577,42 @@ A frozen, token-themed inline `<svg>` for the same data — no runtime, prints
530
577
  exactly, with a `.ui-legend` key and the fallback table:
531
578
 
532
579
  ```html
533
- <figure class="ui-report__figure ui-print-exact" role="group" aria-labelledby="chart-title">
534
- <figcaption id="chart-title" class="ui-report__caption">
580
+ <figure class="ui-report__figure ui-figure ui-print-exact" role="group" aria-labelledby="chart-title">
581
+ <figcaption id="chart-title" class="ui-report__caption ui-figure__caption">
535
582
  Fig 1 - Weekly focus split
536
583
  </figcaption>
537
- <ul class="ui-legend" aria-label="Series">
538
- <li class="ui-legend__item">
539
- <span
540
- class="ui-legend__swatch"
541
- style="--chart-color: var(--chart-1); --chart-pattern: var(--chart-pattern-1)"
542
- aria-hidden="true"
543
- ></span>
544
- <span class="ui-legend__label">Research</span>
545
- </li>
546
- <li class="ui-legend__item">
547
- <span
548
- class="ui-legend__swatch"
549
- style="--chart-color: var(--chart-2); --chart-pattern: var(--chart-pattern-2)"
550
- aria-hidden="true"
551
- ></span>
552
- <span class="ui-legend__label">Delivery</span>
553
- </li>
554
- </ul>
555
- <svg viewBox="0 0 360 160" role="img" aria-labelledby="focus-svg-title">
556
- <title id="focus-svg-title">Weekly focus split</title>
557
- <line x1="36" y1="132" x2="324" y2="132" stroke="var(--line)" />
558
- <rect x="72" y="42" width="96" height="90" fill="var(--chart-1)" />
559
- <rect x="200" y="77" width="96" height="55" fill="var(--chart-2)" />
560
- </svg>
561
- <div class="ui-table-wrap">
584
+ <div class="ui-figure__body ui-figure__body--key-right">
585
+ <div class="ui-figure__stage">
586
+ <svg class="ui-figure__media" viewBox="0 0 360 160" role="img" aria-labelledby="focus-svg-title focus-svg-desc">
587
+ <title id="focus-svg-title">Weekly focus split</title>
588
+ <desc id="focus-svg-desc">Research is 18 hours and delivery is 11 hours.</desc>
589
+ <line x1="36" y1="132" x2="324" y2="132" stroke="var(--line)" />
590
+ <rect x="72" y="42" width="96" height="90" fill="var(--chart-1)" />
591
+ <rect x="200" y="77" width="96" height="55" fill="var(--chart-2)" />
592
+ </svg>
593
+ </div>
594
+ <div class="ui-figure__key">
595
+ <ul class="ui-legend" aria-label="Series">
596
+ <li class="ui-legend__item">
597
+ <span
598
+ class="ui-legend__swatch"
599
+ style="--chart-color: var(--chart-1); --chart-pattern: var(--chart-pattern-1)"
600
+ aria-hidden="true"
601
+ ></span>
602
+ <span class="ui-legend__label">Research</span>
603
+ </li>
604
+ <li class="ui-legend__item">
605
+ <span
606
+ class="ui-legend__swatch"
607
+ style="--chart-color: var(--chart-2); --chart-pattern: var(--chart-pattern-2)"
608
+ aria-hidden="true"
609
+ ></span>
610
+ <span class="ui-legend__label">Delivery</span>
611
+ </li>
612
+ </ul>
613
+ </div>
614
+ </div>
615
+ <div class="ui-figure__data ui-table-wrap">
562
616
  <table class="ui-table ui-table--dense">
563
617
  <caption>Chart source data</caption>
564
618
  <thead>
@@ -807,8 +861,14 @@ important reports. The HTML stays the readable artifact; the sidecar lets a
807
861
  checker prove that claim IDs, source IDs, trust states, retrieval dates and
808
862
  high-risk decisions still line up after editing.
809
863
 
864
+ The declarative contract ships as
865
+ `@ponchia/ui/schemas/report-claims.v1.schema.json` for validators in any
866
+ language. It is a schema only — `@ponchia/ui` does not ship a report generator
867
+ or validation runtime.
868
+
810
869
  ```json
811
870
  {
871
+ "$schema": "https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.7/schemas/report-claims.v1.schema.json",
812
872
  "schemaVersion": "bronto-report-claims.v1",
813
873
  "report": { "title": "Decision readiness", "type": "decision" },
814
874
  "claims": [
@@ -830,6 +890,14 @@ high-risk decisions still line up after editing.
830
890
  "retrievedAt": "2026-06-08T10:00:00Z",
831
891
  "supports": ["claim-primary"]
832
892
  }
893
+ ],
894
+ "relations": [
895
+ {
896
+ "claimId": "claim-primary",
897
+ "sourceId": "source-primary",
898
+ "kind": "supports",
899
+ "note": "The metrics export supports the operational-risk conclusion."
900
+ }
833
901
  ]
834
902
  }
835
903
  ```
@@ -876,6 +944,14 @@ tooling.
876
944
  (Puppeteer ships its own). The repo's `scripts/render-pdf.mjs` is a working
877
945
  copy of this (`npm run report:pdf -- report.html`); it is a dev/example
878
946
  helper, not part of the published API — bronto does not own rendering.
947
+
948
+ **If the report renders figures from `<script type="module">`, do not load
949
+ it over `file://`** — browsers block relative module imports from `file://`
950
+ (CORS, opaque `null` origin), so the figures silently render empty in the
951
+ PDF. Serve the report over HTTP first; `render-pdf.mjs --serve` does this
952
+ (loopback server + load over `http://127.0.0.1`), waits for the report's
953
+ `data-report-ready` signal if it sets one, and logs page errors instead of
954
+ swallowing them.
879
955
  - **As a service / from another language:** run Chromium-as-a-service
880
956
  (e.g. **Gotenberg**'s `POST /forms/chromium/convert/html`, or a hosted CDP
881
957
  endpoint) and POST the HTML + the `dist/css/*` assets. A Python/Go/any host
package/docs/sidenote.md CHANGED
@@ -23,7 +23,13 @@ tradition — the channel for evidence, caveats, and provenance asides that belo
23
23
  (or a section) so the count starts at 1 there.
24
24
  2. **The margin gutter.** At the same breakpoint, give that container
25
25
  `padding-inline-end: calc(var(--sidenote-width) + var(--sidenote-gap))` so the
26
- floated note has room. `--sidenote-width` defaults to `12rem`.
26
+ floated note has room. The two knobs are **root-scoped** (defaults `12rem` /
27
+ `2rem`) precisely so this calc resolves on any container — and overriding
28
+ either one on the container re-sizes the notes and the gutter together.
29
+ **This is load-bearing, not cosmetic:** without the gutter the floated note
30
+ spills past the page edge — a horizontal-overflow defect any visual QA pass
31
+ will flag. In a constrained shell that cannot spare a gutter, use the
32
+ in-flow `ui-sidenote` (which collapses inline) rather than `ui-marginnote`.
27
33
 
28
34
  ```html
29
35
  <article style="counter-reset: ui-sidenote">
package/docs/sources.md CHANGED
@@ -44,7 +44,7 @@ host and add a tone. It draws a small pill with a leading trust dot:
44
44
 
45
45
  ```html
46
46
  <span class="ui-src ui-src--verified">Verified</span>
47
- <span class="ui-src ui-src--generated">AI-generated</span>
47
+ <span class="ui-src ui-src--generated">Machine-generated</span>
48
48
  <span class="ui-src ui-src--stale">Stale · 14d</span>
49
49
  ```
50
50
 
package/docs/stability.md CHANGED
@@ -21,15 +21,17 @@ shipped `files` entry, and the generated artifact provenance map — see
21
21
  | Design tokens | Stable names/roles | Token names and documented roles are public. Exact values and generated colour math outputs may change for visual tuning before 1.0. |
22
22
  | `--accent-1..6` | Stable names/roles | A subtle-to-bold accent ramp derived from `--accent`. Exact resolved values are visual tuning; algorithm changes require release-note visibility and resolver/browser checks. |
23
23
  | Tokens as data (`tokens.json`, `tokens.dtcg.json`, `tokens/resolved.json`) | Stable additive | The JSON shapes are public for non-CSS/non-JS consumers. `resolved.json` exposes `light`/`dark` (resolved colours) and `scale` (resolved non-colour scales). Token names/roles are stable; exact resolved values are visual tuning (pin `~0.x`). |
24
+ | Schemas (`schemas/*.schema.json`) | Stable additive | Declarative JSON Schema contracts for package-adjacent tooling data. Existing schema files and enum values are public within a compatible minor; new optional properties and new schema files are additive. No validator runtime ships. |
24
25
  | Theme axes | Mixed | `data-theme` (light/dark) is the **contractual** base. `data-surface="oled"`, `data-density`, and `data-contrast` are **convenience presets** — best-effort visual variants, **not** part of the stability contract; their presence and exact values may change for tuning. Computed-style smoke tests guard that the presets apply to their intended token families. |
25
26
  | Behavior attributes (`data-bronto-*`) | Stable | Attribute names and documented markup relationships are public. Behavior internals are not. |
26
27
  | Behavior functions (`@ponchia/ui/behaviors`) | Stable | Exported function names, option names, custom events, SSR no-op behavior, idempotency, and cleanup-returning contract are public. |
27
28
  | Glyph registry/renderers (`@ponchia/ui/glyphs`) | Stable additive | Existing glyph names stay valid. New glyphs are additive. Renderer option names and accessibility defaults are public. |
28
29
  | `.ui-icon` mask renderer | Stable | Class name, `--icon-size`, currentColor inheritance, and `--icon-mask` contract are public. The internal data URL encoding is not. |
29
- | React/Solid/Qwik bindings | Stable thin adapters | Hook/primitive names, optional peer behavior, root ref/signal/resolver support, and cleanup lifecycle are public. They remain wrappers over vanilla behaviors, not component APIs. |
30
+ | Framework lifecycle adapters (`react`/`solid`/`qwik`/`svelte`/`vue`) | Stable thin adapters | Hook/action/directive names, optional peer behavior where applicable, root ref/signal/resolver support, and cleanup lifecycle are public. They remain wrappers over vanilla behaviors, not component APIs. |
30
31
  | Skins (`@ponchia/ui/skins`, `css/skins.css`) | Stable additive | Existing skin names stay valid. New skins are additive. Skins are root-level choices. |
31
32
  | Charts (`@ponchia/ui/charts`, `charts.json`, `css/dataviz.css`) | Stable additive | Token names, JSON shape, and 8 categorical slots are public. Exact palette values may tune if gates and release notes justify it. |
32
33
  | Reports (`css/report.css`, `.ui-report*`, print utilities) | Stable additive | Report class names, BEM part names, and print utility names are public. Report CSS is opt-in and not imported by the default bundle. The data key now lives in the standalone Legends layer (below), not `css/report.css`; charting is via the Vega theme target (`@ponchia/ui/vega`, see [vega](./vega.md)) or a token-themed inline SVG, not a shipped renderer. |
34
+ | Report kit roll-up (`css/report-kit.css`) | Stable additive | A convenience `@import` of the complete static-report vocabulary. The set of leaves it bundles may grow additively; each leaf also stays individually exported. Opt-in, not in the default bundle. |
33
35
  | Annotations (`@ponchia/ui/annotations`, `css/annotations.css`, `.ui-annotation*`) | Stable additive | SVG annotation class names, recipe option names, and helper function names are public. Helper internals and exact path-control heuristics may tune before 1.0. |
34
36
  | Legends (`css/legend.css`, `.ui-legend*`, `@ponchia/ui/behaviors` `initLegend`) | Stable additive | Legend class names, recipe option names, and the `bronto:legend:toggle` event contract (`aria-pressed="true"` ⇒ shown) are public. Opt-in, not in the default bundle; swatch colours are gated to the `--chart-*` palette. |
35
37
  | Marks (`css/marks.css`, `.ui-mark*`, `.ui-bracket-note*`) | Stable additive | Text-mark and bracket-note class names and recipe option names are public. Opt-in, not in the default bundle. Uses semantic tones only. |
@@ -37,11 +39,11 @@ shipped `files` entry, and the generated artifact provenance map — see
37
39
  | Spotlight (`css/spotlight.css`, `.ui-spotlight*`, `.ui-tour-note*`, `initSpotlight`) | Stable additive | Spotlight/tour-note class names, the `--spot-*` custom-property contract, and the `data-bronto-spotlight`/`data-target` attributes are public. Opt-in, not in the default bundle. Not a tour engine. |
38
40
  | Crosshair (`css/crosshair.css`, `.ui-crosshair*`, `.ui-readout`, `initCrosshair`) | Stable additive | Crosshair/readout class names, the `--crosshair-x/y` properties, the `data-bronto-crosshair` attribute, and the `bronto:crosshair:move`/`:leave` event contract are public. Opt-in. Reports pointer position only — no data mapping. |
39
41
  | Selection states (`css/selection.css`, `.ui-sel*`) | Stable additive | The `.ui-sel`/`--on`/`--off`/`--maybe` emphasis classes and recipe options are public. Opt-in, cross-cutting. The host owns selection logic; Bronto only styles the states. |
40
- | Analytical roll-up (`css/analytical.css`) | Stable additive | A convenience `@import` of the seven analytical leaves (annotations, legend, marks, connectors, spotlight, crosshair, selection). The set of leaves it bundles may grow additively; each leaf also stays individually exported. Opt-in, not in the default bundle. |
42
+ | Analytical roll-up (`css/analytical.css`) | Stable additive | A convenience `@import` of the nine analytical leaves (figure, annotations, legend, marks, connectors, spotlight, crosshair, selection, highlights). The set of leaves it bundles may grow additively; each leaf also stays individually exported. Opt-in, not in the default bundle. |
41
43
  | Sources / provenance (`css/sources.css`, `.ui-citation*`, `.ui-source-card*`, `.ui-source-list*`, `.ui-provenance*`, `.ui-src--*`, `initSources`) | Stable additive | Citation/source/provenance class names, the cross-cutting `.ui-src--*` trust-state modifiers (always paired with an author label), the optional `data-bronto-sources` / `data-bronto-source-ref` behavior contract, `bronto:source:focus`, and the `ui.citation`/`ui.source`/`ui.provenance` recipes + `cls.sourceList` are public. Opt-in, not in the default bundle. |
42
44
  | Lifecycle state (`css/state.css`, `.ui-state*`, `.ui-syncbar`) | Stable additive | The `.ui-state`/`__label`/`__detail`/`--busy` classes, the canonical lifecycle state modifiers, `.ui-syncbar`, and the `ui.state` recipe are public. Opt-in, not in the default bundle. |
43
45
  | Generated / AI-trust (`css/generated.css`, `.ui-generated*`, `.ui-origin-label*`, `.ui-reasoning*`, `.ui-tool-log`, `.ui-tool-call*`) | Stable additive | The generated-content, origin-label (incl. `--ai`), reasoning-trace and tool-log/tool-call class names and the `ui.originLabel` recipe are public. Opt-in, not in the default bundle. Not a chat kit; no confidence widget. |
44
- | Workbench (`css/workbench.css`, `.ui-inspector*`, `.ui-property*`, `.ui-selectionbar*`) | Stable additive | Inspector, property-row and selection-bar class + BEM part names are public (class-only — no recipe). Opt-in, not in the default bundle. Splitters/drag handles are out of scope. |
46
+ | Workbench (`css/workbench.css`, `.ui-splitter*`, `.ui-inspector*`, `.ui-property*`, `.ui-selectionbar*`, `initSplitter`) | Stable additive | Splitter, inspector, property-row and selection-bar class + BEM part names are public (no recipe). `data-bronto-splitter`, `--splitter-pos`, `bronto:splitter:resize`, and the `initSplitter` cleanup contract are public. Opt-in, not in the default bundle. The host owns pane content, persistence, collapse policy, and selection state. |
45
47
  | Command palette (`css/command.css`, `.ui-command*`, `initCommand`, `useCommand`) | Stable additive | Command class/part names, the `data-bronto-command` attribute, and the event contract — `bronto:command:select` (`detail: { value, label }`) and `bronto:command:close` — are public, plus the `useCommand` binding hook. Bronto filters + navigates (APG combobox/listbox); the host owns the action registry/execution. Opt-in, not in the default bundle, no global hotkey. |
46
48
  | Controlled-modal focus trap (`initModal`, `useModal`, `data-bronto-modal`) | Stable additive | For the `.ui-modal.is-open` (non-`<dialog>`) path: the `data-bronto-modal` opt-in marker, the `inert`-based focus trap + focus-return, and the cancelable `bronto:modal:close` (`detail: { reason }`) event are public. The consumer still owns the `is-open` class; the behavior never changes visibility. The native `<dialog>` path (`initDialog`) is the default and gets the trap for free. |
47
49
  | Keyboard-shortcut hint (`.ui-shortcut`, `.ui-shortcut__sep`) | Stable additive | Class names for the chord/sequence hint over `.ui-kbd` are public. Ships in the core layer (class-only, no recipe). |
package/docs/state.md CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  `@ponchia/ui/css/state.css` is an opt-in vocabulary for the states real apps
4
4
  spend their time in — saving, saved, queued, offline, stale, conflicted, locked,
5
- reviewed. These are usually improvised per product, so even good apps feel
5
+ reviewed, and background work still running after the initiating interaction is
6
+ gone. These are usually improvised per product, so even good apps feel
6
7
  inconsistent. This is the canonical set: a labelled state object with a rationed
7
- tone, plus a page/document sync bar.
8
+ tone, a page/document sync bar, and a persistent background-job row.
8
9
 
9
10
  ```css
10
11
  @import '@ponchia/ui';
@@ -12,10 +13,10 @@ tone, plus a page/document sync bar.
12
13
  ```
13
14
 
14
15
  Bronto ships the visual states and the canonical wording. The host owns the
15
- state machine, retry policy, persistence, and announcements. **Persistent state
16
- deserves persistent UI** — a toast is secondary, not the answer. The tone is a
17
- second channel; the **label is the state**, so it survives forced-colors and
18
- screen readers (WCAG 1.4.1).
16
+ state machine, retry policy, persistence, cancellation, and announcements.
17
+ **Persistent state deserves persistent UI** — a toast is secondary, not the
18
+ answer. The tone is a second channel; the **label is the state**, so it
19
+ survives forced-colors and screen readers (WCAG 1.4.1).
19
20
 
20
21
  ## `.ui-state`
21
22
 
@@ -68,6 +69,61 @@ the other.
68
69
  </div>
69
70
  ```
70
71
 
72
+ ## `.ui-job`
73
+
74
+ A durable row for background jobs, imports, exports, sync runs, and pipelines.
75
+ It is deliberately not a task runner: the host owns polling, retry/cancel
76
+ semantics, queue position, partial failures, and completion messages. Bronto
77
+ paints a persistent status/progress object so long-running work is not hidden in
78
+ a transient toast.
79
+
80
+ ```html
81
+ <article
82
+ class="ui-job ui-job--running"
83
+ style="--job-progress: 64%"
84
+ aria-labelledby="job-title"
85
+ >
86
+ <div class="ui-job__head">
87
+ <h3 class="ui-job__title" id="job-title">Importing listings</h3>
88
+ <span class="ui-state ui-state--saving ui-state--busy">
89
+ <span class="ui-state__label">Running</span>
90
+ <span class="ui-state__detail">64%</span>
91
+ </span>
92
+ </div>
93
+ <p class="ui-job__body">124 of 194 records processed. Latest checkpoint saved.</p>
94
+ <div
95
+ class="ui-job__progress"
96
+ role="progressbar"
97
+ aria-label="Import progress"
98
+ aria-valuemin="0"
99
+ aria-valuemax="100"
100
+ aria-valuenow="64"
101
+ >
102
+ <span class="ui-job__bar"></span>
103
+ </div>
104
+ <div class="ui-job__actions">
105
+ <button class="ui-button ui-button--subtle ui-button--sm" type="button">View log</button>
106
+ <button class="ui-button ui-button--ghost ui-button--sm" type="button">Cancel</button>
107
+ </div>
108
+ </article>
109
+ ```
110
+
111
+ Use the written state as the source of truth:
112
+
113
+ | State | Class | Typical label | Use when… |
114
+ | --- | --- | --- | --- |
115
+ | Queued | `ui-job--queued` | "Queued" | The job is accepted but not running yet. |
116
+ | Running | `ui-job--running` | "Running" / "Syncing" | Work is actively progressing. |
117
+ | Blocked | `ui-job--blocked` | "Blocked" / "Waiting" | Work cannot proceed without another system or user action. |
118
+ | Failed | `ui-job--failed` | "Failed" | The job stopped and needs retry, inspection, or acknowledgement. |
119
+ | Complete | `ui-job--complete` | "Complete" | Work finished and the result is available. |
120
+
121
+ For determinate jobs, set `--job-progress` as a percentage on `.ui-job` and
122
+ put `role="progressbar"` plus `aria-valuenow/min/max` on `.ui-job__progress`.
123
+ For indeterminate jobs, omit the progress block or omit `aria-valuenow`, and
124
+ make the written state clear ("Running", "Waiting for worker", "Retrying").
125
+ Use `ui-job--compact` for dense queues.
126
+
71
127
  ## Recipe
72
128
 
73
129
  ```js
@@ -75,11 +131,12 @@ import { ui } from '@ponchia/ui/classes';
75
131
 
76
132
  ui.state({ state: 'saving', busy: true }); // "ui-state ui-state--saving ui-state--busy"
77
133
  ui.state({ state: 'conflict' }); // "ui-state ui-state--conflict"
134
+ ui.job({ state: 'running' }); // "ui-job ui-job--running"
78
135
  ```
79
136
 
80
137
  ## Scope
81
138
 
82
- CSS only — there is no JS yet. Auto-updating elapsed time ("2m ago") or live
83
- progress text is the host's job; a small optional behavior may come later if a
84
- real consumer needs it. Background-job progress and conflict-resolution
85
- affordances are deliberately deferred until then.
139
+ CSS only — there is no JS yet. Auto-updating elapsed time ("2m ago"), live
140
+ progress text, polling, cancellation, retry, and conflict-resolution affordances
141
+ are the host's job. A small optional behavior may come later if a real consumer
142
+ needs it.
package/docs/theming.md CHANGED
@@ -11,7 +11,7 @@ theme-owned neutral ramp endpoint:
11
11
 
12
12
  | Token | Derivation (light / dark) | Role |
13
13
  | --------------------- | -------------------------------------------- | ---- |
14
- | `--accent-strong` | `--accent` mixed 83% with black / 84% white | darker/lighter accent for hover, emphasis |
14
+ | `--accent-strong` | `--accent` mixed 83% with black / 80% white | darker/lighter accent for hover, emphasis |
15
15
  | `--accent-ramp-end` | white / black | neutral endpoint for the low-chroma OKLCH ramp |
16
16
  | `--accent-text` | `var(--accent-strong)` (alias) | **accent used as foreground text** — the on-surface, AA-safe one |
17
17
  | `--accent-soft` | `--accent` at 10% / 14% over transparent | tinted fills |
@@ -63,7 +63,7 @@ fills — follows automatically, in both light and dark.
63
63
  > defaults to `--accent-strong` (a darkened/​lightened accent). If you
64
64
  > re-brand to a pale hue, raw `--accent` would fail as text — override
65
65
  > `--accent-text` to a sufficiently dark/light value rather than
66
- > relying on the 83%/84% mix.
66
+ > relying on the 83%/80% mix.
67
67
  >
68
68
  > The defaults are tuned for both; verify if you deviate hard. The focus
69
69
  > ring is solid `--accent` (≥ 3:1 non-text) — re-brand to a near-`--bg`
@@ -303,6 +303,14 @@ palette) carry real `$value`s; the CSS-runtime-derived family
303
303
  `$value: null` + the CSS in `$extensions["com.ponchia.css"]` rather than
304
304
  fabricating a number — the resolvable knob is `color.<theme>.accent`.
305
305
 
306
+ `@ponchia/ui/tokens/figma.variables.json` is the resolved local handoff for
307
+ Figma Variables import/sync scripts. It is generated from `tokens/resolved.json`
308
+ and keeps a `Bronto / Color` collection with `Light` and `Dark` modes plus a
309
+ `Bronto / Scale` collection for spacing, radius, type, z-index and motion.
310
+ Colour values are exported as Figma-style RGBA objects; non-colour values keep
311
+ their original CSS value under `$extensions["com.ponchia.css"]` so importer
312
+ scripts do not lose units.
313
+
306
314
  ## Reading tokens from JS
307
315
 
308
316
  `@ponchia/ui/tokens` exposes the model as data. The ergonomic view only
package/docs/usage.md CHANGED
@@ -276,12 +276,14 @@ without it these widgets are unlabelled or unannounced:
276
276
  - **`ui-skiplink`** — keep it the first focusable element and point its `href`
277
277
  at the `id` of your main landmark.
278
278
 
279
- ## App shell: the admin dashboard frame
279
+ ## App shell: the service frame
280
280
 
281
- `ui-app-shell` is a CSS-only two-column admin frame (sidebar rail + main
282
- column) that collapses to a single column with a horizontal rail below 880px —
283
- no behavior required. The nesting matters; the rail is `ui-app-rail` and the
284
- content side is `ui-app-main`:
281
+ `ui-app-shell` is the default cross-service identity frame: a CSS-only
282
+ two-column app shell (sidebar rail + main column) that collapses to a single
283
+ column with a horizontal rail below 880px no behavior required. Use it for
284
+ ops tools, admin apps, internal services, generated dashboards, and any workflow
285
+ surface that should feel like part of the same system. The nesting matters; the
286
+ rail is `ui-app-rail` and the content side is `ui-app-main`:
285
287
 
286
288
  ```html
287
289
  <div class="ui-app-shell">
@@ -290,7 +292,9 @@ content side is `ui-app-main`:
290
292
  <nav class="ui-app-nav" aria-label="Primary">
291
293
  <span class="ui-app-nav__section">Main</span>
292
294
  <a href="/overview" aria-current="page">Overview</a>
293
- <a href="/reports">Reports</a>
295
+ <a href="/jobs">Jobs</a>
296
+ <a href="/integrations">Integrations</a>
297
+ <a href="/settings">Settings</a>
294
298
  </nav>
295
299
  <div class="ui-app-rail__account">…</div>
296
300
  </aside>
@@ -310,6 +314,21 @@ Knobs: `--app-rail` sets the rail width (default 14rem); `ui-app-shell--full`
310
314
  drops the rail for a single-column app. `ui-app-nav` honours `aria-current="page"`
311
315
  (preferred) and the visual-only `.is-active`.
312
316
 
317
+ Service composition checklist:
318
+
319
+ - Start with `ui-app-shell`, `ui-app-rail`, `ui-app-nav`, `ui-app-main`,
320
+ `ui-app-topbar`, and `ui-app-content`.
321
+ - Put brand and account identity in the rail; do not rebuild that frame per app.
322
+ - Use `ui-app-toolbar` for filters/actions above a workflow surface.
323
+ - Use `ui-app-panel` for grouped operational sections, not nested cards.
324
+ - Use `ui-statgrid`/`ui-stat`, `ui-table`, `ui-field`, `ui-alert`, and
325
+ `ui-progress` inside panels; add opt-in `state.css` for `ui-state` lifecycle
326
+ labels.
327
+ - Keep nav current state on `aria-current="page"` so the visual cue and AT cue
328
+ match.
329
+ - Reach for opt-in `workbench.css` only when the service needs panes,
330
+ inspectors, or selected-object bulk actions.
331
+
313
332
  ## Menus: `data-bronto-menu` + `initMenu`
314
333
 
315
334
  A dropdown menu is a native `<details data-bronto-menu>` styled as
@@ -606,10 +625,11 @@ These are JS widgets wearing the Bronto look; without the behavior they are iner
606
625
  | Tabs (`ui-tabs`) | `initTabs` | **author panels visible** — ship `hidden` panels and if `initTabs` never runs the content is unreachable |
607
626
  | Combobox (`ui-combobox`) | `initCombobox` | a plain text input beside an unfiltered list |
608
627
  | Command palette (`ui-command`) | `initCommand` | a static, unfiltered list |
628
+ | Splitter (`ui-splitter`) | `initSplitter` | fixed panes at the authored `--splitter-pos`; no keyboard/pointer resize or `aria-valuenow` updates |
609
629
  | Table sort/select (`[data-bronto-sortable]`) | `initTableSort` | a static table (still readable) |
610
630
  | Popover (`ui-popover`) | `initPopover` | no placement/ARIA — prefer the native `popover` attribute |
611
631
  | Carousel (`ui-carousel`) | `initCarousel` | a native scroll-snap track (usable, no controls) |
612
- | Controlled modal (`ui-modal.is-open`) | `initModal` | no focus trapprovide one or use native `<dialog>` |
632
+ | Controlled modal (`ui-modal.is-open`) | `initModal` | open skin onlyno inert trap, focus-return, or Escape close signal |
613
633
  | Menu (`data-bronto-menu`) | `initMenu` | a button next to a list with no open/close, outside-click, or Escape |
614
634
  | Toast | `toast()` | nothing — it is imperative-only |
615
635
 
@@ -628,10 +648,10 @@ boundary of what CSS alone cannot do.
628
648
  The CSS is the framework; `@ponchia/ui/behaviors` is the *sanctioned*
629
649
  home for the little JS that genuinely needs scripting (theme persistence,
630
650
  disclosure, dialog glue, modal focus-trap, toast, combobox, form-validation,
631
- table-sort). Reach for it instead of reimplementing — every initializer is
632
- SSR-safe, idempotent, and returns a cleanup. If you find yourself writing focus
633
- management or `aria-expanded` toggling by hand, there is probably already
634
- a behavior for it.
651
+ table-sort, splitter resizing). Reach for it instead of reimplementing — every
652
+ initializer is SSR-safe, idempotent, and returns a cleanup. If you find yourself
653
+ writing focus management, ARIA value sync, or `aria-expanded` toggling by hand,
654
+ there is probably already a behavior for it.
635
655
 
636
656
  ## Re-brand obligations (the short version)
637
657