@ponchia/ui 0.5.0 → 0.6.3

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 (196) hide show
  1. package/CHANGELOG.md +386 -4
  2. package/MIGRATIONS.json +14 -0
  3. package/README.md +29 -6
  4. package/annotations/index.d.ts +398 -276
  5. package/annotations/index.d.ts.map +1 -0
  6. package/annotations/index.js +350 -77
  7. package/behaviors/carousel.d.ts +28 -0
  8. package/behaviors/carousel.d.ts.map +1 -0
  9. package/behaviors/carousel.js +20 -16
  10. package/behaviors/combobox.d.ts +40 -0
  11. package/behaviors/combobox.d.ts.map +1 -0
  12. package/behaviors/combobox.js +111 -29
  13. package/behaviors/command.d.ts +41 -0
  14. package/behaviors/command.d.ts.map +1 -0
  15. package/behaviors/command.js +27 -15
  16. package/behaviors/connectors.d.ts +17 -0
  17. package/behaviors/connectors.d.ts.map +1 -0
  18. package/behaviors/connectors.js +7 -5
  19. package/behaviors/crosshair.d.ts +42 -0
  20. package/behaviors/crosshair.d.ts.map +1 -0
  21. package/behaviors/crosshair.js +23 -6
  22. package/behaviors/dialog.d.ts +20 -0
  23. package/behaviors/dialog.d.ts.map +1 -0
  24. package/behaviors/dialog.js +6 -2
  25. package/behaviors/disclosure.d.ts +10 -0
  26. package/behaviors/disclosure.d.ts.map +1 -0
  27. package/behaviors/disclosure.js +6 -2
  28. package/behaviors/dismissible.d.ts +10 -0
  29. package/behaviors/dismissible.d.ts.map +1 -0
  30. package/behaviors/dismissible.js +6 -2
  31. package/behaviors/forms.d.ts +27 -0
  32. package/behaviors/forms.d.ts.map +1 -0
  33. package/behaviors/forms.js +54 -13
  34. package/behaviors/glyph.d.ts +14 -0
  35. package/behaviors/glyph.d.ts.map +1 -0
  36. package/behaviors/glyph.js +28 -5
  37. package/behaviors/index.d.ts +31 -237
  38. package/behaviors/index.d.ts.map +1 -0
  39. package/behaviors/index.js +17 -0
  40. package/behaviors/inert.d.ts +20 -0
  41. package/behaviors/inert.d.ts.map +1 -0
  42. package/behaviors/inert.js +46 -0
  43. package/behaviors/internal.d.ts +25 -0
  44. package/behaviors/internal.d.ts.map +1 -0
  45. package/behaviors/internal.js +77 -1
  46. package/behaviors/legend.d.ts +35 -0
  47. package/behaviors/legend.d.ts.map +1 -0
  48. package/behaviors/legend.js +32 -2
  49. package/behaviors/menu.d.ts +16 -0
  50. package/behaviors/menu.d.ts.map +1 -0
  51. package/behaviors/menu.js +6 -2
  52. package/behaviors/modal.d.ts +41 -0
  53. package/behaviors/modal.d.ts.map +1 -0
  54. package/behaviors/modal.js +124 -0
  55. package/behaviors/popover.d.ts +28 -0
  56. package/behaviors/popover.d.ts.map +1 -0
  57. package/behaviors/popover.js +78 -7
  58. package/behaviors/spotlight.d.ts +17 -0
  59. package/behaviors/spotlight.d.ts.map +1 -0
  60. package/behaviors/spotlight.js +7 -5
  61. package/behaviors/table.d.ts +36 -0
  62. package/behaviors/table.d.ts.map +1 -0
  63. package/behaviors/table.js +84 -17
  64. package/behaviors/tabs.d.ts +20 -0
  65. package/behaviors/tabs.d.ts.map +1 -0
  66. package/behaviors/tabs.js +17 -14
  67. package/behaviors/theme.d.ts +54 -0
  68. package/behaviors/theme.d.ts.map +1 -0
  69. package/behaviors/theme.js +22 -3
  70. package/behaviors/toast.d.ts +49 -0
  71. package/behaviors/toast.d.ts.map +1 -0
  72. package/behaviors/toast.js +47 -3
  73. package/classes/classes.json +2527 -0
  74. package/classes/index.d.ts +134 -15
  75. package/classes/index.js +280 -80
  76. package/classes/vscode.css-custom-data.json +12 -0
  77. package/connectors/index.d.ts +201 -69
  78. package/connectors/index.d.ts.map +1 -0
  79. package/connectors/index.js +142 -25
  80. package/css/app.css +69 -13
  81. package/css/base.css +15 -10
  82. package/css/bullet.css +108 -0
  83. package/css/code.css +98 -0
  84. package/css/connectors.css +17 -0
  85. package/css/content.css +22 -3
  86. package/css/crosshair.css +7 -7
  87. package/css/dataviz.css +5 -1
  88. package/css/diff.css +153 -0
  89. package/css/disclosure.css +53 -7
  90. package/css/dots.css +94 -7
  91. package/css/feedback.css +97 -7
  92. package/css/forms.css +113 -4
  93. package/css/legend.css +16 -9
  94. package/css/marks.css +38 -8
  95. package/css/motion.css +98 -53
  96. package/css/navigation.css +7 -0
  97. package/css/overlay.css +90 -3
  98. package/css/primitives.css +158 -13
  99. package/css/report.css +73 -56
  100. package/css/sidenote.css +67 -0
  101. package/css/site.css +16 -2
  102. package/css/sources.css +43 -1
  103. package/css/spark.css +62 -0
  104. package/css/spotlight.css +1 -1
  105. package/css/table.css +9 -2
  106. package/css/term.css +110 -0
  107. package/css/textref.css +63 -0
  108. package/css/toc.css +91 -0
  109. package/css/tokens.css +49 -1
  110. package/css/tree.css +134 -0
  111. package/css/workbench.css +1 -1
  112. package/dist/bronto.css +1 -1
  113. package/dist/css/analytical.css +1 -1
  114. package/dist/css/app.css +1 -1
  115. package/dist/css/base.css +1 -1
  116. package/dist/css/bullet.css +1 -0
  117. package/dist/css/code.css +1 -0
  118. package/dist/css/connectors.css +1 -1
  119. package/dist/css/content.css +1 -1
  120. package/dist/css/crosshair.css +1 -1
  121. package/dist/css/diff.css +1 -0
  122. package/dist/css/disclosure.css +1 -1
  123. package/dist/css/dots.css +1 -1
  124. package/dist/css/feedback.css +1 -1
  125. package/dist/css/forms.css +1 -1
  126. package/dist/css/legend.css +1 -1
  127. package/dist/css/marks.css +1 -1
  128. package/dist/css/motion.css +1 -1
  129. package/dist/css/navigation.css +1 -1
  130. package/dist/css/overlay.css +1 -1
  131. package/dist/css/primitives.css +1 -1
  132. package/dist/css/report.css +1 -1
  133. package/dist/css/sidenote.css +1 -0
  134. package/dist/css/site.css +1 -1
  135. package/dist/css/sources.css +1 -1
  136. package/dist/css/spark.css +1 -0
  137. package/dist/css/spotlight.css +1 -1
  138. package/dist/css/table.css +1 -1
  139. package/dist/css/term.css +1 -0
  140. package/dist/css/textref.css +1 -0
  141. package/dist/css/toc.css +1 -0
  142. package/dist/css/tokens.css +1 -1
  143. package/dist/css/tree.css +1 -0
  144. package/dist/css/workbench.css +1 -1
  145. package/docs/adr/0003-theme-model.md +1 -1
  146. package/docs/annotations.md +133 -14
  147. package/docs/architecture.md +49 -6
  148. package/docs/bullet.md +78 -0
  149. package/docs/code.md +76 -0
  150. package/docs/contrast.md +116 -92
  151. package/docs/d2.md +196 -0
  152. package/docs/diff.md +146 -0
  153. package/docs/legends.md +23 -3
  154. package/docs/marks.md +9 -2
  155. package/docs/mermaid.md +169 -0
  156. package/docs/reference.md +201 -26
  157. package/docs/reporting.md +416 -57
  158. package/docs/sidenote.md +64 -0
  159. package/docs/sources.md +27 -0
  160. package/docs/spark.md +78 -0
  161. package/docs/stability.md +10 -2
  162. package/docs/term.md +81 -0
  163. package/docs/textref.md +78 -0
  164. package/docs/theming.md +44 -5
  165. package/docs/toc.md +83 -0
  166. package/docs/tree.md +74 -0
  167. package/docs/usage.md +354 -16
  168. package/docs/vega.md +244 -0
  169. package/docs/workbench.md +7 -1
  170. package/glyphs/glyphs.js +13 -5
  171. package/llms.txt +285 -14
  172. package/package.json +95 -17
  173. package/qwik/index.d.ts +44 -59
  174. package/qwik/index.d.ts.map +1 -0
  175. package/qwik/index.js +65 -3
  176. package/react/index.d.ts +41 -61
  177. package/react/index.d.ts.map +1 -0
  178. package/react/index.js +63 -3
  179. package/solid/index.d.ts +68 -61
  180. package/solid/index.d.ts.map +1 -0
  181. package/solid/index.js +66 -3
  182. package/tokens/d2.d.ts +38 -0
  183. package/tokens/d2.js +71 -0
  184. package/tokens/d2.json +43 -0
  185. package/tokens/index.d.ts +5 -5
  186. package/tokens/index.js +15 -1
  187. package/tokens/index.json +9 -0
  188. package/tokens/mermaid.d.ts +23 -0
  189. package/tokens/mermaid.js +181 -0
  190. package/tokens/mermaid.json +163 -0
  191. package/tokens/resolved.json +45 -1
  192. package/tokens/skins.js +3 -2
  193. package/tokens/tokens.dtcg.json +26 -0
  194. package/tokens/vega.d.ts +34 -0
  195. package/tokens/vega.js +155 -0
  196. package/tokens/vega.json +179 -0
@@ -77,7 +77,15 @@ offset, matching d3-annotation's mental model.
77
77
 
78
78
  The visible note text should also be represented in the figure caption,
79
79
  `<desc>`, fallback table, or surrounding prose when the figure is complex.
80
- Use `aria-hidden="true"` only for decorative annotations.
80
+
81
+ Match the accessibility treatment to the annotation's job, in both directions:
82
+ a **data** annotation (a peak, a threshold, a watched region — it says something
83
+ the reader needs) must stay readable, so represent its text as above and do
84
+ **not** `aria-hidden` it; a purely **decorative** mark (a cover flourish, a
85
+ margin doodle that carries no data) should be `aria-hidden="true"` and
86
+ `focusable="false"` on the whole SVG so a screen reader skips the decoration.
87
+ The one thing to avoid is the middle: a meaningful callout hidden from assistive
88
+ tech, or decoration announced as if it were data.
81
89
 
82
90
  ## Variants and motion
83
91
 
@@ -132,7 +140,7 @@ selections, DOM nodes, or frameworks.
132
140
  | --- | --- |
133
141
  | `annotationTransform({ x, y })` | Group transform for the subject anchor. |
134
142
  | `noteTransform({ dx, dy, align, valign, width, height })` | Note transform from the subject anchor, with optional alignment. |
135
- | `notePlacement({ x, y, width, height, bounds, preferred })` | Bounded note offset, alignment and transform for one annotation. |
143
+ | `notePlacement({ x, y, width, height, bounds, preferred, inset })` | Bounded note offset, alignment and transform for one annotation. `inset` reserves an extra margin (e.g. the title stroke-halo, ~3) so a placement that "fits" doesn't clip. |
136
144
  | `declutterLabels(items, { gap, min, max })` | Adjusted centres for `items` (`[{ pos, size }]`) so labels don't overlap along one axis (order-preserving). |
137
145
  | `directLabels(items, { axis, cross, gap, min, max, shape })` | Decluttered label points **and** a leader path per item: `[{ x, y, anchor, key, d }]`. |
138
146
  | `circleSubjectPath({ radius })` | Circle subject path. |
@@ -147,10 +155,10 @@ selections, DOM nodes, or frameworks.
147
155
  | `timelineEventPath({ size, direction })` | Event pin marker path. |
148
156
  | `evidenceMarkerPath({ x, y, width, height, padding })` | Centered square/rect evidence marker path. |
149
157
  | `connectorLine({ dx, dy, subject })` | Straight connector, trimmed against circle/rect subjects. |
150
- | `connectorElbow({ dx, dy, subject })` | D3-like dogleg connector. |
158
+ | `connectorElbow({ dx, dy, subject, mid })` | Right-angle dogleg connector (H/V/H); `mid` (0..1, default 0.5) sets the turn position along the dominant axis. |
151
159
  | `connectorCurve({ dx, dy, subject })` | Deterministic cubic connector. |
152
160
  | `connectorEndDot({ x, y, radius })` | Dot marker path. |
153
- | `connectorEndArrow({ x1, y1, x2, y2, size })` | Arrow marker path. |
161
+ | `connectorEndArrow({ x1, y1, x2, y2, size, spread })` | Arrow marker path. `x1,y1`→`x2,y2` sets the direction (the head sits at `x2,y2`); `spread` is the half-angle (default 0.32 ≈ a crisp 37° head). |
154
162
  | `annotationParts(options)` | Convenience object with `transform`, `subject`, `connector`, and `note`. |
155
163
 
156
164
  `declutterLabels` is a deliberately small, deterministic **1-D** declutter for
@@ -193,6 +201,119 @@ back to another side or a clamped note transform. It is not a collision solver
193
201
  for a whole chart. For dense annotation sets, pre-compute positions or author a
194
202
  mobile-specific SVG.
195
203
 
204
+ ### Using the helpers in a static, no-JS report
205
+
206
+ The [report layer](./reporting.md) is static and ships no behavior JS, but these
207
+ helpers are JS — so in a hand- or LLM-authored report you can't call them at
208
+ render time. Bridge the gap by running them **once, at author/build time**, and
209
+ pasting the returned strings straight into the SVG. The output is deterministic
210
+ (path numbers are rounded to three decimals), so the strings are stable and
211
+ diff-friendly.
212
+
213
+ ```js
214
+ // author-time only — copy the logged strings into the static HTML
215
+ import { circleSubjectPath, connectorLine } from '@ponchia/ui/annotations';
216
+
217
+ circleSubjectPath({ radius: 15 });
218
+ // "M0,-15A15,15 0 1 1 0,15A15,15 0 1 1 0,-15Z"
219
+ connectorLine({ dx: 78, dy: -38, subject: { type: 'circle', radius: 15, radiusPadding: 0 } });
220
+ // "M13.485,-6.57L78,-38"
221
+ ```
222
+
223
+ The static markup then carries only the resolved strings — no runtime, no
224
+ import:
225
+
226
+ ```html
227
+ <g class="ui-annotation ui-annotation--circle ui-annotation--accent" transform="translate(34, 58)">
228
+ <path class="ui-annotation__subject" d="M0,-15A15,15 0 1 1 0,15A15,15 0 1 1 0,-15Z" />
229
+ <path class="ui-annotation__connector" d="M13.485,-6.57L78,-38" />
230
+ <g class="ui-annotation__note" transform="translate(78, -38)">…</g>
231
+ </g>
232
+ ```
233
+
234
+ The same author-time-copy idea covers foreign-renderer theme literals: a
235
+ `file://` report can't `import` the Vega/Mermaid/D2 theme helpers either, so
236
+ `npm run emit:theme <vega|mermaid|d2> <light|dark>` prints the resolved object to
237
+ paste inline, and `npm run emit:theme:check <file>` re-checks a pasted block
238
+ against current tokens. See [vega.md](./vega.md#from-a-cdn-no-bundler).
239
+
240
+ ## Using annotations off-chart
241
+
242
+ Annotations are not only for charts. Two report uses worth calling out:
243
+
244
+ - **A decorative margin mark.** A small `ui-annotation` group — a circled point
245
+ with a short note — adds a hand-annotated feel to a report cover or section
246
+ opener. It carries no data, so mark the whole SVG `aria-hidden="true"` and
247
+ `focusable="false"`: a screen reader should not read decoration.
248
+
249
+ ```html
250
+ <svg width="440" height="92" viewBox="0 0 440 92" aria-hidden="true" focusable="false">
251
+ <g class="ui-annotation ui-annotation--circle ui-annotation--accent" transform="translate(34, 58)">
252
+ <path class="ui-annotation__subject" d="M0,-15A15,15 0 1 1 0,15A15,15 0 1 1 0,-15Z" />
253
+ <circle r="3.5" fill="var(--accent)" />
254
+ <path class="ui-annotation__connector" d="M13.485,-6.57L78,-38" />
255
+ <g class="ui-annotation__note" transform="translate(78, -38)">
256
+ <path class="ui-annotation__note-line" d="M0,0H188" />
257
+ <text class="ui-annotation__title" y="-8">You are here</text>
258
+ <text class="ui-annotation__label" y="12">a short, terse label</text>
259
+ </g>
260
+ </g>
261
+ </svg>
262
+ ```
263
+
264
+ - **A data note that sits off the plot area, statically (no JS).** When the
265
+ callout carries a *finding* — a threshold, a labelled event — rather than
266
+ decoration, it must **not** be `aria-hidden`; instead mirror its text in the
267
+ figure's `<desc>` and the fallback table so the data reaches every reader. The
268
+ JS `notePlacement()` helper computes this offset for you, but for a frozen
269
+ `file://`/print-bound report you place it by hand: reserve a band at the top of
270
+ the `viewBox` for the note (don't draw bars into it), keep the subject (the
271
+ rule/point) on the plot, and translate the `__note` group **up, above the
272
+ plot**, so the connector points off the plotting area. Author the `viewBox` near
273
+ the rendered pixel size to keep text near 1× (see the user-unit trap below):
274
+
275
+ ```html
276
+ <svg viewBox="0 0 360 180" role="img" aria-labelledby="fig-t fig-d" style="max-inline-size: 540px; width: 100%">
277
+ <title id="fig-t">Write success rate over the incident day</title>
278
+ <!-- The note text is repeated here so it is not JS- or sight-only. -->
279
+ <desc id="fig-d">…dashed line marks the 99.9% SLO floor; the off-plot callout reads "SLO floor 99.9% — below floor 41 min".</desc>
280
+
281
+ <line x1="36" y1="150" x2="336" y2="150" stroke="var(--line)" /><!-- axis -->
282
+ <!-- bars drawn only below y≈40, leaving the top band free for the note -->
283
+
284
+ <!-- subject rule ON the plot; note translated UP, off the plotting area -->
285
+ <g class="ui-annotation ui-annotation--threshold ui-annotation--danger" transform="translate(0, 44)">
286
+ <path class="ui-annotation__subject" d="M36,0L336,0" stroke-dasharray="4 4" />
287
+ <path class="ui-annotation__connector" d="M180,0L150,-30" />
288
+ <g class="ui-annotation__note" transform="translate(40, -34)">
289
+ <path class="ui-annotation__note-line" d="M0,0H150" />
290
+ <text class="ui-annotation__title" y="-8">SLO floor 99.9%</text>
291
+ <text class="ui-annotation__label" y="12">Below floor 41 min</text>
292
+ </g>
293
+ </g>
294
+ </svg>
295
+ ```
296
+
297
+ - **Bracketing a passage of prose belongs to marks, not here.** To bracket a
298
+ sentence or paragraph in running text, use `.ui-bracket-note` from the
299
+ [marks layer](./marks.md) — it is the prose analogue of
300
+ `ui-annotation--bracket`. SVG annotations are for SVG figures.
301
+
302
+ ## Sizing: the user-unit trap
303
+
304
+ Annotation text (`__title`, `__label`) is sized in **SVG user units**, so it
305
+ scales with the figure. A 360-unit-wide chart stretched across a full report
306
+ column is scaled roughly 2.5–3×, and the callout text scales with it — long
307
+ notes turn huge and overflow the `viewBox` (SVG text is clipped, not wrapped).
308
+ Two rules keep callouts readable:
309
+
310
+ - **Keep note text terse** — a title and a few words, like the recipe examples
311
+ (`Peak`, `Limit`, `80 kB cap`). Push the full sentence into the figure caption,
312
+ the `<desc>`, or the fallback table.
313
+ - **Constrain the figure width** so the user-unit → pixel scale stays near
314
+ 1–1.5×: set a `max-inline-size` on the SVG instead of letting it stretch to the
315
+ whole column, or author the `viewBox` at roughly the rendered pixel size.
316
+
196
317
  ## Density and responsive rules
197
318
 
198
319
  Annotations are strongest when they explain the few things a reader would miss.
@@ -294,8 +415,8 @@ stable references, threshold annotations for limits, circle/rect subjects for
294
415
  specific data, and badge markers for compact index points.
295
416
 
296
417
  ```html
297
- <figure class="ui-report__figure ui-chart ui-print-exact" role="group" aria-labelledby="annotated-chart">
298
- <figcaption id="annotated-chart" class="ui-chart__caption">
418
+ <figure class="ui-report__figure ui-print-exact" role="group" aria-labelledby="annotated-chart">
419
+ <figcaption id="annotated-chart" class="ui-report__caption">
299
420
  Fig 2 - Weekly throughput, annotated at the peak
300
421
  </figcaption>
301
422
  <svg viewBox="0 0 360 160" role="img" aria-labelledby="throughput-title throughput-desc">
@@ -328,14 +449,12 @@ specific data, and badge markers for compact index points.
328
449
  </g>
329
450
  </g>
330
451
  </svg>
331
- <div class="ui-chart__fallback">
332
- <div class="ui-table-wrap">
333
- <table class="ui-table ui-table--dense">
334
- <caption>Annotated chart source data</caption>
335
- <thead><tr><th>Week</th><th class="is-num">Hours</th></tr></thead>
336
- <tbody><tr><td>Week 4</td><td class="is-num">18</td></tr></tbody>
337
- </table>
338
- </div>
452
+ <div class="ui-table-wrap">
453
+ <table class="ui-table ui-table--dense">
454
+ <caption>Annotated chart source data</caption>
455
+ <thead><tr><th>Week</th><th class="is-num">Hours</th></tr></thead>
456
+ <tbody><tr><td>Week 4</td><td class="is-num">18</td></tr></tbody>
457
+ </table>
339
458
  </div>
340
459
  </figure>
341
460
  ```
@@ -61,7 +61,7 @@ on top of the CSS, none of which require a framework commitment**:
61
61
  not in three places (the two CSS dark blocks are now identical by
62
62
  construction), resolving the duplication ADR-0003 flagged. The CSS-only
63
63
  presets (density / contrast / OLED) stay hand-authored below a marker and are
64
- preserved across regeneration. `scripts/check-tokens.mjs` fails CI if
64
+ preserved across regeneration. `scripts/check-fresh.mjs` fails CI if
65
65
  `css/tokens.css` drifts from the model.
66
66
  - **classes/** — `cls` is the flat registry; recipes only emit from it;
67
67
  `scripts/check-classes.mjs` enforces a bidirectional match with the
@@ -79,6 +79,51 @@ on top of the CSS, none of which require a framework commitment**:
79
79
  - **react/** / **solid/** / **qwik/** — optional lifecycle adapters over `behaviors/`.
80
80
  They do not define markup, own state, or fork behavior logic; they only run
81
81
  the vanilla initializers on mount and cleanup on unmount/dispose.
82
+ - **`css/analytical.css` — the analytical roll-up.** This convenience file
83
+ `@import`s exactly **seven** analytical-figure leaves: `annotations`,
84
+ `legend`, `marks`, `connectors`, `spotlight`, `crosshair`, and `selection`.
85
+ The adjacent opt-in leaves — `sources`, `state`, `generated`, `workbench`,
86
+ and `command` — are report/tooling/trust surfaces that are intentionally
87
+ **not** part of the analytical roll-up and must be imported individually.
88
+ Importing `analytical.css` does not pull in any of those five.
89
+ - **Root export (`.`) is CSS-only.** `exports["."]` resolves to the CSS
90
+ bundle (`dist/bronto.css`). It is a CSS side-effect import for CSS-aware
91
+ bundlers (`@import '@ponchia/ui'` in CSS, or a side-effect
92
+ `import '@ponchia/ui'` in Vite/Astro/SvelteKit). There is no runtime JS at
93
+ the package root — Node/runtime JS imports of `.` are not supported. All JS
94
+ entrypoints are explicit subpaths (`/behaviors`, `/classes`, `/tokens`,
95
+ `/glyphs`, `/react`, `/solid`, `/qwik`, `/skins`, `/charts`). This is a
96
+ permanent, intentional contract.
97
+
98
+ ## Repository layout
99
+
100
+ The repo root mixes five kinds of directory that look alike but follow very
101
+ different rules. Two distinctions matter most: several are **path-frozen
102
+ published subpaths** — the directory name _is_ the public import specifier
103
+ (`@ponchia/ui/react` resolves to `./react/`), so they cannot be moved or
104
+ renamed — and several are **generated** and must never be hand-edited (a
105
+ generator overwrites them and a drift gate fails CI).
106
+
107
+ | Path | Kind | Edit here? | Notes |
108
+ | --- | --- | --- | --- |
109
+ | `css/` | source | yes | The framework. Hand-authored `@layer bronto` CSS. (`css/tokens.css` palette blocks and `css/generated.css` are generated — see below.) |
110
+ | `tokens/index.js` | source | yes | The single source of truth for token **values** (`cssVars`). |
111
+ | `classes/index.js`, `behaviors/`, `annotations/`, `connectors/`, `react/`, `solid/`, `qwik/`, `glyphs/`, `shiki/` | source · published-subpath (path-frozen) | yes — but **do not move** | Authored ESM shipped as-is; the dir name is the public import path. The `.d.ts` beside them are generated/drift-checked: `connectors`/`annotations`/`react`/`solid`/`qwik`/`behaviors` are emitted from JSDoc by `tsc` (`npm run dts:emit`), `classes`/`tokens`/`glyphs` from the runtime. No leaf `.d.ts` is hand-maintained. |
112
+ | `dist/` | generated | no | Build of `css/` (`npm run dist:build`); byte-checked by `check:dist`. |
113
+ | `tokens/index.json`, `tokens/resolved.json`, `tokens/tokens.dtcg.json`, `tokens/charts.json`, `classes/index.d.ts`, `tokens/index.d.ts`, `tokens/{skins,charts}.d.ts`, `glyphs/glyphs.d.ts`, `classes/vscode.css-custom-data.json`, `docs/reference.md` | generated | no | Committed build artifacts; regenerate with `npm run prepack`, never hand-edit. Drift-checked in `npm run check`. |
114
+ | `fonts/` | vendored | — | The Doto webfont (woff2) + its OFL license. |
115
+ | `scripts/` | tooling | yes | `gen-*` regenerate artifacts, `check-*` are the drift/contract gates wired into `npm run check`, plus `build-dist`, `serve`, `size-report`. |
116
+ | `docs/` | source (mostly) | yes | Hand-authored docs + ADRs; the curated subset in `package.json` `files` ships in the tarball. `docs/reference.md` is generated. |
117
+ | `demo/`, `test/`, `examples/` | fixtures | yes | The self-driving demo/showcase, the unit + Playwright e2e suite, and consumer example apps built against the packed tarball. |
118
+ | `.github/`, `*.config.mjs`, `.prettierrc`, `.stylelintrc.json`, `tsconfig.json`, `.editorconfig` | config | yes | CI workflows and tool config. |
119
+ | `package.json`, `llms.txt`, `CHANGELOG.md`, `MIGRATIONS.json`, `README.md`, `CONTRIBUTING.md`, `ROADMAP.md`, `LICENSE` | meta | yes | Manifest, the agent entrypoint, the curated changelog, the rename map, and project docs. |
120
+
121
+ The **path-frozen** dirs are the cost of zero-build, path-stable publishing:
122
+ `files` map 1:1 to published paths and the consumer's own bundler tree-shakes
123
+ the ESM, so there is no `src/` indirection (and no JS bundler — see the
124
+ distribution decision below). **Generated** files are regenerated from their
125
+ source and policed by a drift gate — edit the source, run the generator, commit
126
+ the result.
82
127
 
83
128
  ## Drift control
84
129
 
@@ -89,12 +134,10 @@ gating" below), so a version that fails any invariant never reaches npm.
89
134
  | Invariant | Enforced by |
90
135
  | ----------------------------------------------- | ------------------- |
91
136
  | exports / import graph / `files` consistent | `check-exports.mjs` |
92
- | `tokens.css` `tokens/index.js` `.json` | `check-tokens.mjs` |
137
+ | pure generated mirrors fresh — `tokens.css`/`index.json`, `dtcg.json`, `resolved.json`, `classes`/`tokens` `.d.ts`, `reference.md`, vscode data — each byte-equal to its generator (registry: `scripts/lib/artifacts.mjs`) | `check-fresh.mjs` |
93
138
  | `classes` `cls` ⇄ `.ui-*` selectors | `check-classes.mjs` |
94
- | `classes`/`tokens` `.d.ts` JS runtime (exact) | `check-dts.mjs` |
95
- | `annotations`/`connectors` hand-written `.d.ts` ⇄ exports | `check-helpers-dts.mjs` |
139
+ | `connectors`/`annotations`/`react`/`solid`/`qwik`/`behaviors` `.d.ts` (+ maps) == fresh `tsc` emit of their JSDoc | `check-dts-emit.mjs` |
96
140
  | legend swatch colours ⊆ `charts.js` · opt-in | `check-legend.mjs` |
97
- | `tokens.dtcg.json` ⇄ token model | `check-dtcg.mjs` |
98
141
  | color tokens tiered · no raw chromatic color in components | `check-color-policy.mjs` |
99
142
  | `css/skins.css` ⇄ `tokens/skins.js` · colorways opt-in | `check-skins.mjs` |
100
143
  | every shipped colorway accent meets its WCAG floor | `check-contrast.mjs` |
@@ -116,7 +159,7 @@ payload contract, raised only intentionally with a CHANGELOG note.
116
159
  `test/types.test-d.ts`, whose `@ts-expect-error`s would fail to compile
117
160
  if the generated literal `cls`/token types stopped rejecting typos —
118
161
  so the *value* of the generated `.d.ts` is itself gated, not just their
119
- freshness (`check-dts`).
162
+ freshness (`check-fresh`).
120
163
 
121
164
  ## Release gating
122
165
 
package/docs/bullet.md ADDED
@@ -0,0 +1,78 @@
1
+ # Bullet
2
+
3
+ `@ponchia/ui/css/bullet.css` is an opt-in **bullet graph** — Stephen Few's
4
+ compact "is this measure inside its qualitative budget, and how does it compare
5
+ to target?" figure. A thin measure bar sits over 2–3 grayscale qualitative range
6
+ bands, with a perpendicular target tick. It is the canonical SLO / error-budget
7
+ figure that `ui-meter` (a single label|bar|value reading) structurally cannot
8
+ encode — a bullet carries the bands and the target too. Few designed it grayscale
9
+ on purpose, which is exactly the Nothing palette.
10
+
11
+ ```css
12
+ @import '@ponchia/ui';
13
+ @import '@ponchia/ui/css/bullet.css';
14
+ ```
15
+
16
+ ## How it behaves
17
+
18
+ - **Bands** are a grayscale gradient with hard stops at `--band-lo` and
19
+ `--band-hi` — the qualitative ranges (e.g. poor / ok / good).
20
+ - **The measure** is a thin dark bar whose length is the normalised reading `--v`.
21
+ - **The target** is a full-height tick at `--t`; shape (not colour) distinguishes
22
+ it from the measure, per Few's grayscale rule.
23
+
24
+ ## Wiring — the host normalises every value to 0..1
25
+
26
+ Identical contract to `ui-spark`: Bronto paints geometry, the host does the
27
+ arithmetic. Set the normalised values as custom properties; the figure refuses
28
+ raw values and scale computation.
29
+
30
+ ```html
31
+ <figure>
32
+ <div
33
+ class="ui-bullet"
34
+ style="--band-lo: 0.6; --band-hi: 0.85"
35
+ role="img"
36
+ aria-label="uptime 99.62%, below the 99.9% target, in the warning band"
37
+ >
38
+ <div class="ui-bullet__measure ui-bullet__measure--neg" style="--v: 0.62"></div>
39
+ <div class="ui-bullet__target" style="--t: 0.9"></div>
40
+ </div>
41
+ <figcaption class="ui-bullet__label"><span>Uptime</span><span>99.62%</span></figcaption>
42
+ </figure>
43
+ ```
44
+
45
+ The `.ui-bullet__label` row is optional — use it, a `<figcaption>`, or your own
46
+ caption. Either way the **reading lives in text**, never in colour alone.
47
+
48
+ ## Class reference
49
+
50
+ | Class | Role |
51
+ | ---------------------------- | ------------------------------------------------------------ |
52
+ | `.ui-bullet` | The track + qualitative range bands (`--b1` / `--b2`). |
53
+ | `.ui-bullet__measure` | The measure bar; length is `--v` (0..1). |
54
+ | `.ui-bullet__measure--accent`| Paint the measure in the rationed accent (emphasis). |
55
+ | `.ui-bullet__measure--pos` | Paint the measure in the success tone. |
56
+ | `.ui-bullet__measure--neg` | Paint the measure in the danger tone. |
57
+ | `.ui-bullet__target` | The full-height target tick at `--t` (0..1). |
58
+ | `.ui-bullet__label` | Optional caption row (label + reading, in mono). |
59
+
60
+ | Custom property | On | Meaning |
61
+ | --------------- | --------------------- | -------------------------------------------------------- |
62
+ | `--v` | `.ui-bullet__measure` | **Required.** Normalised measure (0..1). |
63
+ | `--t` | `.ui-bullet__target` | Normalised target position (0..1); omit to drop the tick.|
64
+ | `--band-lo` | `.ui-bullet` | Lower band boundary (0..1, default `0.5`). |
65
+ | `--band-hi` | `.ui-bullet` | Upper band boundary (0..1, default `0.8`, ≥ `--band-lo`).|
66
+
67
+ ## Accessibility & robustness
68
+
69
+ - A bare bullet is **opaque to assistive tech**, so `.ui-bullet` MUST carry a
70
+ host-written `role="img"` + `aria-label` that states the reading, the target,
71
+ and the band — colour is never the only channel (WCAG 1.4.1).
72
+ - Under `forced-colors` the band gradient flattens, so the track gains a border
73
+ and the marks repaint in the system text colour; the band meaning is carried by
74
+ the required label.
75
+ - Bands + marks are `print-color-adjust: exact`, so the figure survives the print
76
+ economy.
77
+ - Relationship to `ui-meter` (report.css): a meter is a labelled progress reading;
78
+ reach for a bullet when you also need a target and qualitative bands.
package/docs/code.md ADDED
@@ -0,0 +1,76 @@
1
+ # Code
2
+
3
+ `@ponchia/ui/css/code.css` is an opt-in chrome for **fenced code** — the surface
4
+ for code-as-evidence in changelogs, version history, config snippets, and
5
+ generated reports. It paints the frame, an optional line-number gutter, and
6
+ add / remove / highlight line states. Its sibling is [diff.css](diff.md)
7
+ (multi-column line/row diffs); the two share one change vocabulary —
8
+ `--add` / `--remove` (`ui-code__line--remove` ↔ `ui-diff__row--remove`).
9
+
10
+ ```css
11
+ @import '@ponchia/ui';
12
+ @import '@ponchia/ui/css/code.css';
13
+ ```
14
+
15
+ ## Boundary — it never parses
16
+
17
+ Bronto owns the chrome; it does **not** tokenize or highlight syntax. The host
18
+ supplies the coloured token spans — hand-written, or from a real highlighter.
19
+ The shipped [`shiki/nothing.json`](../shiki/nothing.json) theme makes Shiki emit
20
+ spans with bronto token colours, so Shiki output drops straight in. A bronto
21
+ class that tried to parse source would cross the same line that removed the
22
+ chart renderer in 0.6.0.
23
+
24
+ ## Markup
25
+
26
+ ```html
27
+ <figure class="ui-code ui-code--numbered">
28
+ <figcaption class="ui-code__head">theme.css</figcaption>
29
+ <pre class="ui-code__body"><code><span class="ui-code__line">.ui-diff {</span>
30
+ <span class="ui-code__line ui-code__line--remove"> font-size: var(--text-sm);</span>
31
+ <span class="ui-code__line ui-code__line--add"> font-size: var(--text-xs);</span>
32
+ <span class="ui-code__line ui-code__line--hl"> line-height: 1.6;</span>
33
+ <span class="ui-code__line">}</span></code></pre>
34
+ </figure>
35
+ ```
36
+
37
+ - `ui-code--numbered` turns on the gutter; it numbers each `.ui-code__line` with
38
+ a CSS counter (no host bookkeeping).
39
+ - Each token span the host emits lives **inside** `.ui-code__line`; Bronto never
40
+ touches it.
41
+ - Plain text inside `.ui-code__body` (no `.ui-code__line` wrappers) renders as an
42
+ unnumbered code block — the line grammar is opt-in.
43
+
44
+ ## Class reference
45
+
46
+ | Class | Role |
47
+ | --- | --- |
48
+ | `.ui-code` | The `<figure>` frame. |
49
+ | `.ui-code--numbered` | Show the line-number gutter. |
50
+ | `.ui-code__head` | A filename / language bar (use on `<figcaption>`). |
51
+ | `.ui-code__body` | The `<pre>` scroll/wrap region. |
52
+ | `.ui-code__line` | One line — the unit numbered + state-tinted. |
53
+ | `.ui-code__line--add` | Added line (green wash). |
54
+ | `.ui-code__line--remove` | Removed line (red wash). |
55
+ | `.ui-code__line--hl` | Neutral highlight / call-out (accent wash, not a change). |
56
+
57
+ ## Recipes
58
+
59
+ ```js
60
+ import { ui } from '@ponchia/ui/classes';
61
+
62
+ ui.code({ numbered: true }); // "ui-code ui-code--numbered"
63
+ ui.codeLine({ change: 'add' }); // "ui-code__line ui-code__line--add"
64
+ ui.codeLine({ change: 'hl' }); // "ui-code__line ui-code__line--hl"
65
+ ui.codeLine(); // "ui-code__line"
66
+ ```
67
+
68
+ ## Accessibility & robustness
69
+
70
+ - **Line states never rely on colour.** In forced-colors mode each
71
+ add/remove/hl line gains an inline-start border, and the tints are forced through
72
+ `print-color-adjust: exact`.
73
+ - **Long lines wrap** rather than scroll, so the block prints cleanly.
74
+ - **Line numbers** are generated content (`user-select: none`), so copying the
75
+ block yields clean code with no line numbers.
76
+ - Keep real text in the `<code>`; don't encode meaning only in a tint.