semiotic 3.8.3 → 3.8.5

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 (215) hide show
  1. package/CLAUDE.md +13 -10
  2. package/README.md +52 -45
  3. package/ai/componentMetadata.cjs +1 -1
  4. package/ai/dist/mcp-server.js +4 -2
  5. package/ai/examples.md +168 -1
  6. package/ai/schema.json +3756 -327
  7. package/ai/surface-manifest.json +6 -4
  8. package/ai/system-prompt.md +1 -1
  9. package/dist/chunk-chartContainerDataAudit-74TLKIP6.module.min.js +2 -0
  10. package/dist/chunk-chunk-2JGJ5GUG.module.min.js +0 -1
  11. package/dist/chunk-chunk-4AANPFOX.module.min.js +2 -0
  12. package/dist/{chunk-chunk-JNCHKRIR.module.min.js → chunk-chunk-5I3UZV4Q.module.min.js} +1 -1
  13. package/dist/chunk-chunk-74CCPVXB.module.min.js +1 -0
  14. package/dist/{chunk-chunk-VVCA3DB7.module.min.js → chunk-chunk-7ALTGHK6.module.min.js} +1 -1
  15. package/dist/chunk-chunk-ACLDBMRH.module.min.js +2 -0
  16. package/dist/chunk-chunk-AVBI6GDT.module.min.js +1 -0
  17. package/dist/{chunk-chunk-XQYVEKEJ.module.min.js → chunk-chunk-BANTPINB.module.min.js} +1 -1
  18. package/dist/chunk-chunk-C4YA6TDR.module.min.js +1 -0
  19. package/dist/chunk-chunk-C57LR3YG.module.min.js +1 -0
  20. package/dist/chunk-chunk-CKMD5NV3.module.min.js +2 -0
  21. package/dist/chunk-chunk-CO5KQMGH.module.min.js +1 -0
  22. package/dist/chunk-chunk-CWQ5YIHX.module.min.js +2 -0
  23. package/dist/chunk-chunk-E6TXX6MI.module.min.js +1 -0
  24. package/dist/{chunk-chunk-7FW6EGLP.module.min.js → chunk-chunk-I36PJ4OC.module.min.js} +1 -1
  25. package/dist/chunk-chunk-IY7RYD4S.module.min.js +1 -0
  26. package/dist/chunk-chunk-K3DMEW22.module.min.js +1 -0
  27. package/dist/chunk-chunk-KT2VAZMZ.module.min.js +1 -0
  28. package/dist/{chunk-chunk-AZJUDQRM.module.min.js → chunk-chunk-L6EA5V3G.module.min.js} +1 -1
  29. package/dist/{chunk-chunk-SANERMIZ.module.min.js → chunk-chunk-LJLS6DY7.module.min.js} +1 -1
  30. package/dist/chunk-chunk-LJPCLYSC.module.min.js +2 -0
  31. package/dist/chunk-chunk-LRIFN2HK.module.min.js +1 -0
  32. package/dist/chunk-chunk-N5DDKCCF.module.min.js +1 -0
  33. package/dist/chunk-chunk-NEVE562N.module.min.js +1 -0
  34. package/dist/chunk-chunk-NLO44TOS.module.min.js +2 -0
  35. package/dist/{chunk-chunk-FYY2ACNJ.module.min.js → chunk-chunk-PRDGLMJT.module.min.js} +1 -1
  36. package/dist/chunk-chunk-PZJ2SBLL.module.min.js +2 -0
  37. package/dist/chunk-chunk-QUJ6TTLY.module.min.js +1 -0
  38. package/dist/chunk-chunk-RBIDBUT4.module.min.js +2 -0
  39. package/dist/chunk-chunk-RFLAG356.module.min.js +1 -0
  40. package/dist/chunk-chunk-RPU4IZAV.module.min.js +2 -0
  41. package/dist/chunk-chunk-SKMBCFIL.module.min.js +2 -0
  42. package/dist/chunk-chunk-TAE7W424.module.min.js +2 -0
  43. package/dist/{chunk-chunk-OQTLSTGI.module.min.js → chunk-chunk-TMWIZL3K.module.min.js} +1 -1
  44. package/dist/chunk-chunk-TUBMTZMU.module.min.js +2 -0
  45. package/dist/chunk-chunk-UFFEXJQY.module.min.js +1 -0
  46. package/dist/chunk-chunk-XSCHTS4D.module.min.js +2 -0
  47. package/dist/chunk-chunk-XXWSHGPL.module.min.js +2 -0
  48. package/dist/chunk-chunk-ZOAQDIJO.module.min.js +1 -0
  49. package/dist/chunk-chunk-ZXIXAPRE.module.min.js +2 -0
  50. package/dist/chunk-minimapBrushOverlay-JU57U7NM.module.min.js +2 -0
  51. package/dist/chunk-scatterplotMatrixBrushOverlay-SDIZSN66.module.min.js +2 -0
  52. package/dist/chunk-statisticalOverlays-QGDLRUHX.module.min.js +1 -0
  53. package/dist/chunk-statisticalOverlays-T77OKLDY.module.min.js +1 -0
  54. package/dist/components/ChartContainer.d.ts +12 -2
  55. package/dist/components/ai/chartCapabilities.d.ts +2 -1
  56. package/dist/components/ai/chartCapabilityTypes.d.ts +19 -0
  57. package/dist/components/chartContainerDataAudit.d.ts +10 -0
  58. package/dist/components/charts/geo/ChoroplethMap.d.ts +2 -0
  59. package/dist/components/charts/geo/DistanceCartogram.d.ts +35 -5
  60. package/dist/components/charts/geo/FlowMap.d.ts +4 -0
  61. package/dist/components/charts/geo/ProportionalSymbolMap.d.ts +3 -1
  62. package/dist/components/charts/index.d.ts +1 -0
  63. package/dist/components/charts/network/ForceDirectedGraph.d.ts +36 -1
  64. package/dist/components/charts/physics/CollisionSwarmChart.d.ts +6 -1
  65. package/dist/components/charts/physics/CrucibleChart.capability.d.ts +8 -0
  66. package/dist/components/charts/physics/CrucibleChart.d.ts +63 -0
  67. package/dist/components/charts/physics/GaltonBoardChart.d.ts +6 -1
  68. package/dist/components/charts/physics/crucibleBodyRenderers.d.ts +27 -0
  69. package/dist/components/charts/physics/crucibleChartProps.d.ts +90 -0
  70. package/dist/components/charts/physics/crucibleChrome.d.ts +47 -0
  71. package/dist/components/charts/physics/crucibleController.d.ts +59 -0
  72. package/dist/components/charts/physics/crucibleEffects.d.ts +30 -0
  73. package/dist/components/charts/physics/cruciblePhysics.d.ts +54 -0
  74. package/dist/components/charts/physics/crucibleProgram.d.ts +38 -0
  75. package/dist/components/charts/physics/crucibleTypes.d.ts +415 -0
  76. package/dist/components/charts/physics/gauntletChartProps.d.ts +7 -1
  77. package/dist/components/charts/physics/gauntletChrome.d.ts +2 -1
  78. package/dist/components/charts/physics/gauntletPhysics.d.ts +19 -4
  79. package/dist/components/charts/physics/gauntletRuntime.d.ts +3 -0
  80. package/dist/components/charts/physics/physicsHocUtils.d.ts +12 -0
  81. package/dist/components/charts/shared/chartMode.d.ts +8 -0
  82. package/dist/components/charts/shared/chartSpecCore.d.ts +10 -2
  83. package/dist/components/charts/shared/crucibleWireSchema.d.ts +5 -0
  84. package/dist/components/charts/shared/diagnoseTypes.d.ts +10 -0
  85. package/dist/components/charts/shared/hatchPattern.d.ts +8 -3
  86. package/dist/components/charts/shared/knownChartComponents.d.ts +1 -1
  87. package/dist/components/charts/shared/lineSeriesSsr.d.ts +55 -0
  88. package/dist/components/charts/shared/types.d.ts +2 -2
  89. package/dist/components/charts/shared/validateProps.d.ts +1 -1
  90. package/dist/components/charts/xy/AreaChart.d.ts +10 -0
  91. package/dist/components/charts/xy/minimapBrushOverlay.d.ts +17 -0
  92. package/dist/components/charts/xy/minimapBrushOverlayLazy.d.ts +4 -0
  93. package/dist/components/charts/xy/scatterplotMatrixBrushOverlay.d.ts +8 -0
  94. package/dist/components/charts/xy/scatterplotMatrixBrushOverlayLazy.d.ts +3 -0
  95. package/dist/components/data/auditData.d.ts +51 -0
  96. package/dist/components/data/dataAuditPresentation.d.ts +18 -0
  97. package/dist/components/data/dataAuditSources.d.ts +9 -0
  98. package/dist/components/data/numericContracts.d.ts +59 -0
  99. package/dist/components/data/numericFieldProfiler.d.ts +23 -0
  100. package/dist/components/realtime/types.d.ts +5 -0
  101. package/dist/components/recipes/gridObservatory.d.ts +176 -0
  102. package/dist/components/recipes/netEnsembleLayout.d.ts +167 -0
  103. package/dist/components/recipes/netEnsembleOverlays.d.ts +20 -0
  104. package/dist/components/recipes/recipeChrome.d.ts +24 -0
  105. package/dist/components/recipes/sequenceLayout.d.ts +139 -0
  106. package/dist/components/recipes/wordTrails.d.ts +156 -0
  107. package/dist/components/recipes/wordTrailsProgressiveReveal.d.ts +42 -0
  108. package/dist/components/semiotic-ai-core.d.ts +3 -0
  109. package/dist/components/semiotic-ai-data-audit.d.ts +4 -0
  110. package/dist/components/semiotic-ai.d.ts +7 -5
  111. package/dist/components/semiotic-physics.d.ts +1 -0
  112. package/dist/components/semiotic-recipes-core.d.ts +10 -2
  113. package/dist/components/semiotic-utils-core.d.ts +3 -0
  114. package/dist/components/semiotic.d.ts +1 -1
  115. package/dist/components/server/renderEvidence.d.ts +27 -0
  116. package/dist/components/server/serverChartConfigs.d.ts +1 -0
  117. package/dist/components/server/serverChartConfigsPhysics.d.ts +9 -0
  118. package/dist/components/server/staticAnnotations.d.ts +15 -1
  119. package/dist/components/stream/GeoPipelineStore.d.ts +1 -0
  120. package/dist/components/stream/GeoSVGOverlay.d.ts +7 -0
  121. package/dist/components/stream/SceneToSVG.d.ts +6 -6
  122. package/dist/components/stream/SceneToSVGOrdinal.d.ts +12 -0
  123. package/dist/components/stream/geoCartogram.d.ts +7 -0
  124. package/dist/components/stream/geoTypes.d.ts +17 -2
  125. package/dist/components/stream/networkFrameHandleTypes.d.ts +4 -0
  126. package/dist/components/stream/networkFramePaint.d.ts +2 -0
  127. package/dist/components/stream/physics/PhysicsPipelineStore.d.ts +18 -2
  128. package/dist/components/stream/physics/PhysicsSettledSVG.d.ts +8 -0
  129. package/dist/components/stream/physics/physicsBodyCanvas.d.ts +7 -0
  130. package/dist/components/stream/physics/physicsPipelineEviction.d.ts +15 -0
  131. package/dist/components/stream/physics/physicsPipelineObservations.d.ts +1 -1
  132. package/dist/components/stream/physics/physicsPipelineQuiescence.d.ts +20 -0
  133. package/dist/components/stream/sceneToSVGShared.d.ts +30 -1
  134. package/dist/components/stream/types.d.ts +4 -0
  135. package/dist/components/stream/useHydration.d.ts +7 -1
  136. package/dist/components/stream/xySceneBuilders/candlestickScene.d.ts +1 -1
  137. package/dist/components/useChartContainerDataAudit.d.ts +7 -0
  138. package/dist/forceLayoutWorker.js +1 -1
  139. package/dist/geo.min.js +1 -1
  140. package/dist/geo.module.min.js +1 -1
  141. package/dist/network.min.js +1 -1
  142. package/dist/network.module.min.js +1 -1
  143. package/dist/ordinal.min.js +1 -1
  144. package/dist/ordinal.module.min.js +1 -1
  145. package/dist/physics.min.js +1 -1
  146. package/dist/physics.module.min.js +1 -1
  147. package/dist/physicsWorker.js +1 -1
  148. package/dist/realtime.min.js +1 -1
  149. package/dist/realtime.module.min.js +1 -1
  150. package/dist/rough.min.js +1 -1
  151. package/dist/rough.module.min.js +1 -1
  152. package/dist/semiotic-ai-core.d.ts +3 -0
  153. package/dist/semiotic-ai-core.min.js +1 -1
  154. package/dist/semiotic-ai-core.module.min.js +1 -1
  155. package/dist/semiotic-ai.d.ts +7 -5
  156. package/dist/semiotic-ai.min.js +1 -1
  157. package/dist/semiotic-ai.module.min.js +1 -1
  158. package/dist/semiotic-experimental.min.js +1 -1
  159. package/dist/semiotic-experimental.module.min.js +1 -1
  160. package/dist/semiotic-physics.d.ts +1 -0
  161. package/dist/semiotic-realtime-core.min.js +1 -1
  162. package/dist/semiotic-realtime-core.module.min.js +1 -1
  163. package/dist/semiotic-recipes-core.d.ts +10 -2
  164. package/dist/semiotic-recipes-core.min.js +1 -1
  165. package/dist/semiotic-recipes-core.module.min.js +1 -1
  166. package/dist/semiotic-recipes.min.js +1 -1
  167. package/dist/semiotic-recipes.module.min.js +1 -1
  168. package/dist/semiotic-server-edge.min.js +1 -1
  169. package/dist/semiotic-server-edge.module.min.js +1 -1
  170. package/dist/semiotic-server-node.min.js +1 -1
  171. package/dist/semiotic-server-node.module.min.js +1 -1
  172. package/dist/semiotic-utils-core.d.ts +3 -0
  173. package/dist/semiotic-utils-core.min.js +1 -1
  174. package/dist/semiotic-utils-core.module.min.js +1 -1
  175. package/dist/semiotic-utils.min.js +1 -1
  176. package/dist/semiotic-utils.module.min.js +1 -1
  177. package/dist/semiotic-value.module.min.js +1 -1
  178. package/dist/semiotic.d.ts +1 -1
  179. package/dist/semiotic.min.js +1 -1
  180. package/dist/semiotic.module.min.js +1 -1
  181. package/dist/server.min.js +1 -1
  182. package/dist/server.module.min.js +1 -1
  183. package/dist/xy.min.js +1 -1
  184. package/dist/xy.module.min.js +1 -1
  185. package/package.json +4 -3
  186. package/dist/chunk-chunk-255KDHW5.module.min.js +0 -1
  187. package/dist/chunk-chunk-2CCFBTQC.module.min.js +0 -2
  188. package/dist/chunk-chunk-2RXP3FS3.module.min.js +0 -1
  189. package/dist/chunk-chunk-2WVCH4PL.module.min.js +0 -1
  190. package/dist/chunk-chunk-4TMZXLCR.module.min.js +0 -1
  191. package/dist/chunk-chunk-5APBQXZA.module.min.js +0 -1
  192. package/dist/chunk-chunk-6HSGDMTA.module.min.js +0 -2
  193. package/dist/chunk-chunk-7OEOSCBP.module.min.js +0 -1
  194. package/dist/chunk-chunk-A63CTACR.module.min.js +0 -2
  195. package/dist/chunk-chunk-B6V35LMY.module.min.js +0 -2
  196. package/dist/chunk-chunk-BOSVL645.module.min.js +0 -2
  197. package/dist/chunk-chunk-CP3EN6KZ.module.min.js +0 -1
  198. package/dist/chunk-chunk-F25UA5HT.module.min.js +0 -1
  199. package/dist/chunk-chunk-FRIO6MCV.module.min.js +0 -1
  200. package/dist/chunk-chunk-GWSWSPXN.module.min.js +0 -1
  201. package/dist/chunk-chunk-I6C3KR5H.module.min.js +0 -2
  202. package/dist/chunk-chunk-IP2UE74X.module.min.js +0 -1
  203. package/dist/chunk-chunk-J3ELI22J.module.min.js +0 -1
  204. package/dist/chunk-chunk-J5RL6GXU.module.min.js +0 -2
  205. package/dist/chunk-chunk-L3H6DQCR.module.min.js +0 -2
  206. package/dist/chunk-chunk-M337NY6L.module.min.js +0 -1
  207. package/dist/chunk-chunk-M7VYYHCE.module.min.js +0 -2
  208. package/dist/chunk-chunk-MC35ILYP.module.min.js +0 -2
  209. package/dist/chunk-chunk-N72SWNBV.module.min.js +0 -1
  210. package/dist/chunk-chunk-OBQRFTIL.module.min.js +0 -2
  211. package/dist/chunk-chunk-QRIBHZYW.module.min.js +0 -1
  212. package/dist/chunk-chunk-SA3D43RA.module.min.js +0 -2
  213. package/dist/chunk-chunk-UHSZEB74.module.min.js +0 -1
  214. package/dist/chunk-chunk-Y6AXCRVO.module.min.js +0 -1
  215. package/dist/chunk-chunk-ZCNBUZPL.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` (136KB gz), `semiotic/ordinal` (116KB gz), `semiotic/network` (122KB gz), `semiotic/geo` (95KB gz), `semiotic/realtime` (148KB gz), `semiotic/realtime/core` (143KB gz), `semiotic/realtime/react` (1KB gz), `semiotic/server` (134KB gz), `semiotic/server/node` (134KB gz), `semiotic/server/edge` (157KB gz), `semiotic/utils` (75KB gz), `semiotic/utils/core` (74KB gz), `semiotic/utils/react` (6KB gz), `semiotic/recipes` (75KB 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` (133KB gz), `semiotic/physics/matter` (1KB gz), `semiotic/physics/rapier` (1KB gz), `semiotic/ai` (430KB gz), `semiotic/ai/core` (68KB gz), `semiotic/controls` (12KB gz), `semiotic/rough` (3KB gz). Full `semiotic` is 347KB gz.
7
+ - **Use sub-path imports** — `semiotic/xy` (138KB gz), `semiotic/ordinal` (117KB gz), `semiotic/network` (124KB gz), `semiotic/geo` (98KB gz), `semiotic/realtime` (151KB gz), `semiotic/realtime/core` (145KB gz), `semiotic/realtime/react` (1KB gz), `semiotic/server` (205KB gz), `semiotic/server/node` (205KB gz), `semiotic/server/edge` (224KB gz), `semiotic/utils` (83KB gz), `semiotic/utils/core` (81KB gz), `semiotic/utils/react` (6KB gz), `semiotic/recipes` (84KB gz), `semiotic/recipes/core` (83KB 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` (153KB gz), `semiotic/physics/matter` (1KB gz), `semiotic/physics/rapier` (1KB gz), `semiotic/ai` (462KB gz), `semiotic/ai/core` (77KB gz), `semiotic/controls` (12KB gz), `semiotic/rough` (4KB gz). Full `semiotic` is 376KB 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
 
@@ -58,7 +58,7 @@ All ordinal: `colorBy`, `colorScheme`, `categoryFormat` (string|ReactNode), `sho
58
58
 
59
59
  ## Network Charts (`semiotic/network`)
60
60
 
61
- **ForceDirectedGraph** — `nodes`, `edges`, `nodeIDAccessor`, `sourceAccessor`, `targetAccessor`, `colorBy`, `nodeSize`, `nodeSizeRange`, `edgeWidth`, `iterations` (300), `forceStrength` (0.1 — link-attraction multiplier), `layoutExecution` ("auto" default | "worker" | "sync" — auto runs big layouts in a Web Worker by estimated cost, sync fallback everywhere), `layoutLoadingContent` (ReactNode while worker layout pends; `false` suppresses), `onLayoutStateChange` (`"pending"|"ready"|"error"`), `showLabels`, `nodeLabel`, `styleRules` (style groups of nodes — rules see the raw node; `ctx.category` = colorBy group)
61
+ **ForceDirectedGraph** — `nodes`, `edges`, `nodeIDAccessor`, `sourceAccessor`, `targetAccessor`, `colorBy`, `nodeSize`, `nodeSizeRange`, `nodeStroke`/`nodeStrokeWidth` (node-only outline), `edgeWidth`, `edgeColor`/`edgeOpacity` (edge-only stroke), `iterations` (300), `forceStrength` (0.1 — link-attraction multiplier), `layoutExecution` ("auto" default | "worker" | "sync" — auto runs big layouts in a Web Worker by estimated cost, sync fallback everywhere), `layoutLoadingContent` (ReactNode while worker layout pends; `false` suppresses), `onLayoutStateChange` (`"pending"|"ready"|"error"`), `showLabels`, `nodeLabel`, `styleRules` (style groups of nodes — rules see the raw node; `ctx.category` = colorBy group). **Node vs edge stroking**: the generic `stroke`/`strokeWidth`/`opacity` style *all* marks uniformly; to stroke nodes and edges separately use node-only `nodeStroke`/`nodeStrokeWidth` (e.g. `nodeStroke="none"` drops the node ring) and edge-only `edgeColor`/`edgeWidth`/`edgeOpacity`. Precedence per property: specific > generic > built-in default (so an explicit `edgeColor` is no longer clobbered by a generic `stroke`).
62
62
  **SankeyDiagram** — `edges`, `nodes`, `valueAccessor`, `nodeIdAccessor`, `colorBy`, `edgeColorBy`, `orientation`, `nodeAlign`, `nodeWidth`, `nodePaddingRatio`, `showLabels`
63
63
  **ProcessSankey** — temporal sankey with real time x-axis. `nodes`, `edges` (each with `startTime`/`endTime`), `domain` (req `[t0, t1]`), `axisTicks?`, `xExtentAccessor` (optional `[start, end]` lifetime per node), `colorBy`/`colorScheme`/`showLegend`/`legendPosition`, `pairing` ("value"|"temporal"), `packing` ("off"|"reuse"), `laneOrder` ("crossing-min"|"inside-out"|"crossing-min+inside-out"|"insertion"), `lifetimeMode` ("full"|"half"), `ribbonLane` ("source"|"target"|"both"), `showLaneRails`, `showLabels` (true), `showQualityReadout`, `showParticles` + `particleStyle`, `timeFormat`/`valueFormat`, push API via ref. Static-graph cycles OK as long as edges move forward in time. Use ProcessSankey for time-stamped events; SankeyDiagram for static snapshots.
64
64
  **ChordDiagram** — `edges`, `nodes`, `valueAccessor`, `edgeColorBy`, `padAngle`, `showLabels`
@@ -92,6 +92,7 @@ Process/arrival/distribution charts backed by `StreamPhysicsFrame`. The settled
92
92
  **ProcessFlowChart** — multi-body workflow lane. `data`, `stages` (required: `[{id, label?, force?, damping?, capacity?, pressure?, portal?, absorb?, share?}]`), `stageAccessor` ("stage"), `idAccessor`, `groupBy` (optional feature key; completion when all members hit an absorb stage), `groupLabelAccessor`, `workAccessor`, `radiusAccessor`, `ballRadius` (6), `colorBy`, `groupCompletion` ("allAbsorbed"|"none"), `groupAnchorAlong` (0.55), `showProjection` (true), `showChrome` (true — processChrome kit), `liveCapacity` (true — FIFO queues at `unitsPerSecond`), `onCapacityChange` (queue depth / processed), `bodyLimit` (soft stream budget + oldest eviction), `bodyMark` ("circle"|"halo"|"faceted"|"pill"|"diamond"|"square" or per-row `datum.__physicsMark`), `selection`, `settle`, `seed`, `size`, `paused`, `frameProps`. Settled projection is stage occupancy + capacity badges; use for review queues / triage / merge pipelines. Prefer **GauntletChart** for one compound plan with timed gate effects.
93
93
  **Physics controllers** (`createCapacityQueueController`, `createPortalController`, `composePhysicsControllers`) — process plugins via `controllers`. Capacity `getSnapshot()` → queueDepth/processedCount; emit `physics-capacity-processed`. **processChrome** (`semiotic/physics` / `semiotic/recipes`) — stage bays, capacity badges, feature sockets (theme: `--semiotic-process-*`). `PhysicsCustomChart`: `layout()` may return `regionEffects`, `controllers`, `bodyForces`; `layoutConfig` hot path without re-enqueue. Guide: `/features/physics-process-guide`. Contracts: `PhysicsContracts.test.tsx`.
94
94
  **GauntletChart** — compound project core + tethered positive/negative property bodies + timed gate events. `positiveProperties`/`negativeProperties` (req), `gates`, `events`, `showChrome` (true), `showProjection` (true — viability/outcome strip), `showTethers` (true), `onStateChange`, `frameProps`. Bodies clamp inside walls (`clampGauntletPoint`). Not for multi-item factory floors (use ProcessFlowChart).
95
+ **CrucibleChart** — bounded peer components undergo authored `phases` and `events`, form declared `products`, and settle into reason-labelled `outlets` with source lineage. The ledger/projection is authoritative; motion never infers classification, timing, membership, loss, or routing. `buildCrucibleProductEvents({productId, form, contributions?, complete})` is pure authoring sugar for `combine → contribute* → complete-product`; every source/relation id, event position, reason, and outlet remains caller-supplied. The ref handle's `replay()` atomically restarts the deterministic tape even mid-run (`reset()` restores-and-pauses; `rerunMS` repeats after settlement). `playbackRate` changes presentation only. No push/live-event API.
95
96
  **Pop (body-removal burst)** — every physics HOC ref is a `PhysicsFrameHandle` (extends the shared push handle) exposing **`popBodies(ids, options?)`** (`StreamPhysicsPopOptions` = `{ color?, durationMs?, radius? }`): removes the bodies and plays a burst — expanding ring + inner glow + radial sparks fading over `durationMs` (`drawPopAnimations`) — returning the removed ids. It reads as a *departure*, the physics/exit-emphasis counterpart to realtime **`pulse`**'s data-*arrival* glow (the same transient-emphasis metaphor on opposite ends of a datum's life). GauntletChart also fires it internally on gate-driven property removal; `/examples/nimby` (civic-value balloons) and `/examples/merge-pressure` (merge-risk traits) drive it that way.
96
97
 
97
98
  ## Value Charts (`semiotic/value`)
@@ -153,27 +154,29 @@ When the catalog doesn't fit, four HOCs take a layout function emitting scene pr
153
154
  Layout signature differs by family:
154
155
  - **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
156
  - **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.
157
+ - **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
158
  - **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
159
  - **`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
160
 
160
161
  **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
162
  - **`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).
163
+ - **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
164
  - **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
165
  - **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
166
  - **Interval/timeline**: `packIntervals` (greedy Gantt sub-track packer), `activeCountOverDomain` (concurrency step series).
166
167
  - **`runs`/`runLengthEncode`**: collapse a per-step categorical/boolean series into drawable runs (condition strips, status timelines, calendar ribbons).
167
168
  - **Cyclical math** (day-of-year, hour, compass bearing): `wrapValue`, `shortestArcDelta`, `cyclicRangeContains`, `selectCyclicRange`.
168
169
  - **`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`).
170
+ - **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
171
  - **`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
172
 
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`).
173
+ **Word Trails** (`wordTrailsLayout`) is the quantitatively anchored word-cloud recipe: column = category, segment = ordered vertical position, weight = font size, with overlap-free stable placement. `wordColor`/`wordOpacity` receive `WordTrailsWordInfo`: canonical `word`/`column`/`weight`/`segment` plus the exact source `datum`, `dataIndex`, `columnIndex`, and `resolvedColumnColor`, so derived encodings do not need brittle compound-key lookup maps. Spread `wordTrailsProgressiveReveal({currentSegment, segmentDomain, oldestOpacity?, currentOpacity?, futureOpacity?, combineWeightOpacity?})` into `layoutConfig` for future hiding + linearly faded history without reflow; zero-opacity rows reserve geometry but emit no glyph or hit target. Analysis-derived color/distinctiveness remains a source-data field, not something the layout infers.
174
+
175
+ `semiotic/recipes` ships pure layout functions (`waffleLayout`, `calendarLayout`, `marimekkoLayout`, `bulletLayout`, `parallelCoordinatesLayout`, `intervalLanesLayout`, `flextreeLayout`, `dagreLayout`, `lineageDagLayout`, `netEnsembleLayout`, `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`), `analyzeNetEnsemble` (the headless net census — see `netEnsembleLayout` below), 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`). `netEnsembleLayout` (network) is the complement — for **ensembles of disconnected/trivially-connected DAGs** (a "bag of little graphs" force layout scatters and dagre/flextree can't place). It splits into weakly-connected components, tests each for **directedness** (borrowing the mathematical *net*/directed-set idea: for a weakly-connected DAG, a single sink ⟹ everything converges to one limit; ≥2 sinks ⟹ it branches), fingerprints components with Weisfeiler–Leman refinement so **order-isomorphic motifs** group together, lays each out converging toward its sink(s) at the bottom, and arranges the ensemble as small multiples in motif bands (collapsing to one census glyph per component when cells get tiny). Config: `colorMode` (`"directedness"|"motif"|"category"`), `groupByMotif`, `sort`, `fingerprintRounds`, `minCellForFull`. `analyzeNetEnsemble(nodes, edges)` is the pure headless census (per-component `directed`/sink/source counts + motif classes) with no rendering. Demo + lay explanation at `/recipes/net-ensemble`.
173
176
 
174
177
  `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
178
 
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.
179
+ **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
180
 
178
181
  **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
182
 
@@ -226,7 +229,7 @@ All components also accept: `width`, `height`, `theme`, `title`, `description`,
226
229
  All HOCs accept `annotations`. Coordinates use data field names.
227
230
 
228
231
  **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)
232
+ **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
233
  **Ordinal**: `category-highlight`
231
234
  **Enclosures**: `enclose`, `rect-enclose`, `highlight`
232
235
  **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 +365,7 @@ These rules are generated from `ai/behaviorContracts.cjs` and are consumed by `s
362
365
 
363
366
  **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
367
 
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.
368
+ **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
369
 
367
370
  **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
371
 
@@ -391,7 +394,7 @@ These rules are generated from `ai/behaviorContracts.cjs` and are consumed by `s
391
394
  - **`categoryFormat`/`xFormat`/`yFormat`**: can return ReactNode (renders in `<foreignObject>`). Tick deduplication: adjacent identical labels auto-removed.
392
395
  - **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
396
  - **`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.
397
+ - **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
398
 
396
399
  ## Performance
397
400
  Prefer string accessors (`xAccessor="value"`) — always referentially stable. Memoize function accessors with `useCallback`.
package/README.md CHANGED
@@ -16,16 +16,21 @@ 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.3
20
-
21
- 3.8.3 aligns browser and server rendering while tightening the docs release gate:
22
-
23
- - Static SVG rendering now shares chart defaults, legend layout, color behavior, and specialized
24
- chart paths with the browser renderers.
25
- - SSR parity coverage compares semantic chart output and targeted rendered geometry without
26
- maintaining a second, redundant visual-snapshot matrix.
27
- - Two new narrative examples—Earthquakes and Europa Languages—exercise geographic and network
28
- visualization paths, and every example source route is smoke-tested in CI.
19
+ ## What's New in 3.8.5
20
+
21
+ 3.8.5 adds a new physics chart and two network/word-cloud recipes, then closes another round of
22
+ server/browser render gaps:
23
+
24
+ - New `CrucibleChart` (`semiotic/physics`): bounded peer components undergo authored phases and
25
+ events, form declared products, and settle into reason-labelled outlets with full source lineage.
26
+ - New `wordTrailsLayout` and `netEnsembleLayout` recipes (`semiotic/recipes`) for quantitatively
27
+ anchored word clouds and small-multiple layouts of disconnected graph ensembles, plus a
28
+ Langren-style strip layout for `DistanceCartogram` and separate node/edge stroke controls on
29
+ `ForceDirectedGraph`.
30
+ - `renderChart` now runs a chart's custom `svgAnnotationRules` server-side, honors `frameProps`
31
+ axis/legend/label overrides, and renders ordinal `x-threshold` annotations—closing SSR gaps that
32
+ used to silently drop custom overlays and chrome toggles. `renderChartWithEvidence` also exposes
33
+ the resolved margin and plot rectangle.
29
34
 
30
35
  ```jsx
31
36
  import { LineChart } from "semiotic/xy"
@@ -55,6 +60,7 @@ correct visualizations without trial and error:
55
60
  - **`npx semiotic-mcp`** — an MCP server for tool-based chart rendering in any MCP client
56
61
  - **`npx semiotic-ai --doctor`** — validate component + props JSON from the command line with typo suggestions and anti-pattern detection
57
62
  - **`diagnoseConfig(component, props)`** — programmatic anti-pattern detector with actionable fixes, spanning validation, encoding, accessibility, and misleading-design (deception) checks
63
+ - **`auditData(component, props, data?)`** — chart-aware numeric preflight for inputs that pass schema validation but break the math: non-finite values, zero-span domains, invalid log inputs, negative size geometry, unsafe normalized totals, and scale-dominating outliers. Returns bounded row evidence and flows into `diagnoseConfig`, Chart Clinic, CLI doctor, and opt-in `ChartContainer` notifications
58
64
  - **`CLAUDE.md`** — instruction files auto-synced for Claude, Cursor, Copilot, Windsurf, and Cline
59
65
  - **`llms.txt`** — machine-readable documentation following the emerging standard
60
66
 
@@ -71,8 +77,9 @@ diagrams, tree layouts, treemaps, circle packing, and orbit diagrams — all
71
77
  as React components with the same prop API as LineChart.
72
78
 
73
79
  **Streaming data.** Realtime charts render on canvas at 60fps with a
74
- ref-based push API. Built-in decay, pulse, and staleness encoding for
75
- monitoring dashboards.
80
+ ref-based push API. Rapid network edge pushes coalesce into one layout per
81
+ animation frame, while read/mutation methods preserve synchronous read-after-write
82
+ semantics. Built-in decay, pulse, and staleness encoding for monitoring dashboards.
76
83
 
77
84
  **Coordinated views.** `LinkedCharts` provides hover cross-highlighting,
78
85
  brush cross-filtering, coordinate-based linked crosshairs, and selection
@@ -372,35 +379,35 @@ The numbers below are **first-party artifact cost**: the gzip size of Semiotic's
372
379
 
373
380
  | Entry Point | gzip | What's inside |
374
381
  |---|---|---|
375
- | `semiotic/xy` | **136 KB** | LineChart, AreaChart, Scatterplot, Heatmap, + 8 more XY charts |
376
- | `semiotic/ordinal` | **116 KB** | BarChart, PieChart, BoxPlot, Histogram, + 11 more categorical charts |
377
- | `semiotic/network` | **122 KB** | ForceDirectedGraph, SankeyDiagram, ProcessSankey, Treemap, + 4 more |
378
- | `semiotic/geo` | **95 KB** | ChoroplethMap, FlowMap, DistanceCartogram, ProportionalSymbolMap |
379
- | `semiotic/realtime` | **148 KB** | RealtimeLineChart, RealtimeHistogram, + 4 streaming charts |
380
- | `semiotic/realtime/core` | **143 KB** | Streaming chart types, HOCs, and buffer helpers |
382
+ | `semiotic/xy` | **138 KB** | LineChart, AreaChart, Scatterplot, Heatmap, + 8 more XY charts |
383
+ | `semiotic/ordinal` | **117 KB** | BarChart, PieChart, BoxPlot, Histogram, + 11 more categorical charts |
384
+ | `semiotic/network` | **124 KB** | ForceDirectedGraph, SankeyDiagram, ProcessSankey, Treemap, + 4 more |
385
+ | `semiotic/geo` | **98 KB** | ChoroplethMap, FlowMap, DistanceCartogram, ProportionalSymbolMap |
386
+ | `semiotic/realtime` | **151 KB** | RealtimeLineChart, RealtimeHistogram, + 4 streaming charts |
387
+ | `semiotic/realtime/core` | **145 KB** | Streaming chart types, HOCs, and buffer helpers |
381
388
  | `semiotic/realtime/react` | **1 KB** | Stream status and synced push hooks |
382
- | `semiotic/server` | **134 KB** | renderChart, renderDashboard, renderToImage, renderToAnimatedGif |
383
- | `semiotic/server/node` | **134 KB** | renderChart, renderDashboard, renderToImage, renderToAnimatedGif |
384
- | `semiotic/server/edge` | **157 KB** | renderChart, renderChartWithEvidence, renderToStaticSVG, renderDashboard |
385
- | `semiotic/utils` | **75 KB** | ThemeProvider, validators, serialization — no chart components |
386
- | `semiotic/utils/core` | **74 KB** | Theme helpers and serialization utilities |
389
+ | `semiotic/server` | **205 KB** | renderChart, renderDashboard, renderToImage, renderToAnimatedGif |
390
+ | `semiotic/server/node` | **205 KB** | renderChart, renderDashboard, renderToImage, renderToAnimatedGif |
391
+ | `semiotic/server/edge` | **224 KB** | renderChart, renderChartWithEvidence, renderToStaticSVG, renderDashboard |
392
+ | `semiotic/utils` | **83 KB** | ThemeProvider, numeric/accessibility audits, serialization — no chart components |
393
+ | `semiotic/utils/core` | **81 KB** | Pure theme helpers, numeric/accessibility audits, and serialization |
387
394
  | `semiotic/utils/react` | **6 KB** | ThemeProvider, useTheme, useReducedMotion, useHighContrast, useStreamStatus |
388
- | `semiotic/recipes` | **75 KB** | Pure layout functions (waffle, marimekko, flextree, dagre, …) |
389
- | `semiotic/recipes/core` | **74 KB** | Pure layout functions (waffle, marimekko, flextree, dagre, …) |
395
+ | `semiotic/recipes` | **84 KB** | Pure layout functions (waffle, marimekko, flextree, dagre, …) |
396
+ | `semiotic/recipes/core` | **83 KB** | Pure layout functions (waffle, marimekko, flextree, dagre, …) |
390
397
  | `semiotic/recipes/react` | **2 KB** | Glyph and React layout-selection helpers |
391
398
  | `semiotic/themes` | **7 KB** | Theme presets only (tufte, carbon, etc.) |
392
399
  | `semiotic/themes/core` | **7 KB** | Theme presets and token helpers |
393
400
  | `semiotic/themes/react` | **6 KB** | ThemeProvider/useTheme and hooks |
394
401
  | `semiotic/data` | **4 KB** | bin, rollup, groupBy, pivot, fromVegaLite |
395
402
  | `semiotic/value` | **8 KB** | BigNumber — focal-value KPI / scorecard (SingleValueFrame POC) |
396
- | `semiotic/physics` | **133 KB** | GaltonBoardChart, EventDropChart, PhysicsPileChart, CollisionSwarmChart, PhysicalFlowChart, PhysicsCustomChart |
403
+ | `semiotic/physics` | **153 KB** | GaltonBoardChart, EventDropChart, PhysicsPileChart, CollisionSwarmChart, PhysicalFlowChart, PhysicsCustomChart |
397
404
  | `semiotic/physics/matter` | **1 KB** | Matter.js migration helpers + optional peer guard (no chart components) |
398
405
  | `semiotic/physics/rapier` | **1 KB** | Rapier peer guard + adapter decision metadata (no chart components) |
399
- | `semiotic/ai` | **430 KB** | All schema-backed charts + validation — optimized for LLM code generation |
400
- | `semiotic/ai/core` | **68 KB** | suggestCharts, validateProps, describeChart, repairChartConfig, tool adapters — no chart components |
406
+ | `semiotic/ai` | **462 KB** | All schema-backed charts + validation — optimized for LLM code generation |
407
+ | `semiotic/ai/core` | **77 KB** | suggestCharts, auditData, describeChart, repairChartConfig, tool adapters — no chart components |
401
408
  | `semiotic/controls` | **12 KB** | DirectManipulationControl, CircularBrush, MobileStandardControls, auditVisualizationControls — no frame renderer |
402
- | `semiotic/rough` | **3 KB** | Optional deterministic Rough.js paint backend — exact Semiotic geometry remains authoritative |
403
- | `semiotic` | **347 KB** | Everything below (full bundle) |
409
+ | `semiotic/rough` | **4 KB** | Optional deterministic Rough.js paint backend — exact Semiotic geometry remains authoritative |
410
+ | `semiotic` | **376 KB** | Everything below (full bundle) |
404
411
 
405
412
  <!-- semiotic-bundle-sizes:end -->
406
413
 
@@ -421,28 +428,28 @@ Method: fresh `npm pack --ignore-scripts` tarball → temporary consumer → min
421
428
 
422
429
  | Public named import | Runtime | gzip cold-consumer bundle |
423
430
  |---|---:|---:|
424
- | `import { LineChart } from "semiotic"` | browser | **146.9 KiB** |
425
- | `import { LineChart } from "semiotic/xy"` | browser | **146.9 KiB** |
426
- | `import { BarChart } from "semiotic/ordinal"` | browser | **121.4 KiB** |
427
- | `import { SankeyDiagram } from "semiotic/network"` | browser | **126.0 KiB** |
428
- | `import { RealtimeLineChart } from "semiotic/realtime"` | browser | **120.8 KiB** |
429
- | `import { RingBuffer } from "semiotic/realtime/core"` | browser | **168.9 KiB** |
431
+ | `import { LineChart } from "semiotic"` | browser | **148.0 KiB** |
432
+ | `import { LineChart } from "semiotic/xy"` | browser | **148.0 KiB** |
433
+ | `import { BarChart } from "semiotic/ordinal"` | browser | **122.1 KiB** |
434
+ | `import { SankeyDiagram } from "semiotic/network"` | browser | **127.3 KiB** |
435
+ | `import { RealtimeLineChart } from "semiotic/realtime"` | browser | **121.3 KiB** |
436
+ | `import { RingBuffer } from "semiotic/realtime/core"` | browser | **169.7 KiB** |
430
437
  | `import { useStreamStatus } from "semiotic/realtime/react"` | browser | **0.6 KiB** |
431
- | `import { GaltonBoardChart } from "semiotic/physics"` | browser | **115.8 KiB** |
438
+ | `import { GaltonBoardChart } from "semiotic/physics"` | browser | **137.1 KiB** |
432
439
  | `import { MATTER_PHYSICS_CAPABILITIES } from "semiotic/physics/matter"` | browser | **0.2 KiB** |
433
440
  | `import { RAPIER_PHYSICS_CAPABILITIES } from "semiotic/physics/rapier"` | browser | **0.2 KiB** |
434
- | `import { renderChart } from "semiotic/server"` | node | **177.0 KiB** |
435
- | `import { generateFrameSVGs } from "semiotic/server/edge"` | node | **84.3 KiB** |
436
- | `import { renderToImage } from "semiotic/server/node"` | node | **177.5 KiB** |
437
- | `import { suggestCharts } from "semiotic/ai"` | browser | **205.8 KiB** |
438
- | `import { suggestCharts } from "semiotic/ai/core"` | browser | **31.1 KiB** |
441
+ | `import { renderChart } from "semiotic/server"` | node | **263.8 KiB** |
442
+ | `import { generateFrameSVGs } from "semiotic/server/edge"` | node | **165.7 KiB** |
443
+ | `import { renderToImage } from "semiotic/server/node"` | node | **264.3 KiB** |
444
+ | `import { suggestCharts } from "semiotic/ai"` | browser | **214.3 KiB** |
445
+ | `import { suggestCharts } from "semiotic/ai/core"` | browser | **34.1 KiB** |
439
446
  | `import { bin } from "semiotic/data"` | browser | **0.4 KiB** |
440
- | `import { ChoroplethMap } from "semiotic/geo"` | browser | **105.6 KiB** |
441
- | `import { createRoughRenderMode } from "semiotic/rough"` | browser | **3.3 KiB** |
447
+ | `import { ChoroplethMap } from "semiotic/geo"` | browser | **107.3 KiB** |
448
+ | `import { createRoughRenderMode } from "semiotic/rough"` | browser | **3.4 KiB** |
442
449
  | `import { resolveThemePreset } from "semiotic/themes"` | browser | **3.4 KiB** |
443
450
  | `import { resolveThemePreset } from "semiotic/themes/core"` | browser | **3.4 KiB** |
444
451
  | `import { ThemeProvider } from "semiotic/themes/react"` | browser | **4.4 KiB** |
445
- | `import { validateProps } from "semiotic/utils"` | browser | **6.4 KiB** |
452
+ | `import { validateProps } from "semiotic/utils"` | browser | **7.4 KiB** |
446
453
  | `import { smartTickFormat } from "semiotic/utils/core"` | browser | **0.2 KiB** |
447
454
  | `import { useReducedMotion } from "semiotic/utils/react"` | browser | **0.3 KiB** |
448
455
  | `import { waffleLayout } from "semiotic/recipes"` | browser | **2.5 KiB** |
@@ -26,7 +26,7 @@ const COMPONENTS_BY_CATEGORY = {
26
26
  ],
27
27
  physics: [
28
28
  "GaltonBoardChart", "EventDropChart", "PhysicsPileChart", "CollisionSwarmChart",
29
- "PhysicalFlowChart", "ProcessFlowChart", "GauntletChart",
29
+ "PhysicalFlowChart", "ProcessFlowChart", "GauntletChart", "CrucibleChart",
30
30
  ],
31
31
  value: [
32
32
  "BigNumber",
@@ -6959,7 +6959,8 @@ var require_componentMetadata = __commonJS({
6959
6959
  "CollisionSwarmChart",
6960
6960
  "PhysicalFlowChart",
6961
6961
  "ProcessFlowChart",
6962
- "GauntletChart"
6962
+ "GauntletChart",
6963
+ "CrucibleChart"
6963
6964
  ],
6964
6965
  value: [
6965
6966
  "BigNumber"
@@ -33060,7 +33061,8 @@ var COMPONENT_REGISTRY = {
33060
33061
  CollisionSwarmChart: { component: import_ai.CollisionSwarmChart, category: "physics" },
33061
33062
  PhysicalFlowChart: { component: import_ai.PhysicalFlowChart, category: "physics" },
33062
33063
  ProcessFlowChart: { component: import_ai.ProcessFlowChart, category: "physics" },
33063
- GauntletChart: { component: import_ai.GauntletChart, category: "physics" }
33064
+ GauntletChart: { component: import_ai.GauntletChart, category: "physics" },
33065
+ CrucibleChart: { component: import_ai.CrucibleChart, category: "physics" }
33064
33066
  };
33065
33067
 
33066
33068
  // ai/renderHOCToSVG.tsx
package/ai/examples.md CHANGED
@@ -1184,6 +1184,86 @@ Key props: `y-threshold` works on vertical ordinal charts. `category-highlight`
1184
1184
 
1185
1185
  ---
1186
1186
 
1187
+ ## Recipe Layouts — Explicit Semantics Without Bespoke Geometry
1188
+
1189
+ ### Word Trails (source-aware color + progressive reveal)
1190
+
1191
+ ```jsx
1192
+ import { useState } from "react"
1193
+ import { OrdinalCustomChart } from "semiotic/ordinal"
1194
+ import { wordTrailsLayout, wordTrailsProgressiveReveal } from "semiotic/recipes"
1195
+
1196
+ // One source row per topic / word / recorded model iteration.
1197
+ const topicWordRuns = [
1198
+ { topic: "Topic A", word: "archive", iteration: 0, probability: 0.08, distinctiveness: 0.28 },
1199
+ { topic: "Topic A", word: "archive", iteration: 1, probability: 0.14, distinctiveness: 0.56 },
1200
+ { topic: "Topic A", word: "archive", iteration: 2, probability: 0.19, distinctiveness: 0.84 },
1201
+ { topic: "Topic A", word: "document", iteration: 2, probability: 0.13, distinctiveness: 0.62 },
1202
+ { topic: "Topic B", word: "labor", iteration: 0, probability: 0.09, distinctiveness: 0.35 },
1203
+ { topic: "Topic B", word: "labor", iteration: 1, probability: 0.16, distinctiveness: 0.72 },
1204
+ { topic: "Topic B", word: "factory", iteration: 2, probability: 0.18, distinctiveness: 0.91 },
1205
+ ]
1206
+
1207
+ const topicColors = { "Topic A": "#2563eb", "Topic B": "#db2777" }
1208
+
1209
+ function distinctivenessColor({ datum, resolvedColumnColor }) {
1210
+ // The callback receives the exact source row, plus canonical layout fields.
1211
+ const strength =
1212
+ datum.distinctiveness >= 0.75 ? 100 :
1213
+ datum.distinctiveness >= 0.5 ? 50 :
1214
+ datum.distinctiveness >= 0.25 ? 25 : 0
1215
+ return `color-mix(in srgb, ${resolvedColumnColor} ${strength}%, var(--surface-0, white))`
1216
+ }
1217
+
1218
+ export default function ProgressiveTopicTrails() {
1219
+ const [iteration, setIteration] = useState(2)
1220
+
1221
+ return (
1222
+ <>
1223
+ <label>
1224
+ Model iteration {iteration}
1225
+ <input
1226
+ type="range"
1227
+ min={0}
1228
+ max={2}
1229
+ value={iteration}
1230
+ onChange={(event) => setIteration(Number(event.target.value))}
1231
+ />
1232
+ </label>
1233
+ <OrdinalCustomChart
1234
+ data={topicWordRuns}
1235
+ layout={wordTrailsLayout}
1236
+ layoutConfig={{
1237
+ textAccessor: "word",
1238
+ weightAccessor: "probability",
1239
+ columnAccessor: "topic",
1240
+ segmentAccessor: "iteration",
1241
+ segmentDomain: [0, 2],
1242
+ columnOrder: ["Topic A", "Topic B"],
1243
+ repeatWords: true,
1244
+ wordColor: distinctivenessColor,
1245
+ columnColor: (topic) => topicColors[topic],
1246
+ ...wordTrailsProgressiveReveal({
1247
+ currentSegment: iteration,
1248
+ segmentDomain: [0, 2],
1249
+ oldestOpacity: 0.25,
1250
+ }),
1251
+ }}
1252
+ categoryAccessor="topic"
1253
+ valueAccessor="probability"
1254
+ width={760}
1255
+ height={460}
1256
+ tooltip
1257
+ />
1258
+ </>
1259
+ )
1260
+ }
1261
+ ```
1262
+
1263
+ Key APIs: `WordTrailsWordInfo` gives `word`, `column`, `weight`, and `segment` plus the exact `datum`, `dataIndex`, `columnIndex`, and `resolvedColumnColor`. `wordTrailsProgressiveReveal` hides future segments, fades reached history, and reserves every row's layout slot so playback never reflows. Set `combineWeightOpacity: true` only when opacity should also encode word weight.
1264
+
1265
+ ---
1266
+
1187
1267
  ## Physics Charts — Motion With A Settled Projection
1188
1268
 
1189
1269
  ### GaltonBoardChart (distribution drop)
@@ -1356,6 +1436,93 @@ import { GauntletChart } from "semiotic/physics"
1356
1436
 
1357
1437
  Key props: `positiveProperties` / `negativeProperties` define satellite marks on each project core, `gates` place timed obstacles along the route, and the settled projection strip summarizes viability. Prefer GauntletChart for one plan with many attached attributes; use ProcessFlowChart for many independent work items.
1358
1438
 
1439
+ ### CrucibleChart (explicit product lifecycle + immediate replay)
1440
+
1441
+ ```jsx
1442
+ import { useRef } from "react"
1443
+ import { CrucibleChart, buildCrucibleProductEvents } from "semiotic/physics"
1444
+
1445
+ const evidence = [
1446
+ { id: "deploy-log", label: "Deploy log", kind: "record" },
1447
+ { id: "trace", label: "Request trace", kind: "telemetry" },
1448
+ { id: "rollback", label: "Rollback result", kind: "experiment" },
1449
+ { id: "traffic", label: "Traffic spike", kind: "counterclaim" },
1450
+ ]
1451
+
1452
+ const phases = [
1453
+ { id: "charge", label: "Charge", duration: 1, motion: "charge" },
1454
+ { id: "test", label: "Test mechanism", duration: 2.4, motion: "mix" },
1455
+ { id: "publish", label: "Publish", duration: 1.6, motion: "pour" },
1456
+ ]
1457
+
1458
+ const products = [
1459
+ { id: "finding", label: "Deploy caused regression", outletId: "findings" },
1460
+ ]
1461
+
1462
+ const events = [
1463
+ ...buildCrucibleProductEvents({
1464
+ productId: "finding",
1465
+ form: {
1466
+ at: { phaseId: "test", progress: 0.25 },
1467
+ sourceIds: ["deploy-log", "trace"],
1468
+ label: "Chronology and mechanism agree",
1469
+ },
1470
+ contributions: [{
1471
+ at: { phaseId: "test", progress: 0.7 },
1472
+ sourceIds: ["rollback"],
1473
+ label: "Rollback supplies the counterfactual",
1474
+ }],
1475
+ complete: {
1476
+ at: { phaseId: "publish", progress: 0.55 },
1477
+ outletId: "findings",
1478
+ reason: "Three authored observations support the finding",
1479
+ },
1480
+ }),
1481
+ {
1482
+ id: "reject-traffic",
1483
+ at: { phaseId: "test", progress: 0.82 },
1484
+ effects: [{
1485
+ type: "eject",
1486
+ select: { ids: ["traffic"] },
1487
+ outletId: "contradicted",
1488
+ reason: "The regression also occurs at ordinary traffic levels",
1489
+ }],
1490
+ },
1491
+ ]
1492
+
1493
+ export default function IncidentEvidenceCrucible() {
1494
+ const chartRef = useRef(null)
1495
+
1496
+ return (
1497
+ <>
1498
+ <button type="button" onClick={() => chartRef.current?.replay()}>
1499
+ Replay evidence
1500
+ </button>
1501
+ <CrucibleChart
1502
+ ref={chartRef}
1503
+ data={evidence}
1504
+ phases={phases}
1505
+ products={products}
1506
+ events={events}
1507
+ outlets={[
1508
+ { id: "findings", label: "Supported finding", side: "bottom" },
1509
+ { id: "contradicted", label: "Contradicted", side: "right" },
1510
+ ]}
1511
+ idAccessor="id"
1512
+ labelAccessor="label"
1513
+ categoryAccessor="kind"
1514
+ projection={{ groupBy: "outlet", measure: "count" }}
1515
+ playbackRate={0.8}
1516
+ controls={{ playPause: true, reset: true, stepPhase: true, speed: true }}
1517
+ size={[820, 400]}
1518
+ />
1519
+ </>
1520
+ )
1521
+ }
1522
+ ```
1523
+
1524
+ Key APIs: `buildCrucibleProductEvents` emits the explicit `combine → contribute* → complete-product` grammar and deterministic fallback event ids. It never infers analysis, timing, product membership, reasons, or routing. The handle's `replay()` atomically restarts the same bounded tape even during a run; `reset()` restores the origin and pauses, while `rerunMS` schedules repetition after settlement.
1525
+
1359
1526
  ---
1360
1527
 
1361
1528
  ## Value Charts — One Number Is The Visualization
@@ -1381,7 +1548,7 @@ import { BigNumber } from "semiotic/value"
1381
1548
  />
1382
1549
  ```
1383
1550
 
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.
1551
+ 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
1552
 
1386
1553
  ### BigNumber with a Semiotic chart embedded via `trendSlot` (wide / rectangular)
1387
1554