semiotic 3.8.2 → 3.8.4

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 (195) hide show
  1. package/CLAUDE.md +9 -9
  2. package/README.md +42 -40
  3. package/ai/examples.md +1 -1
  4. package/ai/schema.json +2726 -300
  5. package/ai/surface-manifest.json +1 -1
  6. package/ai/system-prompt.md +1 -1
  7. package/dist/chunk-chunk-2JGJ5GUG.module.min.js +0 -1
  8. package/dist/chunk-chunk-3ILKBVVQ.module.min.js +1 -0
  9. package/dist/{chunk-chunk-NO4MCQVD.module.min.js → chunk-chunk-5RKXZQ2P.module.min.js} +1 -1
  10. package/dist/{chunk-chunk-CYIML3AV.module.min.js → chunk-chunk-6TPGFL7M.module.min.js} +1 -1
  11. package/dist/{chunk-chunk-VVCA3DB7.module.min.js → chunk-chunk-7ALTGHK6.module.min.js} +1 -1
  12. package/dist/chunk-chunk-7KZJUTKP.module.min.js +2 -0
  13. package/dist/chunk-chunk-7RYBCTK5.module.min.js +1 -0
  14. package/dist/chunk-chunk-ACLDBMRH.module.min.js +2 -0
  15. package/dist/chunk-chunk-ANYDWOZF.module.min.js +1 -0
  16. package/dist/{chunk-chunk-ZLKRTF5N.module.min.js → chunk-chunk-AQPQN2HH.module.min.js} +1 -1
  17. package/dist/chunk-chunk-ASKSE5F5.module.min.js +1 -0
  18. package/dist/chunk-chunk-AVBI6GDT.module.min.js +1 -0
  19. package/dist/chunk-chunk-B3DRZPBK.module.min.js +2 -0
  20. package/dist/chunk-chunk-BFHUYEOS.module.min.js +2 -0
  21. package/dist/chunk-chunk-C4YA6TDR.module.min.js +1 -0
  22. package/dist/chunk-chunk-C57LR3YG.module.min.js +1 -0
  23. package/dist/chunk-chunk-EMO5NUGE.module.min.js +1 -0
  24. package/dist/chunk-chunk-F5V4UIE6.module.min.js +2 -0
  25. package/dist/chunk-chunk-F7WFPD4O.module.min.js +2 -0
  26. package/dist/chunk-chunk-FBGHXL72.module.min.js +2 -0
  27. package/dist/chunk-chunk-FP44FHFO.module.min.js +2 -0
  28. package/dist/chunk-chunk-HKXUAEPJ.module.min.js +2 -0
  29. package/dist/chunk-chunk-ICEXPNDC.module.min.js +2 -0
  30. package/dist/chunk-chunk-IY7RYD4S.module.min.js +1 -0
  31. package/dist/{chunk-chunk-7LHMKRKE.module.min.js → chunk-chunk-JDXT6ORT.module.min.js} +1 -1
  32. package/dist/chunk-chunk-JMVEIZXP.module.min.js +1 -0
  33. package/dist/chunk-chunk-KHCOJMGK.module.min.js +1 -0
  34. package/dist/chunk-chunk-KQ546MKE.module.min.js +2 -0
  35. package/dist/chunk-chunk-LQBSXO7K.module.min.js +2 -0
  36. package/dist/chunk-chunk-LRIFN2HK.module.min.js +1 -0
  37. package/dist/chunk-chunk-MFHF4J2Z.module.min.js +1 -0
  38. package/dist/chunk-chunk-N6VKKF3A.module.min.js +2 -0
  39. package/dist/chunk-chunk-NEVE562N.module.min.js +1 -0
  40. package/dist/chunk-chunk-NKZ75IM5.module.min.js +2 -0
  41. package/dist/chunk-chunk-PLIDPQNG.module.min.js +2 -0
  42. package/dist/{chunk-chunk-FYY2ACNJ.module.min.js → chunk-chunk-PRDGLMJT.module.min.js} +1 -1
  43. package/dist/chunk-chunk-QGZGZI4X.module.min.js +1 -0
  44. package/dist/chunk-chunk-RP7WTID5.module.min.js +1 -0
  45. package/dist/chunk-chunk-S7KLNUO6.module.min.js +1 -0
  46. package/dist/chunk-chunk-SR4ZXOWR.module.min.js +2 -0
  47. package/dist/chunk-chunk-ZOAQDIJO.module.min.js +1 -0
  48. package/dist/chunk-minimapBrushOverlay-JU57U7NM.module.min.js +2 -0
  49. package/dist/chunk-scatterplotMatrixBrushOverlay-SDIZSN66.module.min.js +2 -0
  50. package/dist/chunk-statisticalOverlays-QGDLRUHX.module.min.js +1 -0
  51. package/dist/chunk-statisticalOverlays-T77OKLDY.module.min.js +1 -0
  52. package/dist/components/ChartContainer.d.ts +2 -2
  53. package/dist/components/ai/chartCapabilityTypes.d.ts +5 -0
  54. package/dist/components/charts/custom/XYCustomChart.d.ts +3 -0
  55. package/dist/components/charts/geo/ChoroplethMap.d.ts +2 -0
  56. package/dist/components/charts/geo/DistanceCartogram.d.ts +7 -3
  57. package/dist/components/charts/geo/FlowMap.d.ts +4 -0
  58. package/dist/components/charts/geo/ProportionalSymbolMap.d.ts +3 -1
  59. package/dist/components/charts/physics/CollisionSwarmChart.d.ts +6 -1
  60. package/dist/components/charts/physics/GaltonBoardChart.d.ts +6 -1
  61. package/dist/components/charts/physics/gauntletChartProps.d.ts +7 -1
  62. package/dist/components/charts/physics/gauntletChrome.d.ts +2 -1
  63. package/dist/components/charts/physics/gauntletPhysics.d.ts +19 -4
  64. package/dist/components/charts/physics/gauntletRuntime.d.ts +3 -0
  65. package/dist/components/charts/physics/physicsHocUtils.d.ts +12 -0
  66. package/dist/components/charts/realtime/RealtimeHistogram.d.ts +5 -6
  67. package/dist/components/charts/realtime/temporalHistogramConfig.d.ts +11 -0
  68. package/dist/components/charts/shared/areaSeriesData.d.ts +15 -0
  69. package/dist/components/charts/shared/chartMode.d.ts +75 -0
  70. package/dist/components/charts/shared/chartSpecCore.d.ts +2 -2
  71. package/dist/components/charts/shared/colorUtils.d.ts +8 -0
  72. package/dist/components/charts/shared/geoStyleDefaults.d.ts +3 -0
  73. package/dist/components/charts/shared/hatchPattern.d.ts +8 -3
  74. package/dist/components/charts/shared/hooks.d.ts +13 -80
  75. package/dist/components/charts/shared/lineSeriesSsr.d.ts +55 -0
  76. package/dist/components/charts/shared/types.d.ts +2 -2
  77. package/dist/components/charts/shared/useChartSetup.d.ts +3 -0
  78. package/dist/components/charts/shared/useCustomChartSetup.d.ts +3 -0
  79. package/dist/components/charts/shared/useXYLineStyle.d.ts +3 -2
  80. package/dist/components/charts/shared/validateProps.d.ts +1 -1
  81. package/dist/components/charts/shared/xyLineStyle.d.ts +21 -0
  82. package/dist/components/charts/xy/AreaChart.d.ts +10 -0
  83. package/dist/components/charts/xy/LineChart.d.ts +3 -0
  84. package/dist/components/charts/xy/minimapBrushOverlay.d.ts +17 -0
  85. package/dist/components/charts/xy/minimapBrushOverlayLazy.d.ts +4 -0
  86. package/dist/components/charts/xy/scatterplotMatrixBrushOverlay.d.ts +8 -0
  87. package/dist/components/charts/xy/scatterplotMatrixBrushOverlayLazy.d.ts +3 -0
  88. package/dist/components/legendLayout.d.ts +31 -0
  89. package/dist/components/realtime/types.d.ts +5 -0
  90. package/dist/components/recipes/gridObservatory.d.ts +176 -0
  91. package/dist/components/recipes/recipeChrome.d.ts +24 -0
  92. package/dist/components/recipes/sequenceLayout.d.ts +139 -0
  93. package/dist/components/semiotic-recipes-core.d.ts +6 -2
  94. package/dist/components/server/serverChartConfigShared.d.ts +29 -0
  95. package/dist/components/server/serverChartConfigs.d.ts +2 -13
  96. package/dist/components/server/serverChartConfigsXY.d.ts +3 -0
  97. package/dist/components/server/serverGaugeOverlay.d.ts +28 -0
  98. package/dist/components/server/staticAnnotations.d.ts +3 -1
  99. package/dist/components/server/staticLegend.d.ts +7 -1
  100. package/dist/components/server/staticSVGChrome.d.ts +57 -0
  101. package/dist/components/stream/GeoPipelineStore.d.ts +2 -0
  102. package/dist/components/stream/GeoSVGOverlay.d.ts +2 -0
  103. package/dist/components/stream/SceneToSVG.d.ts +10 -8
  104. package/dist/components/stream/SceneToSVGGeo.d.ts +10 -0
  105. package/dist/components/stream/SceneToSVGNetwork.d.ts +12 -0
  106. package/dist/components/stream/SceneToSVGOrdinal.d.ts +12 -0
  107. package/dist/components/stream/canvasBackground.d.ts +7 -0
  108. package/dist/components/stream/networkFramePaint.d.ts +2 -0
  109. package/dist/components/stream/networkTypes.d.ts +2 -4
  110. package/dist/components/stream/physics/PhysicsPipelineStore.d.ts +18 -2
  111. package/dist/components/stream/physics/physicsBodyCanvas.d.ts +7 -0
  112. package/dist/components/stream/physics/physicsPipelineEviction.d.ts +15 -0
  113. package/dist/components/stream/physics/physicsPipelineObservations.d.ts +1 -1
  114. package/dist/components/stream/physics/physicsPipelineQuiescence.d.ts +20 -0
  115. package/dist/components/stream/renderBackend.d.ts +14 -0
  116. package/dist/components/stream/sceneToSVGShared.d.ts +80 -0
  117. package/dist/components/stream/types.d.ts +4 -0
  118. package/dist/components/stream/useHydration.d.ts +7 -1
  119. package/dist/components/stream/xySceneBuilders/candlestickScene.d.ts +1 -1
  120. package/dist/components/types/legendTypes.d.ts +16 -3
  121. package/dist/forceLayoutWorker.js +1 -1
  122. package/dist/geo.min.js +1 -1
  123. package/dist/geo.module.min.js +1 -1
  124. package/dist/network.min.js +1 -1
  125. package/dist/network.module.min.js +1 -1
  126. package/dist/ordinal.min.js +1 -1
  127. package/dist/ordinal.module.min.js +1 -1
  128. package/dist/physics.min.js +1 -1
  129. package/dist/physics.module.min.js +1 -1
  130. package/dist/physicsWorker.js +1 -1
  131. package/dist/realtime.min.js +1 -1
  132. package/dist/realtime.module.min.js +1 -1
  133. package/dist/rough.min.js +1 -1
  134. package/dist/rough.module.min.js +1 -1
  135. package/dist/semiotic-ai-core.min.js +1 -1
  136. package/dist/semiotic-ai-core.module.min.js +1 -1
  137. package/dist/semiotic-ai.min.js +1 -1
  138. package/dist/semiotic-ai.module.min.js +1 -1
  139. package/dist/semiotic-experimental.min.js +1 -1
  140. package/dist/semiotic-experimental.module.min.js +1 -1
  141. package/dist/semiotic-realtime-core.min.js +1 -1
  142. package/dist/semiotic-realtime-core.module.min.js +1 -1
  143. package/dist/semiotic-recipes-core.d.ts +6 -2
  144. package/dist/semiotic-recipes-core.min.js +1 -1
  145. package/dist/semiotic-recipes-core.module.min.js +1 -1
  146. package/dist/semiotic-recipes.min.js +1 -1
  147. package/dist/semiotic-recipes.module.min.js +1 -1
  148. package/dist/semiotic-server-edge.min.js +1 -1
  149. package/dist/semiotic-server-edge.module.min.js +1 -1
  150. package/dist/semiotic-server-node.min.js +1 -1
  151. package/dist/semiotic-server-node.module.min.js +1 -1
  152. package/dist/semiotic-utils-core.min.js +1 -1
  153. package/dist/semiotic-utils-core.module.min.js +1 -1
  154. package/dist/semiotic-utils.min.js +1 -1
  155. package/dist/semiotic-utils.module.min.js +1 -1
  156. package/dist/semiotic-value.module.min.js +1 -1
  157. package/dist/semiotic.min.js +1 -1
  158. package/dist/semiotic.module.min.js +1 -1
  159. package/dist/server.min.js +1 -1
  160. package/dist/server.module.min.js +1 -1
  161. package/dist/xy.min.js +1 -1
  162. package/dist/xy.module.min.js +1 -1
  163. package/package.json +3 -2
  164. package/dist/chunk-chunk-255KDHW5.module.min.js +0 -1
  165. package/dist/chunk-chunk-27HVVXS6.module.min.js +0 -1
  166. package/dist/chunk-chunk-2UN3ANRN.module.min.js +0 -2
  167. package/dist/chunk-chunk-2VUEY5TH.module.min.js +0 -1
  168. package/dist/chunk-chunk-2WVCH4PL.module.min.js +0 -1
  169. package/dist/chunk-chunk-5WEFWNUW.module.min.js +0 -2
  170. package/dist/chunk-chunk-6F6GEAQU.module.min.js +0 -1
  171. package/dist/chunk-chunk-BHYTYMY2.module.min.js +0 -2
  172. package/dist/chunk-chunk-C6EJ4IOD.module.min.js +0 -2
  173. package/dist/chunk-chunk-CP3EN6KZ.module.min.js +0 -1
  174. package/dist/chunk-chunk-DNY43POY.module.min.js +0 -1
  175. package/dist/chunk-chunk-E7MIQX7D.module.min.js +0 -2
  176. package/dist/chunk-chunk-ES4DCG3L.module.min.js +0 -1
  177. package/dist/chunk-chunk-FAKO3RGQ.module.min.js +0 -2
  178. package/dist/chunk-chunk-H4PPRW6N.module.min.js +0 -1
  179. package/dist/chunk-chunk-IFYPMWKY.module.min.js +0 -1
  180. package/dist/chunk-chunk-J3ELI22J.module.min.js +0 -1
  181. package/dist/chunk-chunk-L6D3I3V7.module.min.js +0 -2
  182. package/dist/chunk-chunk-M337NY6L.module.min.js +0 -1
  183. package/dist/chunk-chunk-ODUZEEBH.module.min.js +0 -2
  184. package/dist/chunk-chunk-P6UBCMSQ.module.min.js +0 -2
  185. package/dist/chunk-chunk-QRIBHZYW.module.min.js +0 -1
  186. package/dist/chunk-chunk-RH5TSQPH.module.min.js +0 -1
  187. package/dist/chunk-chunk-STZFFTAN.module.min.js +0 -2
  188. package/dist/chunk-chunk-T27ZEV6A.module.min.js +0 -1
  189. package/dist/chunk-chunk-U3AGQ5EF.module.min.js +0 -2
  190. package/dist/chunk-chunk-W3DB2MN4.module.min.js +0 -2
  191. package/dist/chunk-chunk-WDIN4LRC.module.min.js +0 -1
  192. package/dist/chunk-chunk-XOYX77KF.module.min.js +0 -2
  193. package/dist/chunk-chunk-Z5KXAVQD.module.min.js +0 -2
  194. package/dist/chunk-chunk-ZCNBUZPL.module.min.js +0 -2
  195. package/dist/chunk-chunk-ZTNVDWAK.module.min.js +0 -2
package/CLAUDE.md CHANGED
@@ -4,7 +4,7 @@
4
4
  - Install: `npm install semiotic`
5
5
  <!-- semiotic-bundle-sizes:start -->
6
6
  <!-- Auto-generated by scripts/sync-bundle-sizes.mjs — do not edit by hand. -->
7
- - **Use sub-path imports** — `semiotic/xy` (134KB gz), `semiotic/ordinal` (115KB gz), `semiotic/network` (121KB gz), `semiotic/geo` (94KB gz), `semiotic/realtime` (147KB gz), `semiotic/realtime/core` (140KB gz), `semiotic/realtime/react` (1KB gz), `semiotic/server` (125KB gz), `semiotic/server/node` (125KB gz), `semiotic/server/edge` (143KB gz), `semiotic/utils` (75KB gz), `semiotic/utils/core` (73KB gz), `semiotic/utils/react` (6KB gz), `semiotic/recipes` (74KB gz), `semiotic/recipes/core` (74KB gz), `semiotic/recipes/react` (2KB gz), `semiotic/themes` (7KB gz), `semiotic/themes/core` (7KB gz), `semiotic/themes/react` (6KB gz), `semiotic/data` (4KB gz), `semiotic/value` (8KB gz), `semiotic/physics` (132KB gz), `semiotic/physics/matter` (1KB gz), `semiotic/physics/rapier` (1KB gz), `semiotic/ai` (429KB gz), `semiotic/ai/core` (68KB gz), `semiotic/controls` (12KB gz), `semiotic/rough` (3KB gz). Full `semiotic` is 346KB gz.
7
+ - **Use sub-path imports** — `semiotic/xy` (138KB gz), `semiotic/ordinal` (117KB gz), `semiotic/network` (123KB gz), `semiotic/geo` (97KB gz), `semiotic/realtime` (150KB gz), `semiotic/realtime/core` (145KB gz), `semiotic/realtime/react` (1KB gz), `semiotic/server` (190KB gz), `semiotic/server/node` (190KB gz), `semiotic/server/edge` (208KB gz), `semiotic/utils` (76KB gz), `semiotic/utils/core` (74KB gz), `semiotic/utils/react` (6KB gz), `semiotic/recipes` (77KB gz), `semiotic/recipes/core` (77KB gz), `semiotic/recipes/react` (2KB gz), `semiotic/themes` (7KB gz), `semiotic/themes/core` (7KB gz), `semiotic/themes/react` (6KB gz), `semiotic/data` (4KB gz), `semiotic/value` (8KB gz), `semiotic/physics` (133KB gz), `semiotic/physics/matter` (1KB gz), `semiotic/physics/rapier` (1KB gz), `semiotic/ai` (432KB gz), `semiotic/ai/core` (70KB gz), `semiotic/controls` (12KB gz), `semiotic/rough` (4KB gz). Full `semiotic` is 349KB gz.
8
8
  <!-- semiotic-bundle-sizes:end -->
9
9
  - CLI: `npx semiotic-ai [--schema|--compact|--examples|--doctor|--audit-a11y]` · MCP: `npx semiotic-mcp`
10
10
 
@@ -153,27 +153,27 @@ When the catalog doesn't fit, four HOCs take a layout function emitting scene pr
153
153
  Layout signature differs by family:
154
154
  - **XY/Ordinal**: `layout: (ctx) => { nodes, overlays? }`. `ctx`: `data`, `scales` ({x,y} XY | {o,r,projection} ordinal), `dimensions` (plot rect — center-anchored for radial ordinal, top-left otherwise), `theme`, `resolveColor(key)`, `config`.
155
155
  - **Network**: `layout: (ctx) => { sceneNodes?, sceneEdges?, labels?, overlays?, htmlMarks? }`. `ctx`: `nodes`, `edges`, `dimensions`, `theme`, `resolveColor(key)`, `config`, `selection` (shared-selection predicate `{ isActive, predicate(datum) }` from `LinkedCharts`, `null` when unwired — dim/highlight by it). Run external positioners (`d3-flextree`, `dagre`) then emit network scene primitives (circle/rect/arc/**symbol**/**glyph** nodes; line/bezier/curved edges). The `symbol` node is the per-datum **shape** channel — a `d3-shape` glyph (`circle`/`square`/`triangle`/`diamond`/`star`/`cross`/`wye`/`chevron`, or a custom `path`) sized by `size` (area), rendered on canvas + SVG/SSR and hit-tested + keyboard-navigated as a unit. **The `symbol` mark is cross-pipeline**: XY and ordinal custom layouts emit it too (`{type:"symbol", x, y, size, symbolType}` — note `x`/`y`, vs the network variant's `cx`/`cy`), and Scatterplot/SwarmPlot expose it as the `symbolBy` encoding. One shared `symbolPath` implementation backs canvas/SVG/hit-test across all three families.
156
- - **The `glyph` node — composite pictograms (ALL FOUR families incl. geo)**: where `symbol` is one path, `glyph` stamps a multi-part vector pictogram — a `GlyphDef` (`{viewBox?, anchor?, parts: [{d, fill?, stroke?, strokeWidth?, opacity?}]}`) whose parts declare **role paints** (`"color"`/`"accent"`/literal) resolved per node (`color`/`accent` props), so one definition recolors per category like a pictogram plate reused in many inks. `{type:"glyph", x, y, size, glyph, color?, accent?, fraction?, fractionStart?, fractionDirection?, ghostColor?, rotation?, style, datum, pointId?}` (network variant uses `cx`/`cy` + `id`/`label`). `size` = rendered **height** px (width follows viewBox aspect); `anchor: [0.5, 1]` stands a sign's feet on a baseline/terrain. **Partial fills**: `fraction`/`fractionStart` clip a `[start, end]` window (horizontal or bottom-up vertical) with an optional full-extent `ghostColor` silhouette — the ISOTYPE partial-symbol convention, fed directly by `unitize`. Full pipeline citizen: canvas + SVG/SSR, hit-test + keyboard nav over the drawn bounds, `pointId`/`id` annotation anchoring, and enter/move/exit transition identity (which `symbol` lacks). Datum-less glyph nodes (`datum: null`) paint but don't hit-test/navigate — use one `hitTarget` per logical mark under a multi-sign tally. `<Glyph def size color …/>` (from `semiotic/recipes`) renders the same definition as React SVG for overlays/legends/chrome; `glyphPlacement`/`glyphExtent` expose its geometry for layout math.
156
+ - **The `glyph` node — composite pictograms (ALL FOUR families incl. geo)**: where `symbol` is one path, `glyph` stamps a multi-part vector pictogram — a `GlyphDef` (`{viewBox?, anchor?, parts: [{d, fill?, stroke?, strokeWidth?, opacity?}]}`) whose parts declare **role paints** (`"color"`/`"accent"`/literal) resolved per node (`color`/`accent` props), so one definition recolors per category like a pictogram plate reused in many inks. `{type:"glyph", x, y, size, glyph, color?, accent?, fraction?, fractionStart?, fractionDirection?, ghostColor?, rotation?, style, datum, pointId?}` (network variant uses `cx`/`cy` + `id`/`label`). `size` = rendered **height** px (width follows viewBox aspect); `anchor: [0.5, 1]` stands a sign's feet on a baseline/terrain. **Partial fills**: `fraction`/`fractionStart` clip a `[start, end]` window (horizontal or bottom-up vertical) with an optional full-extent `ghostColor` silhouette — the ISOTYPE partial-symbol convention, fed directly by `unitize`. Full pipeline citizen: canvas + SVG/SSR, hit-test + keyboard nav over the drawn bounds, `pointId`/`id` annotation anchoring, and enter/move/exit transition identity (which `symbol` lacks). Datum-less glyph nodes (`datum: null`) paint but don't hit-test/navigate — use one `hitTarget` per logical mark under a multi-sign tally. `<Glyph def size color …/>` (from `semiotic/recipes`) renders the same definition as React SVG for overlays/legends/decoration; `glyphPlacement`/`glyphExtent` expose its geometry for layout math.
157
157
  - **Geo**: `layout: (ctx) => { nodes?, overlays? }`. `ctx`: `areas`, `points`, `lines`, fitted `GeoScales`, `dimensions`, `theme`, `resolveColor(key)`, `config`, `selection`. Emit `geoarea`/`point`/`line`/`glyph` nodes; use overlays for labels and sprites.
158
158
  - **`htmlMarks`** (network only): `NetworkHtmlMark[]` = `{ id, x, y, width, height, content: ReactNode }`, positioned in the **same plot space as `sceneNodes`** and rendered into one real-DOM layer the framework places **above the canvas and SVG `overlays`** (stack: canvas → `overlays` → `htmlMarks`). Reach for it over an SVG `<foreignObject>` when a mark is **text-heavy/rich and dims or animates on hover** — a real `<div>` composites `opacity`/`transform`/`visibility` changes instead of re-rasterizing text (the `foreignObject` stall on large graphs). Framework owns the margin (and future zoom/pan) transform so marks stay pixel-aligned; each mark is its own element, keyed by `id` (position-only re-runs reposition without remounting). `pointer-events: none` by default — keep a transparent hit-rect `sceneNode` per mark so canvas hit-testing/`onObservation` stays authoritative. Mark `content` can read `useCustomLayoutSelection()` to dim on shared selection without a relayout. Additive: omit it and no extra DOM renders. Class hooks: `.semiotic-network-html-marks` (layer) / `.semiotic-network-html-mark` (each).
159
159
 
160
160
  **Custom-chart authoring kit** (`semiotic/recipes`; `hitTarget*` also from `semiotic/xy`/`ordinal`/`network`). The shape every hand-built custom chart converges on — draw real marks in `overlays`, emit a **transparent scene node per mark** for interaction — is first-class:
161
161
  - **`hitTargetPoint`/`hitTargetRect`** (XY/ordinal) + **`networkHitTarget`** (circle or rect) + **`geoHitTarget`** (geo — same transparent `PointSceneNode`; project lon/lat via `ctx.scales.projectedPoint` first): a zero-opacity, fully-transparent, hit-tested node from `{x, y, (r|width,height), datum, id}`. The `id` becomes the node's `pointId`/`id` (annotation anchor + nav-tree leaf) **and** its transition key. This is how a custom chart inherits **accessibility** (keyboard nav, focus ring, data table), **annotation** anchoring, **AI**/`onObservation` + shared selection, and **chart-mode** transitions for free — replaces the `rgba(0,0,0,0)`+`opacity:0`+`pointId`+`_transitionKey` boilerplate. The visible glyph lives in `overlays`; the focus ring still draws on the invisible target. A keyboard-focused **geoarea** outlines its polygon (a shape focus ring) rather than a centroid dot.
162
- - **Radial coordinate kit**: `polarToXY`/`xyToAngle` (0 = up, clockwise), `angleScale`/`radiusScale`, `ringArcPath` (annular-sector / wedge / full-ring path), `TAU` — angle ⟂ radius for two-continuous-channel radial charts (the radial analogue of the chrome kit).
162
+ - **Radial coordinate kit**: `polarToXY`/`xyToAngle` (0 = up, clockwise), `angleScale`/`radiusScale`, `ringArcPath` (annular-sector / wedge / full-ring path), `TAU` — angle ⟂ radius for two-continuous-channel radial charts (the radial analogue of the decoration kit).
163
163
  - **Edge-router kit** (custom network edges): `curvedEdgePath` (S-curve + near-level side-bow), `orthogonalEdgePath`, `boxEdgeAnchors` (box exit/entry by direction), `fanOutBend` (fan parallel edges apart). Plus cubic-Bézier evaluation — `cubicPoint`/`cubicTangent` (sample a point/tangent along a `CubicCurve` to *seat a mark on the curve* — a node mid-edge, an arrowhead at the end) and `cubicPath` (serialize to SVG).
164
164
  - **2D vector kit**: `addPoints`/`subtractPoints`/`scalePoint`/`pointMagnitude`/`normalizePoint` — the point math any hand-built radial or network layout re-derives (an edge offset normal to its tangent, a spoke, a leader line). Operates on the shared `Point`, composes with the radial + edge kits.
165
165
  - **Interval/timeline**: `packIntervals` (greedy Gantt sub-track packer), `activeCountOverDomain` (concurrency step series).
166
166
  - **`runs`/`runLengthEncode`**: collapse a per-step categorical/boolean series into drawable runs (condition strips, status timelines, calendar ribbons).
167
167
  - **Cyclical math** (day-of-year, hour, compass bearing): `wrapValue`, `shortestArcDelta`, `cyclicRangeContains`, `selectCyclicRange`.
168
168
  - **`axisFixedForcePositions`** (+ shared **`rectCollide`** positioner): pin one axis from a data field, relax the other with edge attraction + an anchor spring + **rectangular** (label-box) collision — the "time is structural, the graph settles the cross-axis" family that hierarchical recipes don't cover. `axisFixedForceLayout` wraps it as a ready `NetworkCustomLayout`.
169
- - **Chrome**: `linearAxis` (tick axis + gridlines from *any* scale — the bespoke-scale escape hatch `showAxes` can't cover), `legendSwatches` (portable SVG legend for `overlays` — fill/line/shape/hatch swatches; sibling to `legendGroupsFrom` which feeds `frameProps.legend`), `hatchFill` (`{def, fill}` SVG `<pattern>` for percentile/uncertainty bands — the SVG analogue of `createHatchPattern`).
169
+ - **Decoration**: `linearAxis` (tick axis + gridlines from *any* scale — the bespoke-scale escape hatch `showAxes` can't cover), `legendSwatches` (portable SVG legend for `overlays` — fill/line/shape/hatch swatches; sibling to `legendGroupsFrom` which feeds `frameProps.legend`), `hatchFill` (`{def, fill}` SVG `<pattern>` for percentile/uncertainty bands — the SVG analogue of `createHatchPattern`).
170
170
  - **`unwrapDatum`** (`semiotic/recipes` + `semiotic/utils`): collapse the wrapped-vs-raw datum split — always the raw user object (handles both `.data` wrappers and `.datum` nesting). **The** unwrap path for `onObservation` handlers AND `frameProps.tooltipContent` renderers: call it once on the incoming value; never pre-unwrap the argument (`unwrapDatum(x?.data ?? x)` double-unwraps).
171
171
 
172
- `semiotic/recipes` ships pure layout functions (`waffleLayout`, `calendarLayout`, `marimekkoLayout`, `bulletLayout`, `parallelCoordinatesLayout`, `intervalLanesLayout`, `flextreeLayout`, `dagreLayout`, `lineageDagLayout`, `axisFixedForceLayout`, `packedClusterMatrix`, `isometricLandmarkLayout`, `forceLayout`, `arcLayout`, `adjacencyMatrix`, `circularLayout`). **Network-analysis kit** (`semiotic/recipes`, pure graph algorithms): `buildAdjacency`, `bfsDistances`, `shortestPath`, `egoNetwork`, `degree`/`betweenness`(Brandes)/`closeness`/`clustering` (+ `normalizeScores`), and `proximityProblem` — the "spatial problem" layout diagnostic that flags nodes drawn closer than their graph distance warrants. Pair with network charts to size by centrality, highlight an ego network on hover, trace a shortest path, or diagnose a misleading layout. `forceLayout(nodes, edges, {seed})` is a **seeded, deterministic** positioner returning normalized `{id:{x,y}}` for `NetworkCustomChart` (same seed ⇒ same layout; re-seed for a "re-run the layout" interaction); `forceLayoutAsync(nodes, edges, {execution?, workerThreshold?, signal?})` is its Promise sibling that runs large layouts in a short-lived module Web Worker (identical deterministic output; graceful sync fallback), and `useForceLayout(nodes, edges, options)` → `{positions, status, error}` is the React wrapper — SSR and first hydration stay synchronous for markup parity, client graph changes go async while previous positions stay visible, and settled positions are memoized by node/edge array identity + options so remounting the same module-constant graph is "ready" immediately (no loading flash). `arcLayout`/`adjacencyMatrix`/`circularLayout` (+ `orderByGroupDegree`, `arcPath`) are the classic physics-free network forms. `allocateCells` is the largest-remainder grid allocator behind `waffleLayout` (turn `{key, weight}[]` + a cell count into integer cells with no rounding drift; `minPerCategory` keeps small categories visible) — reusable for any feature-mix / proportional waffle. **`unitize(value, {unit, maxUnits?, minFraction?})` / `unitizeRange(value, rangeValue, opts)`** is the counting sibling: the pictogram/tally allocator (value → repeated unit signs with a fractional final sign — ISOTYPE: symbols repeat, they never grow). Returns `{units: [{index, fraction, start, end, value}], total, shown, overflow}` — `maxUnits` caps with an `overflow` flag, `minFraction` drops trailing slivers while `total` vs `shown` keeps the ledger honest; `unitizeRange` extends the tally to a projected/scenario endpoint (`rangeUnits` drawn hatched), sharing a mid-sign boundary exactly via `startFraction`. Feeds `glyph`-node `fraction`s directly (unit charts, sign stacks, arrow bundles; `allocateCells` divides fixed cells, `unitize` counts). **Tokenized reasoning helpers**: `generateTokens(input, tokenEncoding)` wraps `unitize` plus `actual`, `fixed-denominator`, `quantile`, `posterior-sample`/`sample`, and seeded `random-sample` strategies with explicit `tokenType` (`dot`/`icon`/`glyph`), `tokenSemantics` (`observed-unit`, `unitized-measure`, `risk-case`, `possible-outcome`, etc.), and `countStrategy`; `{ value, rangeValue }` yields `rangeTokens` for scenario/projection tallies. `layoutTokenGrid` places the resulting tokens for icon/glyph arrays; `normalizeTokenEncoding` keeps legacy `token`/`unit` aliases working while canonical configs use `icon`/`unitValue`; `diagnoseTokenEncoding`, `suggestTokenEncoding`, and `tokenTaskIntentToCapabilityIntents` expose IDID-style warnings, task-aware defaults, and a bridge to `suggestCharts` intents. Built-in token glyph names include `person`, `server`, `chip`, `bolt`, and `bus`. `intervalLanesLayout` (ordinal) packs concurrent `{start,end,lane}` records into stacked Gantt sub-tracks per lane with period bands + lane labels + a time axis — packing runs in rendered-pixel space and honors `minBarWidth` (2) so zero/short-duration events stay visible without overlapping same-track neighbors; `axisFixedForceLayout` (network) pins one axis from a field and settles the other (rect-aware collision). BYO heavy deps (`d3-flextree`, `dagre`) in user code. `packedClusterMatrix` (network) bins records into a column×row matrix of **densely-packed beeswarm clusters** (deterministic self-contained packing, geometry cached) and emits **multi-channel glyphs** — hue (`colorAccessor`/`colorMap`), size (`sizeAccessor`, area), shade (`shadeAccessor`, CIELAB lightness), plus EITHER shape-encoding (`symbolAccessor`/`symbolMap` — the base mark becomes that shape) OR the **composite-glyph** model (`iconAccessor`/`iconMap` — base is a filled circle, only mapped values get a stroked inner icon). `rowMode:"banded"` (default) gives aligned global orbit-bands (row labels align, one enclosure spans the columns per band, columns vary in height) vs `"stacked"` (per-column cell heights ∝ count). `callouts:[{field,value,label}]` draws leader lines to named marks. `cellSizing:"proportional"` makes area ∝ count. **Recipe chrome kit** (exported from `semiotic/recipes`, for any custom-layout's `overlays`): `roundedEnclosure`/`boundsOf` (group/band borders), `bandLabel` (overflow-aware axis/band labels), `markCallout` (leader-line callout to a mark), `readField` (`node.data`-wrapper reader), `groupBy`, `dimFor` (the highlight/dim opacity rule — `{predicate?, highlight?, baseOpacity?, dimOpacity?, brighten?}`; `matchesHighlight` is its `{field,value}[]` matcher), `signatureKey`/`LayoutCache` (content-signature geometry cache so re-styling never re-runs an expensive layout — key by *content*, never by `ctx.nodes` identity), `legendGroupsFrom` (`{colorMap|keys, symbolMap?, sizeStops?}` → `LegendGroup[]` for `frameProps.legend`), `shade`/`makeShade`, `symbolPathString`/`symbolRadius`/`symbolExtent`/`SYMBOL_SEQUENCE`, and the small numeric/color one-liners every layout re-declares — `clamp`, `mean`, `withAlpha`, `nonNegativeFinite` (hex→`rgba()` so a hover-dim can ride a recipe's `resolveColor` callback). (`bandLabel`/`dimFor` are adopted by marimekko/bullet/parallelCoordinates/packedClusterMatrix.) `lineageDagLayout` renders a pre-positioned **layered lineage/DAG** (reads logical layer/row coords, no re-layout) with composite node glyphs (one hit-rect per node + icon/label/store-chip chrome in `overlays`), level-of-detail collapse (full→compact→icon→dot), distinct dashed back-edges, and host-driven reach-dimming (`layoutConfig.reachableIds`) + selection (`layoutConfig.selectedId` / shared `ctx.selection`).
172
+ `semiotic/recipes` ships pure layout functions (`waffleLayout`, `calendarLayout`, `marimekkoLayout`, `bulletLayout`, `parallelCoordinatesLayout`, `intervalLanesLayout`, `flextreeLayout`, `dagreLayout`, `lineageDagLayout`, `axisFixedForceLayout`, `packedClusterMatrix`, `isometricLandmarkLayout`, `forceLayout`, `arcLayout`, `adjacencyMatrix`, `circularLayout`). **Network-analysis kit** (`semiotic/recipes`, pure graph algorithms): `buildAdjacency`, `bfsDistances`, `shortestPath`, `egoNetwork`, `degree`/`betweenness`(Brandes)/`closeness`/`clustering` (+ `normalizeScores`), and `proximityProblem` — the "spatial problem" layout diagnostic that flags nodes drawn closer than their graph distance warrants. Pair with network charts to size by centrality, highlight an ego network on hover, trace a shortest path, or diagnose a misleading layout. `forceLayout(nodes, edges, {seed})` is a **seeded, deterministic** positioner returning normalized `{id:{x,y}}` for `NetworkCustomChart` (same seed ⇒ same layout; re-seed for a "re-run the layout" interaction); `forceLayoutAsync(nodes, edges, {execution?, workerThreshold?, signal?})` is its Promise sibling that runs large layouts in a short-lived module Web Worker (identical deterministic output; graceful sync fallback), and `useForceLayout(nodes, edges, options)` → `{positions, status, error}` is the React wrapper — SSR and first hydration stay synchronous for markup parity, client graph changes go async while previous positions stay visible, and settled positions are memoized by node/edge array identity + options so remounting the same module-constant graph is "ready" immediately (no loading flash). `arcLayout`/`adjacencyMatrix`/`circularLayout` (+ `orderByGroupDegree`, `arcPath`) are the classic physics-free network forms. `allocateCells` is the largest-remainder grid allocator behind `waffleLayout` (turn `{key, weight}[]` + a cell count into integer cells with no rounding drift; `minPerCategory` keeps small categories visible) — reusable for any feature-mix / proportional waffle. **`unitize(value, {unit, maxUnits?, minFraction?})` / `unitizeRange(value, rangeValue, opts)`** is the counting sibling: the pictogram/tally allocator (value → repeated unit signs with a fractional final sign — ISOTYPE: symbols repeat, they never grow). Returns `{units: [{index, fraction, start, end, value}], total, shown, overflow}` — `maxUnits` caps with an `overflow` flag, `minFraction` drops trailing slivers while `total` vs `shown` keeps the ledger honest; `unitizeRange` extends the tally to a projected/scenario endpoint (`rangeUnits` drawn hatched), sharing a mid-sign boundary exactly via `startFraction`. Feeds `glyph`-node `fraction`s directly (unit charts, sign stacks, arrow bundles; `allocateCells` divides fixed cells, `unitize` counts). **Tokenized reasoning helpers**: `generateTokens(input, tokenEncoding)` wraps `unitize` plus `actual`, `fixed-denominator`, `quantile`, `posterior-sample`/`sample`, and seeded `random-sample` strategies with explicit `tokenType` (`dot`/`icon`/`glyph`), `tokenSemantics` (`observed-unit`, `unitized-measure`, `risk-case`, `possible-outcome`, etc.), and `countStrategy`; `{ value, rangeValue }` yields `rangeTokens` for scenario/projection tallies. `layoutTokenGrid` places the resulting tokens for icon/glyph arrays; `normalizeTokenEncoding` keeps legacy `token`/`unit` aliases working while canonical configs use `icon`/`unitValue`; `diagnoseTokenEncoding`, `suggestTokenEncoding`, and `tokenTaskIntentToCapabilityIntents` expose IDID-style warnings, task-aware defaults, and a bridge to `suggestCharts` intents. Built-in token glyph names include `person`, `server`, `chip`, `bolt`, and `bus`. `intervalLanesLayout` (ordinal) packs concurrent `{start,end,lane}` records into stacked Gantt sub-tracks per lane with period bands + lane labels + a time axis — packing runs in rendered-pixel space and honors `minBarWidth` (2) so zero/short-duration events stay visible without overlapping same-track neighbors; `axisFixedForceLayout` (network) pins one axis from a field and settles the other (rect-aware collision). BYO heavy deps (`d3-flextree`, `dagre`) in user code. `packedClusterMatrix` (network) bins records into a column×row matrix of **densely-packed beeswarm clusters** (deterministic self-contained packing, geometry cached) and emits **multi-channel glyphs** — hue (`colorAccessor`/`colorMap`), size (`sizeAccessor`, area), shade (`shadeAccessor`, CIELAB lightness), plus EITHER shape-encoding (`symbolAccessor`/`symbolMap` — the base mark becomes that shape) OR the **composite-glyph** model (`iconAccessor`/`iconMap` — base is a filled circle, only mapped values get a stroked inner icon). `rowMode:"banded"` (default) gives aligned global orbit-bands (row labels align, one enclosure spans the columns per band, columns vary in height) vs `"stacked"` (per-column cell heights ∝ count). `callouts:[{field,value,label}]` draws leader lines to named marks. `cellSizing:"proportional"` makes area ∝ count. **Recipe decoration kit** (exported from `semiotic/recipes`, for any custom-layout's `overlays`): `roundedEnclosure`/`boundsOf` (group/band borders), `bandLabel` (overflow-aware axis/band labels), `markCallout` (leader-line callout to a mark), `readField` (`node.data`-wrapper reader), `groupBy`, `dimFor` (the highlight/dim opacity rule — `{predicate?, highlight?, baseOpacity?, dimOpacity?, brighten?}`; `matchesHighlight` is its `{field,value}[]` matcher), `signatureKey`/`LayoutCache` (content-signature geometry cache so re-styling never re-runs an expensive layout — key by *content*, never by `ctx.nodes` identity), `legendGroupsFrom` (`{colorMap|keys, symbolMap?, sizeStops?}` → `LegendGroup[]` for `frameProps.legend`), `shade`/`makeShade`, `symbolPathString`/`symbolRadius`/`symbolExtent`/`SYMBOL_SEQUENCE`, and the small numeric/color one-liners every layout re-declares — `clamp`, `mean`, `withAlpha`, `nonNegativeFinite` (hex→`rgba()` so a hover-dim can ride a recipe's `resolveColor` callback). (`bandLabel`/`dimFor` are adopted by marimekko/bullet/parallelCoordinates/packedClusterMatrix.) `lineageDagLayout` renders a pre-positioned **layered lineage/DAG** (reads logical layer/row coords, no re-layout) with composite node glyphs (one hit-rect per node + icon/label/store-chip decoration in `overlays`), level-of-detail collapse (full→compact→icon→dot), distinct dashed back-edges, and host-driven reach-dimming (`layoutConfig.reachableIds`) + selection (`layoutConfig.selectedId` / shared `ctx.selection`).
173
173
 
174
174
  `tokenLayer({input, encoding, options})` is the high-level tokenized-rendering helper for custom layouts: it runs `generateTokens`, applies row/column/grid/waffle/dotplot/bar-segment/quantile-strip placement (or `positionToken` for scale/map-driven placement), and returns ordinary dot/symbol/glyph scene nodes with `pointId`/transition identity. Use it for ISOTYPE, icon arrays, risk grids, quantile dotplots, strips, and hybrid token overlays; pass `includeRange` to render `rangeTokens`; drop down to `generateTokens` when a bespoke layout only needs records.
175
175
 
176
- **Chrome (labels/axes/legends): the recipe owns it.** Recipes emit own chrome via `overlays` return field (ReactNode painted on top). Built-in axes via `showAxes` on the HOC work for layouts respecting the standard scale. Recipe convention: `showXxx` boolean toggles, `xxxFormat` callbacks. Shipped recipes' toggles: marimekko `showCategoryLabels`, bullet `showLabels`+`showTicks`, parallelCoordinates `showAxes`, flextree/dagre `showLabels`, waffle/calendar none.
176
+ **Decoration (labels/axes/legends): the recipe owns it.** Recipes emit own decoration via `overlays` return field (ReactNode painted on top). Built-in axes via `showAxes` on the HOC work for layouts respecting the standard scale. Recipe convention: `showXxx` boolean toggles, `xxxFormat` callbacks. Shipped recipes' toggles: marimekko `showCategoryLabels`, bullet `showLabels`+`showTicks`, parallelCoordinates `showAxes`, flextree/dagre `showLabels`, waffle/calendar none.
177
177
 
178
178
  **Interaction (hover/brush/selection): the parent owns it.** Recipes are pure — they take predicate props (e.g. `parallelCoordinatesLayout`'s `highlightFn?`) and the parent manages state via `onObservation` (`{type: "hover" | "hover-end" | ...}`), feeding a derived predicate back into `layoutConfig`. Matching rows render at full opacity; non-matching dim; highlighted z-order on top.
179
179
 
@@ -226,7 +226,7 @@ All components also accept: `width`, `height`, `theme`, `title`, `description`,
226
226
  All HOCs accept `annotations`. Coordinates use data field names.
227
227
 
228
228
  **Positioning**: `widget`, `label`, `callout`, `callout-circle`, `callout-rect`, `text`, `bracket`
229
- **Reference lines**: `y-threshold` (`value`, `label`, `color`, `labelPosition`), `x-threshold`, `band` (`y0`, `y1`), `x-band` (`x0`, `x1`, `fill`, `fillOpacity` — full-height vertical region for eras/phases; skipped when either bound is missing)
229
+ **Reference lines**: `y-threshold` (`value`, `label`, `color`, `labelPosition`), `x-threshold`, `band` (`y0`, `y1` — a `null`/omitted bound extends to the axis min/max on that side, e.g. `y1: null` shades "at least `y0`"), `x-band` (`x0`, `x1`, `fill`, `fillOpacity` — full-height vertical region for eras/phases; a `null`/omitted bound likewise extends to the domain edge; only skipped when the axis has no scale at all)
230
230
  **Ordinal**: `category-highlight`
231
231
  **Enclosures**: `enclose`, `rect-enclose`, `highlight`
232
232
  **Label backgrounds**: every region-bounding annotation (`y-threshold`, `x-threshold`, `band`, `x-band`, `enclose`, `rect-enclose`, `category-highlight`) accepts `labelBackground` for a legibility backdrop behind the label text — `"halo"`/`true` (stroke halo in the plot bg; default for threshold/band labels), `"box"` (semitransparent rounded panel), `"none"`/`false` (plain), or a config `{ type: "halo"|"box", fill?, opacity? (0.85), padding? ({x,y}), radius? (3), stroke?, haloWidth? (3) }`. One shared renderer (`AnnotationLabel`) drives client + SSR, so it works on every frame and in server SVG. `band`/`x-band` `fill` also accepts a `HatchFill` for hatched regions.
@@ -362,7 +362,7 @@ These rules are generated from `ai/behaviorContracts.cjs` and are consumed by `s
362
362
 
363
363
  **Chartability audit**: `auditAccessibility(component, props, { inChartContainer?, describe?, navigable? })` (from `semiotic/ai` or `semiotic/utils`) grades a config against Chartability (POUR-CAF) — credits built-ins, flags author-actionable gaps, marks un-checkable items `manual` (not a false pass). `formatAccessibilityAudit(result)` renders the report. Surfaced as `npx semiotic-ai --audit-a11y` (non-zero exit on critical fail → CI gate) + the `auditAccessibility` MCP tool. Returns `{ ok, summary, findings[] }`; each finding has `{ id, principle, heuristic, critical, status: "pass"|"fail"|"warn"|"manual"|"not-applicable", message, fix }`. NOT a pass/fail cert — pair with manual NVDA/JAWS/VoiceOver testing.
364
364
 
365
- **Chart descriptions**: `describeChart(component, props, { levels?, locale?, capability?, audience? })` (from `semiotic/ai` or `semiotic/utils`) generates a layered natural-language description (Lundgard L1 encoding / L2 statistics / L3 trend) → `{ text, levels: {l1?,l2?,l3?,l4?}, annotations? }`. Richest for XY/bar/part-to-whole/distribution; degrades to L1 for network/hierarchy/geo/value. **Annotations**: when `props.annotations` is present, the result carries an `annotations` sentence ("The author has marked 2 features…") and it *leads* `text` ahead of L1–L3 — an author-placed note is intent in its purest form. Provenance-aware: an `authorKind`/`source` of `agent`/`ai`/`watcher` qualifies it ("an AI-suggested callout"). Absent when no annotations, so un-annotated callers are unchanged. **L4 (intent)**: pass a `capability` (a chart's descriptor or a resolved `{ family, intentScores }`) and `describeChart` emits the illocutionary *communicative-act* sentence ("This is an alerting chart; the peak at March is the point to investigate") — opt-in, default output stays L1–L3. Helpers: `resolveCommunicativeAct(component, capability)`, `communicativeActForIntent(intent)`, type `CommunicativeAct`. **Full-accessibility chrome (title, caption, description, navigation, data download) is the opt-in `ChartContainer` layer — not baked into the bare chart.** `<ChartContainer describe chartConfig={{component, props}}>` renders an sr-only L1–L3 description (`describe={{ visible:true }}` to show it, `{ levels }` for verbosity). The audit's `assistive.features-described` passes when `describe` is on.
365
+ **Chart descriptions**: `describeChart(component, props, { levels?, locale?, capability?, audience? })` (from `semiotic/ai` or `semiotic/utils`) generates a layered natural-language description (Lundgard L1 encoding / L2 statistics / L3 trend) → `{ text, levels: {l1?,l2?,l3?,l4?}, annotations? }`. Richest for XY/bar/part-to-whole/distribution; degrades to L1 for network/hierarchy/geo/value. **Annotations**: when `props.annotations` is present, the result carries an `annotations` sentence ("The author has marked 2 features…") and it *leads* `text` ahead of L1–L3 — an author-placed note is intent in its purest form. Provenance-aware: an `authorKind`/`source` of `agent`/`ai`/`watcher` qualifies it ("an AI-suggested callout"). Absent when no annotations, so un-annotated callers are unchanged. **L4 (intent)**: pass a `capability` (a chart's descriptor or a resolved `{ family, intentScores }`) and `describeChart` emits the illocutionary *communicative-act* sentence ("This is an alerting chart; the peak at March is the point to investigate") — opt-in, default output stays L1–L3. Helpers: `resolveCommunicativeAct(component, capability)`, `communicativeActForIntent(intent)`, type `CommunicativeAct`. **Full-accessibility decoration (title, caption, description, navigation, data download) is the opt-in `ChartContainer` layer — not baked into the bare chart.** `<ChartContainer describe chartConfig={{component, props}}>` renders an sr-only L1–L3 description (`describe={{ visible:true }}` to show it, `{ levels }` for verbosity). The audit's `assistive.features-described` passes when `describe` is on.
366
366
 
367
367
  **Agent-reader grounding**: `buildReaderGrounding(component, props, { capability?, audience?, includeStructure? })` (from `semiotic/ai` or `semiotic/utils`) → `{ description (L1–L3), intent (act + L4 sentence), structure (nav tree), text }` — the single payload an LLM reads to interpret a chart faithfully (the reader-side complement to a capability descriptor). MCP: `groundChart` tool.
368
368
 
@@ -391,7 +391,7 @@ These rules are generated from `ai/behaviorContracts.cjs` and are consumed by `s
391
391
  - **`categoryFormat`/`xFormat`/`yFormat`**: can return ReactNode (renders in `<foreignObject>`). Tick deduplication: adjacent identical labels auto-removed.
392
392
  - **Composing overlays**: XY/Ordinal paint `--semiotic-bg` across the canvas; stack with `frameProps={{ background: "transparent" }}` on the overlay. Network/Geo don't paint bg by default.
393
393
  - **`foregroundGraphics`/`backgroundGraphics` resolved scales**: the function form receives `{ size, margin, scales }` — `scales` is the frame's **resolved** scales (`{x, y}` for XY, `{o, r, projection}` for ordinal; `null` before first layout). Anchor a bespoke SVG overlay to `scales.x(...)`/`scales.y(...)` so it can't drift from the axes the chart drew (the HOC analogue of a custom layout's `ctx.scales`). Fall back to your own mapping while `scales` is null.
394
- - **Theming a custom chart**: prefer `--semiotic-*` tokens for chrome (`stroke="var(--semiotic-text-secondary)"`) and `ctx.resolveColor(key)` / semantic role vars (`var(--semiotic-danger)`) for data, so the chart tracks `ThemeProvider`/dark mode — *or* deliberately paint fixed editorial colors on a `background: "transparent"` frame for art direction. Both are first-class; the kit's chrome helpers default to `--semiotic-*` tokens either way.
394
+ - **Theming a custom chart**: prefer `--semiotic-*` tokens for decoration (`stroke="var(--semiotic-text-secondary)"`) and `ctx.resolveColor(key)` / semantic role vars (`var(--semiotic-danger)`) for data, so the chart tracks `ThemeProvider`/dark mode — *or* deliberately paint fixed editorial colors on a `background: "transparent"` frame for art direction. Both are first-class; the kit's decoration helpers default to `--semiotic-*` tokens either way.
395
395
 
396
396
  ## Performance
397
397
  Prefer string accessors (`xAccessor="value"`) — always referentially stable. Memoize function accessors with `useCallback`.
package/README.md CHANGED
@@ -16,15 +16,17 @@ AI coding assistants generate correct chart code on the first try.
16
16
  <img src="./docs/public/assets/img/semiotic-release-dashboard.svg" alt="Semiotic release dashboard showing chart count, bundle sizes, capability coverage, chart families, and documentation growth" width="100%">
17
17
  <!-- semiotic-readme-dashboard:end -->
18
18
 
19
- ## What's New in 3.8.2
19
+ ## What's New in 3.8.4
20
20
 
21
- 3.8.2 makes the hosted MCP accessibility contract internally consistent:
21
+ 3.8.4 closes the remaining server/browser render gaps and hardens the physics and network runtimes:
22
22
 
23
- - Chart schemas, validation, repair guidance, and audits consistently expose `title`,
24
- `description`, `summary`, and `accessibleTable`.
25
- - Hosted `getChartSchema` returns structured output for component lists, schemas, and errors,
26
- including direct and `ChartContainer` accessibility guidance.
27
- - The public MCP profile remains a focused five-tool surface with render-proven `createChart` output.
23
+ - Static SVG and `renderChart` now match the browser for line+area fills, semantic gradients, line
24
+ bands, pie/donut start angles, swimlane extents, treemap `colorBy`, range candlesticks,
25
+ streamgraph baselines, and the LineChart series features—each guarded by parity tests.
26
+ - Physics simulations settle and rerun reliably via quiescence-based `atRest` detection, and network
27
+ frames rebuild scenes without the duplicate builds that could flash during hydration.
28
+ - New `layoutSequence` and `gridObservatory` recipes, a report-style Semiotic Standard example, and
29
+ lazy-loaded brush overlays that trim the entry graph.
28
30
 
29
31
  ```jsx
30
32
  import { LineChart } from "semiotic/xy"
@@ -371,35 +373,35 @@ The numbers below are **first-party artifact cost**: the gzip size of Semiotic's
371
373
 
372
374
  | Entry Point | gzip | What's inside |
373
375
  |---|---|---|
374
- | `semiotic/xy` | **134 KB** | LineChart, AreaChart, Scatterplot, Heatmap, + 8 more XY charts |
375
- | `semiotic/ordinal` | **115 KB** | BarChart, PieChart, BoxPlot, Histogram, + 11 more categorical charts |
376
- | `semiotic/network` | **121 KB** | ForceDirectedGraph, SankeyDiagram, ProcessSankey, Treemap, + 4 more |
377
- | `semiotic/geo` | **94 KB** | ChoroplethMap, FlowMap, DistanceCartogram, ProportionalSymbolMap |
378
- | `semiotic/realtime` | **147 KB** | RealtimeLineChart, RealtimeHistogram, + 4 streaming charts |
379
- | `semiotic/realtime/core` | **140 KB** | Streaming chart types, HOCs, and buffer helpers |
376
+ | `semiotic/xy` | **138 KB** | LineChart, AreaChart, Scatterplot, Heatmap, + 8 more XY charts |
377
+ | `semiotic/ordinal` | **117 KB** | BarChart, PieChart, BoxPlot, Histogram, + 11 more categorical charts |
378
+ | `semiotic/network` | **123 KB** | ForceDirectedGraph, SankeyDiagram, ProcessSankey, Treemap, + 4 more |
379
+ | `semiotic/geo` | **97 KB** | ChoroplethMap, FlowMap, DistanceCartogram, ProportionalSymbolMap |
380
+ | `semiotic/realtime` | **150 KB** | RealtimeLineChart, RealtimeHistogram, + 4 streaming charts |
381
+ | `semiotic/realtime/core` | **145 KB** | Streaming chart types, HOCs, and buffer helpers |
380
382
  | `semiotic/realtime/react` | **1 KB** | Stream status and synced push hooks |
381
- | `semiotic/server` | **125 KB** | renderChart, renderDashboard, renderToImage, renderToAnimatedGif |
382
- | `semiotic/server/node` | **125 KB** | renderChart, renderDashboard, renderToImage, renderToAnimatedGif |
383
- | `semiotic/server/edge` | **143 KB** | renderChart, renderChartWithEvidence, renderToStaticSVG, renderDashboard |
384
- | `semiotic/utils` | **75 KB** | ThemeProvider, validators, serialization — no chart components |
385
- | `semiotic/utils/core` | **73 KB** | Theme helpers and serialization utilities |
383
+ | `semiotic/server` | **190 KB** | renderChart, renderDashboard, renderToImage, renderToAnimatedGif |
384
+ | `semiotic/server/node` | **190 KB** | renderChart, renderDashboard, renderToImage, renderToAnimatedGif |
385
+ | `semiotic/server/edge` | **208 KB** | renderChart, renderChartWithEvidence, renderToStaticSVG, renderDashboard |
386
+ | `semiotic/utils` | **76 KB** | ThemeProvider, validators, serialization — no chart components |
387
+ | `semiotic/utils/core` | **74 KB** | Theme helpers and serialization utilities |
386
388
  | `semiotic/utils/react` | **6 KB** | ThemeProvider, useTheme, useReducedMotion, useHighContrast, useStreamStatus |
387
- | `semiotic/recipes` | **74 KB** | Pure layout functions (waffle, marimekko, flextree, dagre, …) |
388
- | `semiotic/recipes/core` | **74 KB** | Pure layout functions (waffle, marimekko, flextree, dagre, …) |
389
+ | `semiotic/recipes` | **77 KB** | Pure layout functions (waffle, marimekko, flextree, dagre, …) |
390
+ | `semiotic/recipes/core` | **77 KB** | Pure layout functions (waffle, marimekko, flextree, dagre, …) |
389
391
  | `semiotic/recipes/react` | **2 KB** | Glyph and React layout-selection helpers |
390
392
  | `semiotic/themes` | **7 KB** | Theme presets only (tufte, carbon, etc.) |
391
393
  | `semiotic/themes/core` | **7 KB** | Theme presets and token helpers |
392
394
  | `semiotic/themes/react` | **6 KB** | ThemeProvider/useTheme and hooks |
393
395
  | `semiotic/data` | **4 KB** | bin, rollup, groupBy, pivot, fromVegaLite |
394
396
  | `semiotic/value` | **8 KB** | BigNumber — focal-value KPI / scorecard (SingleValueFrame POC) |
395
- | `semiotic/physics` | **132 KB** | GaltonBoardChart, EventDropChart, PhysicsPileChart, CollisionSwarmChart, PhysicalFlowChart, PhysicsCustomChart |
397
+ | `semiotic/physics` | **133 KB** | GaltonBoardChart, EventDropChart, PhysicsPileChart, CollisionSwarmChart, PhysicalFlowChart, PhysicsCustomChart |
396
398
  | `semiotic/physics/matter` | **1 KB** | Matter.js migration helpers + optional peer guard (no chart components) |
397
399
  | `semiotic/physics/rapier` | **1 KB** | Rapier peer guard + adapter decision metadata (no chart components) |
398
- | `semiotic/ai` | **429 KB** | All schema-backed charts + validation — optimized for LLM code generation |
399
- | `semiotic/ai/core` | **68 KB** | suggestCharts, validateProps, describeChart, repairChartConfig, tool adapters — no chart components |
400
+ | `semiotic/ai` | **432 KB** | All schema-backed charts + validation — optimized for LLM code generation |
401
+ | `semiotic/ai/core` | **70 KB** | suggestCharts, validateProps, describeChart, repairChartConfig, tool adapters — no chart components |
400
402
  | `semiotic/controls` | **12 KB** | DirectManipulationControl, CircularBrush, MobileStandardControls, auditVisualizationControls — no frame renderer |
401
- | `semiotic/rough` | **3 KB** | Optional deterministic Rough.js paint backend — exact Semiotic geometry remains authoritative |
402
- | `semiotic` | **346 KB** | Everything below (full bundle) |
403
+ | `semiotic/rough` | **4 KB** | Optional deterministic Rough.js paint backend — exact Semiotic geometry remains authoritative |
404
+ | `semiotic` | **349 KB** | Everything below (full bundle) |
403
405
 
404
406
  <!-- semiotic-bundle-sizes:end -->
405
407
 
@@ -420,28 +422,28 @@ Method: fresh `npm pack --ignore-scripts` tarball → temporary consumer → min
420
422
 
421
423
  | Public named import | Runtime | gzip cold-consumer bundle |
422
424
  |---|---:|---:|
423
- | `import { LineChart } from "semiotic"` | browser | **145.9 KiB** |
424
- | `import { LineChart } from "semiotic/xy"` | browser | **145.9 KiB** |
425
- | `import { BarChart } from "semiotic/ordinal"` | browser | **120.6 KiB** |
426
- | `import { SankeyDiagram } from "semiotic/network"` | browser | **125.7 KiB** |
427
- | `import { RealtimeLineChart } from "semiotic/realtime"` | browser | **118.3 KiB** |
428
- | `import { RingBuffer } from "semiotic/realtime/core"` | browser | **166.2 KiB** |
425
+ | `import { LineChart } from "semiotic"` | browser | **147.9 KiB** |
426
+ | `import { LineChart } from "semiotic/xy"` | browser | **148.0 KiB** |
427
+ | `import { BarChart } from "semiotic/ordinal"` | browser | **122.1 KiB** |
428
+ | `import { SankeyDiagram } from "semiotic/network"` | browser | **127.1 KiB** |
429
+ | `import { RealtimeLineChart } from "semiotic/realtime"` | browser | **121.3 KiB** |
430
+ | `import { RingBuffer } from "semiotic/realtime/core"` | browser | **169.6 KiB** |
429
431
  | `import { useStreamStatus } from "semiotic/realtime/react"` | browser | **0.6 KiB** |
430
- | `import { GaltonBoardChart } from "semiotic/physics"` | browser | **114.7 KiB** |
432
+ | `import { GaltonBoardChart } from "semiotic/physics"` | browser | **118.7 KiB** |
431
433
  | `import { MATTER_PHYSICS_CAPABILITIES } from "semiotic/physics/matter"` | browser | **0.2 KiB** |
432
434
  | `import { RAPIER_PHYSICS_CAPABILITIES } from "semiotic/physics/rapier"` | browser | **0.2 KiB** |
433
- | `import { renderChart } from "semiotic/server"` | node | **165.0 KiB** |
434
- | `import { generateFrameSVGs } from "semiotic/server/edge"` | node | **80.9 KiB** |
435
- | `import { renderToImage } from "semiotic/server/node"` | node | **165.4 KiB** |
436
- | `import { suggestCharts } from "semiotic/ai"` | browser | **204.9 KiB** |
437
- | `import { suggestCharts } from "semiotic/ai/core"` | browser | **31.1 KiB** |
435
+ | `import { renderChart } from "semiotic/server"` | node | **249.5 KiB** |
436
+ | `import { generateFrameSVGs } from "semiotic/server/edge"` | node | **165.3 KiB** |
437
+ | `import { renderToImage } from "semiotic/server/node"` | node | **249.9 KiB** |
438
+ | `import { suggestCharts } from "semiotic/ai"` | browser | **207.3 KiB** |
439
+ | `import { suggestCharts } from "semiotic/ai/core"` | browser | **32.2 KiB** |
438
440
  | `import { bin } from "semiotic/data"` | browser | **0.4 KiB** |
439
- | `import { ChoroplethMap } from "semiotic/geo"` | browser | **105.2 KiB** |
440
- | `import { createRoughRenderMode } from "semiotic/rough"` | browser | **3.3 KiB** |
441
+ | `import { ChoroplethMap } from "semiotic/geo"` | browser | **106.6 KiB** |
442
+ | `import { createRoughRenderMode } from "semiotic/rough"` | browser | **3.4 KiB** |
441
443
  | `import { resolveThemePreset } from "semiotic/themes"` | browser | **3.4 KiB** |
442
444
  | `import { resolveThemePreset } from "semiotic/themes/core"` | browser | **3.4 KiB** |
443
445
  | `import { ThemeProvider } from "semiotic/themes/react"` | browser | **4.4 KiB** |
444
- | `import { validateProps } from "semiotic/utils"` | browser | **6.4 KiB** |
446
+ | `import { validateProps } from "semiotic/utils"` | browser | **6.9 KiB** |
445
447
  | `import { smartTickFormat } from "semiotic/utils/core"` | browser | **0.2 KiB** |
446
448
  | `import { useReducedMotion } from "semiotic/utils/react"` | browser | **0.3 KiB** |
447
449
  | `import { waffleLayout } from "semiotic/recipes"` | browser | **2.5 KiB** |
package/ai/examples.md CHANGED
@@ -1381,7 +1381,7 @@ import { BigNumber } from "semiotic/value"
1381
1381
  />
1382
1382
  ```
1383
1383
 
1384
- Key props: `value` (the one number), `format` ("number"|"currency"|"percent"|"compact"|"duration"|fn), `comparison` derives a delta with auto-sentiment, `target` renders "X% of goal", `thresholds` map value to a semantic theme role (`--semiotic-{success|warning|danger|info}`). Suppress chrome via `mode="thumbnail"` for dense grids or `mode="inline"` for prose. Stream via `ref.current.push({ value, time })` — pair with `stalenessThreshold` to dim the card when updates stop.
1384
+ Key props: `value` (the one number), `format` ("number"|"currency"|"percent"|"compact"|"duration"|fn), `comparison` derives a delta with auto-sentiment, `target` renders "X% of goal", `thresholds` map value to a semantic theme role (`--semiotic-{success|warning|danger|info}`). Suppress decoration via `mode="thumbnail"` for dense grids or `mode="inline"` for prose. Stream via `ref.current.push({ value, time })` — pair with `stalenessThreshold` to dim the card when updates stop.
1385
1385
 
1386
1386
  ### BigNumber with a Semiotic chart embedded via `trendSlot` (wide / rectangular)
1387
1387