semiotic 3.6.0 → 3.7.0

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 (94) hide show
  1. package/CLAUDE.md +190 -227
  2. package/README.md +44 -14
  3. package/ai/cli.js +41 -0
  4. package/ai/componentMetadata.cjs +11 -2
  5. package/ai/dist/mcp-server.js +209 -6
  6. package/ai/examples.md +98 -0
  7. package/ai/schema.json +581 -1
  8. package/ai/system-prompt.md +5 -2
  9. package/dist/components/AccessibleNavTree.d.ts +25 -0
  10. package/dist/components/Annotation.d.ts +40 -14
  11. package/dist/components/ChartContainer.d.ts +32 -2
  12. package/dist/components/ai/annotationProvenance.d.ts +349 -0
  13. package/dist/components/ai/audienceProfile.d.ts +60 -3
  14. package/dist/components/ai/chartCapabilityTypes.d.ts +60 -2
  15. package/dist/components/ai/chartRoles.d.ts +27 -0
  16. package/dist/components/ai/conversationArc.d.ts +379 -0
  17. package/dist/components/ai/dataScaleProfile.d.ts +320 -0
  18. package/dist/components/ai/describeChart.d.ts +114 -0
  19. package/dist/components/ai/navigationTree.d.ts +45 -0
  20. package/dist/components/ai/readerGrounding.d.ts +70 -0
  21. package/dist/components/ai/suggestCharts.d.ts +34 -1
  22. package/dist/components/ai/useConversationArc.d.ts +89 -0
  23. package/dist/components/ai/useNavigationSync.d.ts +61 -0
  24. package/dist/components/ai/variantDiscovery.d.ts +168 -0
  25. package/dist/components/charts/realtime/RealtimeHeatmap.d.ts +3 -0
  26. package/dist/components/charts/realtime/RealtimeHistogram.d.ts +3 -0
  27. package/dist/components/charts/realtime/RealtimeLineChart.d.ts +3 -0
  28. package/dist/components/charts/realtime/RealtimeSwarmChart.d.ts +3 -0
  29. package/dist/components/charts/realtime/RealtimeWaterfallChart.d.ts +3 -0
  30. package/dist/components/charts/shared/annotationHierarchy.d.ts +42 -0
  31. package/dist/components/charts/shared/annotationResolvers.d.ts +3 -2
  32. package/dist/components/charts/shared/annotationRules.d.ts +16 -0
  33. package/dist/components/charts/shared/annotationTypes.d.ts +14 -0
  34. package/dist/components/charts/shared/auditAccessibility.d.ts +90 -0
  35. package/dist/components/charts/shared/chartSpecs.d.ts +2 -3
  36. package/dist/components/charts/shared/diagnoseConfig.d.ts +4 -6
  37. package/dist/components/charts/shared/selectionUtils.d.ts +5 -2
  38. package/dist/components/charts/shared/streamPropsHelpers.d.ts +2 -0
  39. package/dist/components/charts/shared/types.d.ts +5 -1
  40. package/dist/components/charts/value/BigNumber.capability.d.ts +13 -0
  41. package/dist/components/charts/value/BigNumber.d.ts +14 -0
  42. package/dist/components/charts/value/formatting.d.ts +40 -0
  43. package/dist/components/charts/value/thresholdSparkline.d.ts +40 -0
  44. package/dist/components/charts/value/types.d.ts +292 -0
  45. package/dist/components/realtime/lifecycleBands.d.ts +44 -0
  46. package/dist/components/realtime/types.d.ts +23 -8
  47. package/dist/components/recipes/annotationDensity.d.ts +69 -0
  48. package/dist/components/recipes/annotationLayout.d.ts +93 -0
  49. package/dist/components/semiotic-ai.d.ts +38 -15
  50. package/dist/components/semiotic-realtime.d.ts +2 -0
  51. package/dist/components/semiotic-recipes.d.ts +4 -0
  52. package/dist/components/semiotic-utils.d.ts +8 -0
  53. package/dist/components/semiotic-value.d.ts +55 -0
  54. package/dist/components/semiotic.d.ts +7 -0
  55. package/dist/components/server/staticAnnotations.d.ts +2 -0
  56. package/dist/components/stream/AccessibleDataTable.d.ts +10 -1
  57. package/dist/components/stream/NetworkSVGOverlay.d.ts +11 -5
  58. package/dist/components/stream/OrdinalSVGOverlay.d.ts +2 -0
  59. package/dist/components/stream/SVGOverlay.d.ts +2 -0
  60. package/dist/components/stream/geoTypes.d.ts +3 -0
  61. package/dist/components/stream/networkTypes.d.ts +2 -0
  62. package/dist/components/stream/ordinalTypes.d.ts +2 -0
  63. package/dist/components/stream/types.d.ts +2 -0
  64. package/dist/geo.min.js +1 -1
  65. package/dist/geo.module.min.js +1 -1
  66. package/dist/network.min.js +1 -1
  67. package/dist/network.module.min.js +1 -1
  68. package/dist/ordinal.min.js +1 -1
  69. package/dist/ordinal.module.min.js +1 -1
  70. package/dist/realtime.min.js +1 -1
  71. package/dist/realtime.module.min.js +1 -1
  72. package/dist/semiotic-ai.d.ts +38 -15
  73. package/dist/semiotic-ai.min.js +1 -1
  74. package/dist/semiotic-ai.module.min.js +1 -1
  75. package/dist/semiotic-realtime.d.ts +2 -0
  76. package/dist/semiotic-recipes.d.ts +4 -0
  77. package/dist/semiotic-recipes.min.js +1 -1
  78. package/dist/semiotic-recipes.module.min.js +1 -1
  79. package/dist/semiotic-themes.min.js +1 -1
  80. package/dist/semiotic-themes.module.min.js +1 -1
  81. package/dist/semiotic-utils.d.ts +8 -0
  82. package/dist/semiotic-utils.min.js +1 -1
  83. package/dist/semiotic-utils.module.min.js +1 -1
  84. package/dist/semiotic-value.d.ts +55 -0
  85. package/dist/semiotic-value.min.js +2 -0
  86. package/dist/semiotic-value.module.min.js +2 -0
  87. package/dist/semiotic.d.ts +7 -0
  88. package/dist/semiotic.min.js +1 -1
  89. package/dist/semiotic.module.min.js +1 -1
  90. package/dist/server.min.js +1 -1
  91. package/dist/server.module.min.js +1 -1
  92. package/dist/xy.min.js +1 -1
  93. package/dist/xy.module.min.js +1 -1
  94. package/package.json +18 -4
package/README.md CHANGED
@@ -35,7 +35,7 @@ generate correct code without examples.
35
35
  Semiotic ships with everything an AI coding assistant needs to generate
36
36
  correct visualizations without trial and error:
37
37
 
38
- - **`semiotic/ai`** — a single import with 42 HOC charts (XY, ordinal, network, realtime), optimized for LLM code generation. Geo charts are in `semiotic/geo` to keep d3-geo out of non-geo bundles.
38
+ - **`semiotic/ai`** — a single import with the 47-chart capability catalog (XY, ordinal, network, realtime, geo, value), optimized for LLM code generation. Prefer family subpaths such as `semiotic/xy`, `semiotic/geo`, and `semiotic/value` when bundle size matters.
39
39
  - **`ai/schema.json`** — machine-readable prop schemas for every component
40
40
  - **`npx semiotic-mcp`** — an MCP server for tool-based chart rendering in any MCP client
41
41
  - **`npx semiotic-ai --doctor`** — validate component + props JSON from the command line with typo suggestions and anti-pattern detection
@@ -71,6 +71,13 @@ with d3-geo projections, zoom/pan, tile basemaps, and drag-rotate globe spinning
71
71
  LOESS smoothing, forecast with confidence envelopes, and anomaly detection.
72
72
  Marginal distribution graphics on scatterplot axes with a single prop.
73
73
 
74
+ **First-class annotations.** Annotations are data-bound objects, not post-hoc
75
+ artwork. Labels, callouts, thresholds, enclosures, statistical overlays, and
76
+ React widgets move with the chart and render through browser, SSR, and export
77
+ paths. Opt into placement, hierarchy, density, progressive disclosure,
78
+ audience-aware amount, provenance, and editorial lifecycle when the chart
79
+ needs to communicate more than its encoding alone.
80
+
74
81
  ### Start simple, go deep
75
82
 
76
83
  | Layer | For | Example |
@@ -278,6 +285,27 @@ configToJSX(config)
278
285
  Supports bar, line, area, point, rect, arc, tick marks with encoding translation
279
286
  for color, size, aggregation, and binning.
280
287
 
288
+ ### Conversation Arc Telemetry
289
+
290
+ Capture and replay the path an AI-assisted chart session took:
291
+
292
+ ```ts
293
+ import {
294
+ createLocalStorageConversationArcSink,
295
+ enableConversationArc,
296
+ getConversationArcStore,
297
+ loadConversationArc,
298
+ registerConversationArcSink,
299
+ } from "semiotic/ai"
300
+
301
+ const sink = createLocalStorageConversationArcSink({ key: "my-app:arc" })
302
+ registerConversationArcSink(sink)
303
+ enableConversationArc({ sessionId: "session-abc" })
304
+
305
+ getConversationArcStore().record({ type: "chart-rendered", component: "LineChart" })
306
+ loadConversationArc(sink.load(), { enabled: false })
307
+ ```
308
+
281
309
  ## Bundle Sizes
282
310
 
283
311
  Semiotic ships 12 entry points. **Don't import from `"semiotic"` unless you need everything** — use the sub-path that matches your chart type:
@@ -287,18 +315,19 @@ Semiotic ships 12 entry points. **Don't import from `"semiotic"` unless you need
287
315
 
288
316
  | Entry Point | gzip | What's inside |
289
317
  |---|---|---|
290
- | `semiotic/xy` | **86 KB** | LineChart, AreaChart, Scatterplot, Heatmap, + 8 more XY charts |
291
- | `semiotic/ordinal` | **70 KB** | BarChart, PieChart, BoxPlot, Histogram, + 11 more categorical charts |
292
- | `semiotic/network` | **64 KB** | ForceDirectedGraph, SankeyDiagram, ProcessSankey, Treemap, + 4 more |
293
- | `semiotic/geo` | **52 KB** | ChoroplethMap, FlowMap, DistanceCartogram, ProportionalSymbolMap |
294
- | `semiotic/realtime` | **91 KB** | RealtimeLineChart, RealtimeHistogram, + 4 streaming charts |
295
- | `semiotic/server` | **122 KB** | renderChart, renderDashboard, renderToImage, renderToAnimatedGif |
296
- | `semiotic/utils` | **22 KB** | ThemeProvider, validators, serialization — no chart components |
297
- | `semiotic/recipes` | **5 KB** | Pure layout functions (waffle, marimekko, flextree, dagre, …) |
318
+ | `semiotic/xy` | **90 KB** | LineChart, AreaChart, Scatterplot, Heatmap, + 8 more XY charts |
319
+ | `semiotic/ordinal` | **74 KB** | BarChart, PieChart, BoxPlot, Histogram, + 11 more categorical charts |
320
+ | `semiotic/network` | **68 KB** | ForceDirectedGraph, SankeyDiagram, ProcessSankey, Treemap, + 4 more |
321
+ | `semiotic/geo` | **55 KB** | ChoroplethMap, FlowMap, DistanceCartogram, ProportionalSymbolMap |
322
+ | `semiotic/realtime` | **95 KB** | RealtimeLineChart, RealtimeHistogram, + 4 streaming charts |
323
+ | `semiotic/server` | **127 KB** | renderChart, renderDashboard, renderToImage, renderToAnimatedGif |
324
+ | `semiotic/utils` | **37 KB** | ThemeProvider, validators, serialization — no chart components |
325
+ | `semiotic/recipes` | **9 KB** | Pure layout functions (waffle, marimekko, flextree, dagre, …) |
298
326
  | `semiotic/themes` | **4 KB** | Theme presets only (tufte, carbon, etc.) |
299
327
  | `semiotic/data` | **3 KB** | bin, rollup, groupBy, pivot, fromVegaLite |
300
- | `semiotic/ai` | **211 KB** | All 42 HOCs + validation optimized for LLM code generation |
301
- | `semiotic` | **190 KB** | Everything below (full bundle) |
328
+ | `semiotic/value` | **6 KB** | BigNumber focal-value KPI / scorecard (SingleValueFrame POC) |
329
+ | `semiotic/ai` | **246 KB** | All 47 schema-backed charts + validation — optimized for LLM code generation |
330
+ | `semiotic` | **203 KB** | Everything below (full bundle) |
302
331
 
303
332
  <!-- semiotic-bundle-sizes:end -->
304
333
 
@@ -389,7 +418,7 @@ No API keys or authentication required. The server runs locally via stdio. HTTP
389
418
  | Tool | Description |
390
419
  |------|-------------|
391
420
  | **`renderChart`** | Render a Semiotic chart to static SVG. Supports the components returned by `getSchema` that are marked `[renderable]`. Pass `{ component: "LineChart", props: { data: [...], xAccessor: "x", yAccessor: "y" } }`. Returns SVG string or validation errors with fix suggestions. |
392
- | **`getSchema`** | Return the prop schema for a specific component. Pass `{ component: "LineChart" }` to get its props, or omit `component` to list all 46 chart schemas. Components marked `[renderable]` are available through `renderChart`; realtime charts require a browser/live environment. |
421
+ | **`getSchema`** | Return the prop schema for a specific component. Pass `{ component: "LineChart" }` to get its props, or omit `component` to list all 47 chart schemas. Components marked `[renderable]` are available through `renderChart`; realtime charts require a browser/live environment. |
393
422
  | **`suggestChart`** | Legacy sample-row recommender. Pass `{ data: [{...}, ...] }` with 1–5 sample objects plus optional broad intent/capability filters. |
394
423
  | **`suggestCharts`** | Capability-based recommender for bounded row data. Returns ranked chart suggestions with scores, reasons, caveats, import paths, and ready-to-use props. |
395
424
  | **`suggestStreamCharts`** | Recommend realtime charts from a stream schema, throughput, and retention hints. |
@@ -509,7 +538,7 @@ Semiotic is indexed by AI-coding-agent documentation tools so your assistant (Cl
509
538
 
510
539
  Agent-facing API surface:
511
540
 
512
- - **`CLAUDE.md`**, **`ai/schema.json`**, **`ai/behaviorContracts.cjs`** — bundled in the npm tarball (see `package.json#files`); agents that install Semiotic locally read these directly. `CLAUDE.md` is the quick-start cheat sheet (HOC props, push API, theming, usage notes); `ai/schema.json` is the JSON Schema for every chart's prop surface (46 charts); `ai/behaviorContracts.cjs` carries the agent-visible semantic rules (color precedence, push-mode requirements, ID-accessor contracts).
541
+ - **`CLAUDE.md`**, **`ai/schema.json`**, **`ai/behaviorContracts.cjs`** — bundled in the npm tarball (see `package.json#files`); agents that install Semiotic locally read these directly. `CLAUDE.md` is the quick-start cheat sheet (HOC props, push API, theming, usage notes); `ai/schema.json` is the JSON Schema for every chart's prop surface (47 charts); `ai/behaviorContracts.cjs` carries the agent-visible semantic rules (color precedence, push-mode requirements, ID-accessor contracts).
513
542
  - [**`semiotic.nteract.io/llms.txt`**](https://semiotic.nteract.io/llms.txt) + [**`/llms-full.txt`**](https://semiotic.nteract.io/llms-full.txt) — deployed at the docs site per the [llms.txt standard](https://llmstxt.org). Agents fetch the navigation map (`llms.txt`) or the full inlined docs (`llms-full.txt`) over HTTP; they're not part of the npm package itself.
514
543
 
515
544
  ## Documentation
@@ -519,7 +548,8 @@ Agent-facing API surface:
519
548
  - [Getting Started](https://semiotic.nteract.io/getting-started)
520
549
  - [Charts](https://semiotic.nteract.io/charts) — chart types with live examples
521
550
  - [Frames](https://semiotic.nteract.io/frames) — full Frame API reference
522
- - [Features](https://semiotic.nteract.io/features) — axes, annotations, tooltips, styling, Vega-Lite translator
551
+ - [Features](https://semiotic.nteract.io/features) — axes, tooltips, interaction, responsive behavior, and composition
552
+ - [Annotations](https://semiotic.nteract.io/annotations) — first-class annotation types, design guidance, provenance, and lifecycle
523
553
  - [Cookbook](https://semiotic.nteract.io/cookbook) — advanced patterns and recipes
524
554
  - [Playground](https://semiotic.nteract.io/playground) — interactive prop exploration
525
555
 
package/ai/cli.js CHANGED
@@ -48,6 +48,8 @@ Usage:
48
48
  npx semiotic-ai --compact Print ai/system-prompt.md (compact prompt)
49
49
  npx semiotic-ai --examples Print ai/examples.md (copy-paste examples)
50
50
  npx semiotic-ai --doctor Validate { component, props, usageMode? } JSON from stdin
51
+ npx semiotic-ai --audit-a11y Audit { component, props, inChartContainer?, describe?, navigable? }
52
+ JSON against Chartability (POUR-CAF) accessibility heuristics
51
53
  npx semiotic-ai --help Show this help message
52
54
  `.trim()
53
55
 
@@ -341,6 +343,45 @@ if (flag === "--doctor") {
341
343
  process.exit(0)
342
344
  }
343
345
 
346
+ // --audit-a11y: grade component + props against Chartability heuristics
347
+ if (flag === "--audit-a11y") {
348
+ const input = readJSONInput("Usage: npx semiotic-ai --audit-a11y '{\"component\":\"LineChart\",\"props\":{\"data\":[...],\"xAccessor\":\"x\",\"yAccessor\":\"y\"}}'\n echo '{\"component\":\"BarChart\",\"props\":{...},\"inChartContainer\":true}' | npx semiotic-ai --audit-a11y")
349
+
350
+ try {
351
+ const { component, props, inChartContainer, describe, navigable } = JSON.parse(input)
352
+ if (!component || !props) {
353
+ console.error("Input must be JSON with { component, props } fields.")
354
+ process.exit(1)
355
+ }
356
+
357
+ // Load the audit from dist (same strategy as --doctor). It lives in the
358
+ // semiotic/ai bundle; a clean source checkout without a build can't run it.
359
+ const distPath = path.join(pkgRoot, "dist", "semiotic-ai.min.js")
360
+ let auditAccessibility, formatAccessibilityAudit
361
+ try {
362
+ if (!process.env.SEMIOTIC_AI_SCHEMA_ONLY) {
363
+ const mod = require(distPath)
364
+ auditAccessibility = mod.auditAccessibility
365
+ formatAccessibilityAudit = mod.formatAccessibilityAudit
366
+ }
367
+ } catch (e) {
368
+ // Dist unavailable.
369
+ }
370
+
371
+ if (!auditAccessibility || !formatAccessibilityAudit) {
372
+ console.error("Accessibility audit requires the built library. Run `npm run dist` first, or use the MCP `auditAccessibility` tool.")
373
+ process.exit(2)
374
+ }
375
+
376
+ const result = auditAccessibility(component, props, { inChartContainer: inChartContainer === true, describe: describe === true, navigable: navigable === true })
377
+ console.log(formatAccessibilityAudit(result))
378
+ process.exit(result.ok ? 0 : 1)
379
+ } catch (err) {
380
+ console.error(`Failed to parse input: ${errorMessage(err)}`)
381
+ process.exit(1)
382
+ }
383
+ }
384
+
344
385
  const filePath = flag ? FILES[flag] : FILES.default
345
386
 
346
387
  if (!filePath) {
@@ -1,6 +1,6 @@
1
1
  "use strict"
2
2
 
3
- const CATEGORY_ORDER = ["xy", "ordinal", "network", "geo", "realtime"]
3
+ const CATEGORY_ORDER = ["xy", "ordinal", "network", "geo", "realtime", "value"]
4
4
 
5
5
  const COMPONENTS_BY_CATEGORY = {
6
6
  xy: [
@@ -24,6 +24,9 @@ const COMPONENTS_BY_CATEGORY = {
24
24
  "RealtimeLineChart", "RealtimeHistogram", "TemporalHistogram", "RealtimeSwarmChart",
25
25
  "RealtimeWaterfallChart", "RealtimeHeatmap",
26
26
  ],
27
+ value: [
28
+ "BigNumber",
29
+ ],
27
30
  }
28
31
 
29
32
  const COMPONENT_TO_CATEGORY = new Map()
@@ -59,11 +62,17 @@ function metadataForComponent(entryOrName) {
59
62
  // any other static HOC. Matches the name-prefix exclusion the
60
63
  // check-surface-parity script applies.
61
64
  const isPushOnly = category === "realtime" && name.startsWith("Realtime")
65
+ // Value-family charts (BigNumber, future SingleValueFrame HOCs)
66
+ // render via react-dom/server in a normal React tree, but they do
67
+ // not route through the frame-driven `renderChart` path in
68
+ // serverChartConfigs.ts — so they are not MCP-renderable. Mirrors
69
+ // the `hoc-ssr-only` special feature documented in chartSpecs.ts.
70
+ const isValueCategory = category === "value"
62
71
  return {
63
72
  name,
64
73
  category,
65
74
  importPath: importPathForCategory(category),
66
- renderable: !isPushOnly,
75
+ renderable: !isPushOnly && !isValueCategory,
67
76
  description: typeof entryOrName === "string" ? undefined : entryOrName.description,
68
77
  }
69
78
  }
@@ -6890,7 +6890,7 @@ var require_dist = __commonJS({
6890
6890
  var require_componentMetadata = __commonJS({
6891
6891
  "ai/componentMetadata.cjs"(exports2, module2) {
6892
6892
  "use strict";
6893
- var CATEGORY_ORDER = ["xy", "ordinal", "network", "geo", "realtime"];
6893
+ var CATEGORY_ORDER = ["xy", "ordinal", "network", "geo", "realtime", "value"];
6894
6894
  var COMPONENTS_BY_CATEGORY = {
6895
6895
  xy: [
6896
6896
  "LineChart",
@@ -6947,6 +6947,9 @@ var require_componentMetadata = __commonJS({
6947
6947
  "RealtimeSwarmChart",
6948
6948
  "RealtimeWaterfallChart",
6949
6949
  "RealtimeHeatmap"
6950
+ ],
6951
+ value: [
6952
+ "BigNumber"
6950
6953
  ]
6951
6954
  };
6952
6955
  var COMPONENT_TO_CATEGORY = /* @__PURE__ */ new Map();
@@ -6972,11 +6975,12 @@ var require_componentMetadata = __commonJS({
6972
6975
  const name = typeof entryOrName === "string" ? entryOrName : entryOrName.name;
6973
6976
  const category = categoryForComponent(name);
6974
6977
  const isPushOnly = category === "realtime" && name.startsWith("Realtime");
6978
+ const isValueCategory = category === "value";
6975
6979
  return {
6976
6980
  name,
6977
6981
  category,
6978
6982
  importPath: importPathForCategory(category),
6979
- renderable: !isPushOnly,
6983
+ renderable: !isPushOnly && !isValueCategory,
6980
6984
  description: typeof entryOrName === "string" ? void 0 : entryOrName.description
6981
6985
  };
6982
6986
  }
@@ -32643,6 +32647,22 @@ ${contracts}` : msg}` }] };
32643
32647
  isError: true
32644
32648
  };
32645
32649
  }
32650
+ async function auditAccessibilityHandler(args) {
32651
+ const component = args.component;
32652
+ const props = args.props ?? {};
32653
+ if (!component) {
32654
+ return {
32655
+ content: [{ type: "text", text: "Missing 'component' field. Provide { component: 'LineChart', props: { ... } }." }],
32656
+ isError: true
32657
+ };
32658
+ }
32659
+ const result = (0, import_ai3.auditAccessibility)(component, props, { inChartContainer: args.inChartContainer === true, describe: args.describe === true, navigable: args.navigable === true });
32660
+ return {
32661
+ content: [{ type: "text", text: (0, import_ai3.formatAccessibilityAudit)(result) }],
32662
+ // Only block on provable critical failures; warnings/manual items are advisory.
32663
+ isError: !result.ok
32664
+ };
32665
+ }
32646
32666
  async function reportIssueHandler(args) {
32647
32667
  const title = args.title;
32648
32668
  const body = args.body;
@@ -32743,6 +32763,99 @@ Dark-mode presets: ${THEME_PRESET_NAMES.filter((n) => n.includes("dark")).join("
32743
32763
  content: [{ type: "text", text: usage.join("\n") }]
32744
32764
  };
32745
32765
  }
32766
+ function profileInputFromVariantArgs(args) {
32767
+ const props = args.props ?? {};
32768
+ if (Array.isArray(args.data)) {
32769
+ return { data: args.data };
32770
+ }
32771
+ if (Array.isArray(props.data)) {
32772
+ return { data: props.data };
32773
+ }
32774
+ if (Array.isArray(props.nodes) && (Array.isArray(props.edges) || Array.isArray(props.links))) {
32775
+ return {
32776
+ data: [],
32777
+ rawInput: {
32778
+ nodes: props.nodes,
32779
+ edges: props.edges ?? props.links
32780
+ }
32781
+ };
32782
+ }
32783
+ if (props.data && typeof props.data === "object" && !Array.isArray(props.data)) {
32784
+ return { data: [], rawInput: props.data };
32785
+ }
32786
+ return { data: [] };
32787
+ }
32788
+ function buildVariantProposalProps(proposal, profile, audience) {
32789
+ if (proposal.buildProps) return proposal.buildProps(profile, audience);
32790
+ const capability = (0, import_ai3.getCapability)(proposal.baseComponent);
32791
+ const variant = proposal.variantKey ? capability?.variants?.find((v) => v.key === proposal.variantKey) : void 0;
32792
+ return capability ? capability.buildProps(profile, variant) : {};
32793
+ }
32794
+ async function proposeChartVariantsHandler(args) {
32795
+ const { component, intent, maxResults, audience } = args;
32796
+ const capability = (0, import_ai3.getCapability)(component);
32797
+ if (!capability) {
32798
+ return {
32799
+ content: [{ type: "text", text: `No chart capability registered for "${component}". Call suggestCharts first to pick from known capability components.` }],
32800
+ isError: true
32801
+ };
32802
+ }
32803
+ const { data, rawInput } = profileInputFromVariantArgs(args);
32804
+ const profile = (0, import_ai3.profileData)(data, { rawInput });
32805
+ const intentArg = Array.isArray(intent) ? intent : intent ? [intent] : void 0;
32806
+ const fitReason = capability.fits(profile);
32807
+ const proposals = (0, import_ai3.proposeVariant)(component, capability, {
32808
+ profile,
32809
+ audience,
32810
+ intent: intentArg,
32811
+ existingVariants: capability.variants
32812
+ });
32813
+ const ranked = proposals.map((proposal) => {
32814
+ const score = (0, import_ai3.evaluateVariantProposal)(proposal, profile, audience, {
32815
+ intent: intentArg,
32816
+ baselineComponent: component
32817
+ });
32818
+ const { buildProps: _buildProps, ...proposalMeta } = proposal;
32819
+ return {
32820
+ proposal: proposalMeta,
32821
+ score,
32822
+ props: buildVariantProposalProps(proposal, profile, audience)
32823
+ };
32824
+ }).sort((a, b) => {
32825
+ if (b.score.fit !== a.score.fit) return b.score.fit - a.score.fit;
32826
+ if (a.score.risk !== b.score.risk) return a.score.risk - b.score.risk;
32827
+ return b.score.novelty - a.score.novelty;
32828
+ }).slice(0, maxResults ?? 8);
32829
+ const lines = [
32830
+ `${ranked.length} variant proposal${ranked.length === 1 ? "" : "s"} for ${component}${intentArg ? ` (intent: ${intentArg.join(", ")})` : ""}:`,
32831
+ ...fitReason ? [`Base chart fit warning: ${fitReason}`] : [],
32832
+ "",
32833
+ ...ranked.map((entry, i) => {
32834
+ const label = entry.proposal.label ?? entry.proposal.variantKey ?? entry.proposal.id;
32835
+ const tags = entry.proposal.tags?.length ? ` [${entry.proposal.tags.join(", ")}]` : "";
32836
+ const reasons = entry.score.reasons.length ? `
32837
+ ${entry.score.reasons.join("; ")}` : "";
32838
+ return `${i + 1}. ${entry.proposal.baseComponent} / ${label}${tags} (fit ${entry.score.fit.toFixed(1)}/5, novelty ${entry.score.novelty.toFixed(2)}, risk ${entry.score.risk.toFixed(2)})${reasons}`;
32839
+ })
32840
+ ];
32841
+ return {
32842
+ content: [{ type: "text", text: lines.join("\n") }],
32843
+ structuredContent: {
32844
+ component,
32845
+ profile: {
32846
+ rowCount: profile.rowCount,
32847
+ primary: profile.primary,
32848
+ categoryCount: profile.categoryCount ?? null,
32849
+ seriesCount: profile.seriesCount ?? null,
32850
+ hasHierarchy: profile.hasHierarchy,
32851
+ hasNetwork: profile.hasNetwork,
32852
+ hasGeo: profile.hasGeo
32853
+ },
32854
+ fitReason,
32855
+ proposals: ranked
32856
+ }
32857
+ };
32858
+ }
32746
32859
  async function suggestChartsHandler(args) {
32747
32860
  const { data, intent, maxResults, allow, deny, audience } = args;
32748
32861
  const intentArg = Array.isArray(intent) ? intent : intent ? [intent] : void 0;
@@ -32903,6 +33016,34 @@ Contextual instructions:
32903
33016
  }
32904
33017
  return { content, structuredContent: { summary, component, props } };
32905
33018
  }
33019
+ async function groundChartHandler(args) {
33020
+ const component = args.component;
33021
+ const props = args.props ?? {};
33022
+ if (!component) {
33023
+ return {
33024
+ content: [{ type: "text", text: "Missing 'component' field. Provide { component: 'LineChart', props: { ... } }." }],
33025
+ isError: true
33026
+ };
33027
+ }
33028
+ const capability = (0, import_ai3.getCapability)(component);
33029
+ const grounding = (0, import_ai3.buildReaderGrounding)(component, props, { capability });
33030
+ const nodeCount = grounding.structure ? (0, import_ai3.countNodes)(grounding.structure) : 0;
33031
+ const lines = [
33032
+ `Reader grounding for ${component} \u2014 the payload an agent reads to interpret this chart without seeing it:`,
33033
+ "",
33034
+ `L1\u2013L3 (description): ${grounding.description.text}`,
33035
+ grounding.intent ? `L4 (intent \xB7 ${grounding.intent.act}): ${grounding.intent.sentence}` : "L4 (intent): not resolved (no capability for this component).",
33036
+ "",
33037
+ `Structure: ${nodeCount} navigable node(s) (chart \u2192 axes/series \u2192 datum) in structuredContent.structure.`,
33038
+ "",
33039
+ "Combined text:",
33040
+ grounding.text
33041
+ ];
33042
+ return {
33043
+ content: [{ type: "text", text: lines.join("\n") }],
33044
+ structuredContent: grounding
33045
+ };
33046
+ }
32906
33047
  function createServer2() {
32907
33048
  const srv = new McpServer({
32908
33049
  name: "semiotic",
@@ -33069,6 +33210,18 @@ function createServer2() {
33069
33210
  },
33070
33211
  diagnoseConfigHandler
33071
33212
  );
33213
+ srv.tool(
33214
+ "auditAccessibility",
33215
+ "Audit a Semiotic chart configuration against the Chartability (POUR-CAF) accessibility framework \u2014 Perceivable, Operable, Understandable, Robust, Compromising, Assistive, Flexible. Statically grades the config (no DOM/AT): credits the built-ins every HOC ships (keyboard nav, focus ring, skip link, screen-reader data table, reduced-motion + forced-colors, shareable state), flags author-actionable gaps (missing title/description/summary, low contrast, small text, color-only encoding, undescribed trends, data density), and routes everything that needs real assistive-technology testing to a 'manual' item. Returns a per-principle report with the 14 critical heuristics marked. Pass inChartContainer=true to credit data-download/share affordances. Pair with manual NVDA/JAWS/VoiceOver testing \u2014 Chartability is not a pass/fail certification.",
33216
+ {
33217
+ component: external_exports3.string().describe("Chart component name, e.g. 'LineChart'"),
33218
+ props: external_exports3.record(external_exports3.string(), external_exports3.unknown()).optional().describe("Chart props object, e.g. { data: [...], xAccessor: 'x', title: '...' }."),
33219
+ inChartContainer: external_exports3.boolean().optional().describe("True if the chart is (or will be) wrapped in a ChartContainer exposing data-download/copy-config actions."),
33220
+ describe: external_exports3.boolean().optional().describe("True if ChartContainer's describe option (auto-generated L1\u2013L3 description via describeChart) is enabled \u2014 passes the 'features described' heuristic."),
33221
+ navigable: external_exports3.boolean().optional().describe("True if ChartContainer's navigable option (structured navigation tree via buildNavigationTree) is enabled \u2014 passes the 'navigable structure' heuristic.")
33222
+ },
33223
+ auditAccessibilityHandler
33224
+ );
33072
33225
  srv.tool(
33073
33226
  "reportIssue",
33074
33227
  "Generate a GitHub issue URL for Semiotic bug reports or feature requests. Returns a URL the user can open to submit. For rendering bugs, include the component name, props summary, and any diagnoseConfig output in the body.",
@@ -33097,6 +33250,15 @@ function createServer2() {
33097
33250
  },
33098
33251
  interrogateChartHandler
33099
33252
  );
33253
+ srv.tool(
33254
+ "groundChart",
33255
+ "Build the agent-reader grounding payload for a Semiotic chart: the layered L1\u2013L3 natural-language description, the L4 communicative-act sentence (what the chart is asking the reader to do \u2014 'this is an alerting chart; the spike warrants a closer look'), and a structured navigation tree (chart \u2192 axes/series \u2192 datum). This is the documented thing an LLM reads to interpret a chart faithfully without seeing the pixels \u2014 the reader-side complement to a capability descriptor. The L4 act is resolved from the chart's registered capability. Returns prose plus the full structured payload (description/intent/structure/text).",
33256
+ {
33257
+ component: external_exports3.string().describe("Chart component name, e.g. 'LineChart'"),
33258
+ props: external_exports3.record(external_exports3.string(), external_exports3.unknown()).describe("The full chart props including data")
33259
+ },
33260
+ groundChartHandler
33261
+ );
33100
33262
  srv.tool(
33101
33263
  "suggestStreamCharts",
33102
33264
  "Recommend realtime/streaming Semiotic charts for a schema (not row data). Pass a schema describing field types plus optional throughput ('low'|'medium'|'high') and retention ('windowed'|'cumulative') hints; the engine ranks realtime charts (RealtimeLineChart, RealtimeHistogram, RealtimeHeatmap, RealtimeWaterfallChart, RealtimeSwarmChart, TemporalHistogram) by their fit. Use when the user is wiring up a live dashboard or monitoring view rather than visualizing a bounded dataset.",
@@ -33144,8 +33306,9 @@ function createServer2() {
33144
33306
  reason: external_exports3.string().optional()
33145
33307
  })
33146
33308
  ).optional(),
33147
- exposureLevel: external_exports3.union([external_exports3.literal(0), external_exports3.literal(1), external_exports3.literal(2)]).optional()
33148
- }).describe("Audience profile \u2014 familiarity, targets, exposure level."),
33309
+ exposureLevel: external_exports3.union([external_exports3.literal(0), external_exports3.literal(1), external_exports3.literal(2)]).optional(),
33310
+ receptionModality: external_exports3.enum(["visual", "screen-reader", "sonified", "agent"]).optional().describe("Reception channel \u2014 see suggestCharts.")
33311
+ }).describe("Audience profile \u2014 familiarity, targets, exposure level, reception modality."),
33149
33312
  intent: external_exports3.union([external_exports3.string(), external_exports3.array(external_exports3.string())]).optional(),
33150
33313
  maxResults: external_exports3.number().int().min(1).max(20).optional()
33151
33314
  },
@@ -33162,6 +33325,32 @@ function createServer2() {
33162
33325
  },
33163
33326
  repairChartConfigHandler
33164
33327
  );
33328
+ srv.tool(
33329
+ "proposeChartVariants",
33330
+ "Propose and score chart variants for a selected Semiotic component. Uses the capability registry plus heuristic variant discovery: registered variants, conservative transforms, and same-intent cross-family alternatives. Returns ranked proposals with fit/novelty/risk scores, rationale, and ready-to-use props. Use after suggestCharts when an agent wants to actively explore variants rather than stop at the first chart recommendation.",
33331
+ {
33332
+ component: external_exports3.string().describe("Base chart component to vary, e.g. 'LineChart', 'BarChart', or 'BoxPlot'."),
33333
+ props: external_exports3.record(external_exports3.string(), external_exports3.unknown()).optional().describe("Existing chart props. If props.data is present it is profiled; network/hierarchy/geo object data can be passed here as raw input."),
33334
+ data: external_exports3.array(external_exports3.record(external_exports3.string(), external_exports3.unknown())).optional().describe("Row data to profile. Overrides props.data when present."),
33335
+ intent: external_exports3.union([external_exports3.string(), external_exports3.array(external_exports3.string())]).optional().describe("Ranking intent(s), e.g. trend, distribution, rank, compare-categories, composition-over-time."),
33336
+ maxResults: external_exports3.number().int().min(1).max(20).optional().describe("Cap on proposals returned (default 8)."),
33337
+ audience: external_exports3.object({
33338
+ name: external_exports3.string().optional(),
33339
+ familiarity: external_exports3.record(external_exports3.string(), external_exports3.number()).optional(),
33340
+ targets: external_exports3.record(
33341
+ external_exports3.string(),
33342
+ external_exports3.object({
33343
+ direction: external_exports3.enum(["increase", "decrease"]),
33344
+ weight: external_exports3.number().int().min(1).max(3).optional(),
33345
+ reason: external_exports3.string().optional()
33346
+ })
33347
+ ).optional(),
33348
+ exposureLevel: external_exports3.union([external_exports3.literal(0), external_exports3.literal(1), external_exports3.literal(2)]).optional(),
33349
+ receptionModality: external_exports3.enum(["visual", "screen-reader", "sonified", "agent"]).optional().describe("Reception channel \u2014 see suggestCharts.")
33350
+ }).optional().describe("Audience profile \u2014 familiarity, adoption targets, exposure level, and reception modality.")
33351
+ },
33352
+ proposeChartVariantsHandler
33353
+ );
33165
33354
  srv.tool(
33166
33355
  "suggestCharts",
33167
33356
  "Recommend Semiotic charts for a dataset using heuristic capability descriptors. Each chart declares which data shapes it serves and which intents (trend, compare-categories, distribution, correlation, part-to-whole, etc.) it answers \u2014 the engine returns a ranked list with scores, reasons, caveats, and ready-to-use props. Heuristic only; no LLM call. Use the result as structured context when answering 'what chart should I use?' or generating chart code.",
@@ -33170,7 +33359,21 @@ function createServer2() {
33170
33359
  intent: external_exports3.union([external_exports3.string(), external_exports3.array(external_exports3.string())]).optional().describe("Ranking intent. One of: trend, compare-series, compare-categories, rank, part-to-whole, distribution, correlation, flow, hierarchy, geo, outlier-detection, composition-over-time, change-detection. Custom intents accepted."),
33171
33360
  maxResults: external_exports3.number().int().min(1).max(40).optional().describe("Cap on suggestions returned (default 8)."),
33172
33361
  allow: external_exports3.array(external_exports3.string()).optional().describe("Restrict to these component names."),
33173
- deny: external_exports3.array(external_exports3.string()).optional().describe("Exclude these component names.")
33362
+ deny: external_exports3.array(external_exports3.string()).optional().describe("Exclude these component names."),
33363
+ audience: external_exports3.object({
33364
+ name: external_exports3.string().optional(),
33365
+ familiarity: external_exports3.record(external_exports3.string(), external_exports3.number()).optional(),
33366
+ targets: external_exports3.record(
33367
+ external_exports3.string(),
33368
+ external_exports3.object({
33369
+ direction: external_exports3.enum(["increase", "decrease"]),
33370
+ weight: external_exports3.number().int().min(1).max(3).optional(),
33371
+ reason: external_exports3.string().optional()
33372
+ })
33373
+ ).optional(),
33374
+ exposureLevel: external_exports3.union([external_exports3.literal(0), external_exports3.literal(1), external_exports3.literal(2)]).optional(),
33375
+ receptionModality: external_exports3.enum(["visual", "screen-reader", "sonified", "agent"]).optional().describe("Reception channel. A non-visual value down-ranks charts the audience can't receive in that channel (e.g. a many-slice pie for a screen reader) and adds receivability caveats.")
33376
+ }).optional().describe("Audience profile \u2014 familiarity, adoption targets, exposure level, and reception modality.")
33174
33377
  },
33175
33378
  suggestChartsHandler
33176
33379
  );
@@ -33220,7 +33423,7 @@ async function main() {
33220
33423
  });
33221
33424
  httpServer.listen(port, () => {
33222
33425
  console.error(`Semiotic MCP server (HTTP) listening on http://localhost:${port}`);
33223
- console.error("Tools: getSchema, suggestChart, suggestCharts, suggestStreamCharts, suggestDashboard, suggestStretchCharts, repairChartConfig, renderChart, interrogateChart, diagnoseConfig, reportIssue, applyTheme");
33426
+ console.error("Tools: getSchema, suggestChart, suggestCharts, proposeChartVariants, suggestStreamCharts, suggestDashboard, suggestStretchCharts, repairChartConfig, renderChart, interrogateChart, groundChart, diagnoseConfig, auditAccessibility, reportIssue, applyTheme");
33224
33427
  console.error("Resources: semiotic://schema, semiotic://components, semiotic://behavior-contracts, semiotic://system-prompt, semiotic://examples");
33225
33428
  });
33226
33429
  } else {
package/ai/examples.md CHANGED
@@ -1181,3 +1181,101 @@ import { BarChart } from "semiotic/ai"
1181
1181
  ```
1182
1182
 
1183
1183
  Key props: `y-threshold` works on vertical ordinal charts. `category-highlight` highlights a category column. `labelPosition` controls label placement.
1184
+
1185
+ ---
1186
+
1187
+ ## Value Charts — One Number Is The Visualization
1188
+
1189
+ ### BigNumber (KPI tile — comparison + target + threshold zones)
1190
+
1191
+ ```jsx
1192
+ import { BigNumber } from "semiotic/value"
1193
+
1194
+ <BigNumber
1195
+ value={1284900}
1196
+ label="Q3 Revenue"
1197
+ caption="Year-to-date bookings"
1198
+ format="currency"
1199
+ precision={0}
1200
+ comparison={{ value: 980000, label: "vs Q2" }}
1201
+ target={{ value: 1500000, label: "Q3 plan" }}
1202
+ thresholds={[
1203
+ { at: -Infinity, level: "danger" },
1204
+ { at: 1000000, level: "warning" },
1205
+ { at: 1300000, level: "success" },
1206
+ ]}
1207
+ />
1208
+ ```
1209
+
1210
+ 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.
1211
+
1212
+ ### BigNumber with a Semiotic chart embedded via `trendSlot` (wide / rectangular)
1213
+
1214
+ ```jsx
1215
+ import { BigNumber } from "semiotic/value"
1216
+ import { LineChart } from "semiotic/xy"
1217
+
1218
+ <BigNumber
1219
+ value={1284900}
1220
+ label="Q3 Revenue"
1221
+ format="currency"
1222
+ comparison={{ value: 980000, label: "vs Q2" }}
1223
+ trendSlot={(ctx) => (
1224
+ <LineChart
1225
+ data={[820000, 870000, 920000, 1010000, 1120000, 1284900].map((y, x) => ({ x, y }))}
1226
+ xAccessor="x"
1227
+ yAccessor="y"
1228
+ mode="sparkline"
1229
+ width={260}
1230
+ height={32}
1231
+ color={ctx.color}
1232
+ />
1233
+ )}
1234
+ />
1235
+ ```
1236
+
1237
+ Key props: BigNumber ships NO built-in chart renderer. `trendSlot` accepts any ReactNode (or `(ctx) => ReactNode`); pass a `LineChart`/`AreaChart` in `mode="sparkline"` for wide / rectangular charts. The slot context exposes `ctx.color` (resolved threshold colour) so the embedded chart picks up the BigNumber's level for cohesive theming.
1238
+
1239
+ ### BigNumber with a square Semiotic chart via `chartSlot`
1240
+
1241
+ ```jsx
1242
+ import { BigNumber } from "semiotic/value"
1243
+ import { DonutChart } from "semiotic/ordinal"
1244
+
1245
+ <BigNumber
1246
+ value={1284900}
1247
+ label="Q3 Revenue by region"
1248
+ format="currency"
1249
+ chartSlot={
1250
+ <DonutChart
1251
+ data={[
1252
+ { region: "NA", revenue: 540000 },
1253
+ { region: "EU", revenue: 420000 },
1254
+ { region: "APAC", revenue: 324900 },
1255
+ ]}
1256
+ categoryAccessor="region"
1257
+ valueAccessor="revenue"
1258
+ width={120}
1259
+ height={120}
1260
+ innerRadius={32}
1261
+ />
1262
+ }
1263
+ />
1264
+ ```
1265
+
1266
+ Key props: `chartSlot` is the square-aspect counterpart to `trendSlot`. The card splits horizontally — text content on the left, square chart on the right (DonutChart / PieChart / Scatterplot / Treemap / CirclePack). Pass both `trendSlot` and `chartSlot` to get the square chart on the right and the wide sparkline at the bottom.
1267
+
1268
+ ### BigNumber (inverted direction — lower is better)
1269
+
1270
+ ```jsx
1271
+ import { BigNumber } from "semiotic/value"
1272
+
1273
+ <BigNumber
1274
+ value={486}
1275
+ label="P99 latency"
1276
+ suffix=" ms"
1277
+ comparison={{ value: 410, label: "vs last week", direction: "lower-is-better" }}
1278
+ />
1279
+ ```
1280
+
1281
+ Key props: `direction: "lower-is-better"` flips sentiment colouring — a value that went UP now reads as negative (danger). Same pattern for error rate, churn, complaint count, etc.