semiotic 3.5.1 → 3.5.2

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 (92) hide show
  1. package/CLAUDE.md +21 -19
  2. package/README.md +22 -16
  3. package/ai/chartSuggestions.cjs +191 -3
  4. package/ai/componentMetadata.cjs +3 -3
  5. package/ai/dist/mcp-server.js +266 -48
  6. package/ai/examples.md +68 -0
  7. package/ai/schema.json +900 -1
  8. package/ai/system-prompt.md +4 -1
  9. package/dist/components/Tooltip/FlippingTooltip.d.ts +16 -1
  10. package/dist/components/charts/geo/FlowMap.d.ts +13 -4
  11. package/dist/components/charts/index.d.ts +6 -0
  12. package/dist/components/charts/network/OrbitDiagram.d.ts +5 -5
  13. package/dist/components/charts/network/ProcessSankey.d.ts +141 -0
  14. package/dist/components/charts/network/processSankey/algorithm.d.ts +136 -0
  15. package/dist/components/charts/network/processSankey/buildScenes.d.ts +45 -0
  16. package/dist/components/charts/network/processSankey/ribbonInputs.d.ts +32 -0
  17. package/dist/components/charts/network/processSankey/streamingLayout.d.ts +58 -0
  18. package/dist/components/charts/network/processSankey/tooltipUtils.d.ts +41 -0
  19. package/dist/components/charts/ordinal/BarChart.d.ts +12 -0
  20. package/dist/components/charts/ordinal/DotPlot.d.ts +9 -0
  21. package/dist/components/charts/ordinal/GaugeChart.d.ts +20 -0
  22. package/dist/components/charts/ordinal/SwimlaneChart.d.ts +5 -0
  23. package/dist/components/charts/realtime/RealtimeHistogram.d.ts +14 -11
  24. package/dist/components/charts/realtime/defaultRealtimeTooltip.d.ts +19 -0
  25. package/dist/components/charts/shared/axisExtent.d.ts +59 -0
  26. package/dist/components/charts/shared/chartSpecs.d.ts +75 -0
  27. package/dist/components/charts/shared/colorUtils.d.ts +8 -2
  28. package/dist/components/charts/shared/networkUtils.d.ts +3 -5
  29. package/dist/components/charts/shared/radialGeometry.d.ts +99 -0
  30. package/dist/components/charts/shared/regressionUtils.d.ts +59 -0
  31. package/dist/components/charts/shared/selectionUtils.d.ts +8 -1
  32. package/dist/components/charts/shared/streamPropsHelpers.d.ts +5 -0
  33. package/dist/components/charts/shared/types.d.ts +13 -0
  34. package/dist/components/charts/shared/useAreaSeriesSetup.d.ts +75 -0
  35. package/dist/components/charts/shared/useEncodingDomain.d.ts +48 -0
  36. package/dist/components/charts/shared/useFrameImperativeHandle.d.ts +1 -1
  37. package/dist/components/charts/shared/useNetworkChartSetup.d.ts +148 -0
  38. package/dist/components/charts/shared/useOrdinalPieceStyle.d.ts +87 -0
  39. package/dist/components/charts/shared/useSeriesFeatures.d.ts +57 -0
  40. package/dist/components/charts/shared/useStreamStatus.d.ts +33 -0
  41. package/dist/components/charts/shared/useXYLineStyle.d.ts +69 -0
  42. package/dist/components/charts/shared/useXYPointStyle.d.ts +87 -0
  43. package/dist/components/charts/xy/AreaChart.d.ts +24 -0
  44. package/dist/components/charts/xy/BubbleChart.d.ts +9 -0
  45. package/dist/components/charts/xy/ConnectedScatterplot.d.ts +16 -0
  46. package/dist/components/charts/xy/DifferenceChart.d.ts +172 -0
  47. package/dist/components/charts/xy/Scatterplot.d.ts +34 -0
  48. package/dist/components/geometry/ribbonGeometry.d.ts +76 -0
  49. package/dist/components/semiotic-ai.d.ts +2 -0
  50. package/dist/components/semiotic-network.d.ts +4 -0
  51. package/dist/components/semiotic-realtime.d.ts +2 -0
  52. package/dist/components/semiotic-utils.d.ts +4 -0
  53. package/dist/components/semiotic-xy.d.ts +2 -0
  54. package/dist/components/semiotic.d.ts +3 -3
  55. package/dist/components/server/serverChartConfigs.d.ts +2 -0
  56. package/dist/components/stream/GeoPipelineStore.d.ts +21 -0
  57. package/dist/components/stream/OrdinalSVGOverlay.d.ts +8 -0
  58. package/dist/components/stream/PipelineStore.d.ts +5 -0
  59. package/dist/components/stream/SVGOverlay.d.ts +18 -0
  60. package/dist/components/stream/annotationAccessorResolver.d.ts +39 -0
  61. package/dist/components/stream/geoTypes.d.ts +12 -0
  62. package/dist/components/stream/ordinalTypes.d.ts +12 -0
  63. package/dist/components/stream/renderers/cornerRadii.d.ts +33 -0
  64. package/dist/components/stream/types.d.ts +23 -0
  65. package/dist/components/types/legendTypes.d.ts +1 -1
  66. package/dist/geo.min.js +1 -1
  67. package/dist/geo.module.min.js +1 -1
  68. package/dist/network.min.js +1 -1
  69. package/dist/network.module.min.js +1 -1
  70. package/dist/ordinal.min.js +1 -1
  71. package/dist/ordinal.module.min.js +1 -1
  72. package/dist/realtime.min.js +1 -1
  73. package/dist/realtime.module.min.js +1 -1
  74. package/dist/semiotic-ai.d.ts +2 -0
  75. package/dist/semiotic-ai.min.js +1 -1
  76. package/dist/semiotic-ai.module.min.js +1 -1
  77. package/dist/semiotic-network.d.ts +4 -0
  78. package/dist/semiotic-realtime.d.ts +2 -0
  79. package/dist/semiotic-recipes.min.js +1 -1
  80. package/dist/semiotic-recipes.module.min.js +1 -1
  81. package/dist/semiotic-utils.d.ts +4 -0
  82. package/dist/semiotic-utils.min.js +1 -1
  83. package/dist/semiotic-utils.module.min.js +1 -1
  84. package/dist/semiotic-xy.d.ts +2 -0
  85. package/dist/semiotic.d.ts +3 -3
  86. package/dist/semiotic.min.js +1 -1
  87. package/dist/semiotic.module.min.js +1 -1
  88. package/dist/server.min.js +1 -1
  89. package/dist/server.module.min.js +1 -1
  90. package/dist/xy.min.js +1 -1
  91. package/dist/xy.module.min.js +1 -1
  92. package/package.json +10 -5
@@ -3106,6 +3106,9 @@ var require_utils = __commonJS({
3106
3106
  "use strict";
3107
3107
  var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
3108
3108
  var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
3109
+ var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
3110
+ var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
3111
+ var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
3109
3112
  function stringArrayToHexStripped(input) {
3110
3113
  let acc = "";
3111
3114
  let code = 0;
@@ -3298,27 +3301,77 @@ var require_utils = __commonJS({
3298
3301
  }
3299
3302
  return output.join("");
3300
3303
  }
3301
- function normalizeComponentEncoding(component, esc2) {
3302
- const func = esc2 !== true ? escape : unescape;
3303
- if (component.scheme !== void 0) {
3304
- component.scheme = func(component.scheme);
3305
- }
3306
- if (component.userinfo !== void 0) {
3307
- component.userinfo = func(component.userinfo);
3308
- }
3309
- if (component.host !== void 0) {
3310
- component.host = func(component.host);
3304
+ var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
3305
+ var HOST_DELIM_RE = /[@/?#:]/g;
3306
+ var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
3307
+ function reescapeHostDelimiters(host, isIP) {
3308
+ const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
3309
+ re.lastIndex = 0;
3310
+ return host.replace(re, (ch) => HOST_DELIMS[ch]);
3311
+ }
3312
+ function normalizePercentEncoding(input, decodeUnreserved = false) {
3313
+ if (input.indexOf("%") === -1) {
3314
+ return input;
3311
3315
  }
3312
- if (component.path !== void 0) {
3313
- component.path = func(component.path);
3316
+ let output = "";
3317
+ for (let i = 0; i < input.length; i++) {
3318
+ if (input[i] === "%" && i + 2 < input.length) {
3319
+ const hex3 = input.slice(i + 1, i + 3);
3320
+ if (isHexPair(hex3)) {
3321
+ const normalizedHex = hex3.toUpperCase();
3322
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
3323
+ if (decodeUnreserved && isUnreserved(decoded)) {
3324
+ output += decoded;
3325
+ } else {
3326
+ output += "%" + normalizedHex;
3327
+ }
3328
+ i += 2;
3329
+ continue;
3330
+ }
3331
+ }
3332
+ output += input[i];
3314
3333
  }
3315
- if (component.query !== void 0) {
3316
- component.query = func(component.query);
3334
+ return output;
3335
+ }
3336
+ function normalizePathEncoding(input) {
3337
+ let output = "";
3338
+ for (let i = 0; i < input.length; i++) {
3339
+ if (input[i] === "%" && i + 2 < input.length) {
3340
+ const hex3 = input.slice(i + 1, i + 3);
3341
+ if (isHexPair(hex3)) {
3342
+ const normalizedHex = hex3.toUpperCase();
3343
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
3344
+ if (decoded !== "." && isUnreserved(decoded)) {
3345
+ output += decoded;
3346
+ } else {
3347
+ output += "%" + normalizedHex;
3348
+ }
3349
+ i += 2;
3350
+ continue;
3351
+ }
3352
+ }
3353
+ if (isPathCharacter(input[i])) {
3354
+ output += input[i];
3355
+ } else {
3356
+ output += escape(input[i]);
3357
+ }
3317
3358
  }
3318
- if (component.fragment !== void 0) {
3319
- component.fragment = func(component.fragment);
3359
+ return output;
3360
+ }
3361
+ function escapePreservingEscapes(input) {
3362
+ let output = "";
3363
+ for (let i = 0; i < input.length; i++) {
3364
+ if (input[i] === "%" && i + 2 < input.length) {
3365
+ const hex3 = input.slice(i + 1, i + 3);
3366
+ if (isHexPair(hex3)) {
3367
+ output += "%" + hex3.toUpperCase();
3368
+ i += 2;
3369
+ continue;
3370
+ }
3371
+ }
3372
+ output += escape(input[i]);
3320
3373
  }
3321
- return component;
3374
+ return output;
3322
3375
  }
3323
3376
  function recomposeAuthority(component) {
3324
3377
  const uriTokens = [];
@@ -3333,7 +3386,7 @@ var require_utils = __commonJS({
3333
3386
  if (ipV6res.isIPV6 === true) {
3334
3387
  host = `[${ipV6res.escapedHost}]`;
3335
3388
  } else {
3336
- host = component.host;
3389
+ host = reescapeHostDelimiters(host, false);
3337
3390
  }
3338
3391
  }
3339
3392
  uriTokens.push(host);
@@ -3347,7 +3400,10 @@ var require_utils = __commonJS({
3347
3400
  module2.exports = {
3348
3401
  nonSimpleDomain,
3349
3402
  recomposeAuthority,
3350
- normalizeComponentEncoding,
3403
+ reescapeHostDelimiters,
3404
+ normalizePercentEncoding,
3405
+ normalizePathEncoding,
3406
+ escapePreservingEscapes,
3351
3407
  removeDotSegments,
3352
3408
  isIPv4,
3353
3409
  isUUID,
@@ -3571,12 +3627,12 @@ var require_schemes = __commonJS({
3571
3627
  var require_fast_uri = __commonJS({
3572
3628
  "node_modules/fast-uri/index.js"(exports2, module2) {
3573
3629
  "use strict";
3574
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
3630
+ var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3575
3631
  var { SCHEMES, getSchemeHandler } = require_schemes();
3576
3632
  function normalize(uri, options) {
3577
3633
  if (typeof uri === "string") {
3578
3634
  uri = /** @type {T} */
3579
- serialize(parse3(uri, options), options);
3635
+ normalizeString(uri, options);
3580
3636
  } else if (typeof uri === "object") {
3581
3637
  uri = /** @type {T} */
3582
3638
  parse3(serialize(uri, options), options);
@@ -3643,19 +3699,9 @@ var require_fast_uri = __commonJS({
3643
3699
  return target;
3644
3700
  }
3645
3701
  function equal(uriA, uriB, options) {
3646
- if (typeof uriA === "string") {
3647
- uriA = unescape(uriA);
3648
- uriA = serialize(normalizeComponentEncoding(parse3(uriA, options), true), { ...options, skipEscape: true });
3649
- } else if (typeof uriA === "object") {
3650
- uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true });
3651
- }
3652
- if (typeof uriB === "string") {
3653
- uriB = unescape(uriB);
3654
- uriB = serialize(normalizeComponentEncoding(parse3(uriB, options), true), { ...options, skipEscape: true });
3655
- } else if (typeof uriB === "object") {
3656
- uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true });
3657
- }
3658
- return uriA.toLowerCase() === uriB.toLowerCase();
3702
+ const normalizedA = normalizeComparableURI(uriA, options);
3703
+ const normalizedB = normalizeComparableURI(uriB, options);
3704
+ return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA.toLowerCase() === normalizedB.toLowerCase();
3659
3705
  }
3660
3706
  function serialize(cmpts, opts) {
3661
3707
  const component = {
@@ -3680,12 +3726,12 @@ var require_fast_uri = __commonJS({
3680
3726
  if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
3681
3727
  if (component.path !== void 0) {
3682
3728
  if (!options.skipEscape) {
3683
- component.path = escape(component.path);
3729
+ component.path = escapePreservingEscapes(component.path);
3684
3730
  if (component.scheme !== void 0) {
3685
3731
  component.path = component.path.split("%3A").join(":");
3686
3732
  }
3687
3733
  } else {
3688
- component.path = unescape(component.path);
3734
+ component.path = normalizePercentEncoding(component.path);
3689
3735
  }
3690
3736
  }
3691
3737
  if (options.reference !== "suffix" && component.scheme) {
@@ -3720,7 +3766,16 @@ var require_fast_uri = __commonJS({
3720
3766
  return uriTokens.join("");
3721
3767
  }
3722
3768
  var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
3723
- function parse3(uri, opts) {
3769
+ function getParseError(parsed, matches) {
3770
+ if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
3771
+ return 'URI path must start with "/" when authority is present.';
3772
+ }
3773
+ if (typeof parsed.port === "number" && (parsed.port < 0 || parsed.port > 65535)) {
3774
+ return "URI port is malformed.";
3775
+ }
3776
+ return void 0;
3777
+ }
3778
+ function parseWithStatus(uri, opts) {
3724
3779
  const options = Object.assign({}, opts);
3725
3780
  const parsed = {
3726
3781
  scheme: void 0,
@@ -3731,6 +3786,7 @@ var require_fast_uri = __commonJS({
3731
3786
  query: void 0,
3732
3787
  fragment: void 0
3733
3788
  };
3789
+ let malformedAuthorityOrPort = false;
3734
3790
  let isIP = false;
3735
3791
  if (options.reference === "suffix") {
3736
3792
  if (options.scheme) {
@@ -3751,6 +3807,11 @@ var require_fast_uri = __commonJS({
3751
3807
  if (isNaN(parsed.port)) {
3752
3808
  parsed.port = matches[5];
3753
3809
  }
3810
+ const parseError = getParseError(parsed, matches);
3811
+ if (parseError !== void 0) {
3812
+ parsed.error = parsed.error || parseError;
3813
+ malformedAuthorityOrPort = true;
3814
+ }
3754
3815
  if (parsed.host) {
3755
3816
  const ipv4result = isIPv4(parsed.host);
3756
3817
  if (ipv4result === false) {
@@ -3789,14 +3850,18 @@ var require_fast_uri = __commonJS({
3789
3850
  parsed.scheme = unescape(parsed.scheme);
3790
3851
  }
3791
3852
  if (parsed.host !== void 0) {
3792
- parsed.host = unescape(parsed.host);
3853
+ parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
3793
3854
  }
3794
3855
  }
3795
3856
  if (parsed.path) {
3796
- parsed.path = escape(unescape(parsed.path));
3857
+ parsed.path = normalizePathEncoding(parsed.path);
3797
3858
  }
3798
3859
  if (parsed.fragment) {
3799
- parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
3860
+ try {
3861
+ parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
3862
+ } catch {
3863
+ parsed.error = parsed.error || "URI malformed";
3864
+ }
3800
3865
  }
3801
3866
  }
3802
3867
  if (schemeHandler && schemeHandler.parse) {
@@ -3805,7 +3870,29 @@ var require_fast_uri = __commonJS({
3805
3870
  } else {
3806
3871
  parsed.error = parsed.error || "URI can not be parsed.";
3807
3872
  }
3808
- return parsed;
3873
+ return { parsed, malformedAuthorityOrPort };
3874
+ }
3875
+ function parse3(uri, opts) {
3876
+ return parseWithStatus(uri, opts).parsed;
3877
+ }
3878
+ function normalizeString(uri, opts) {
3879
+ return normalizeStringWithStatus(uri, opts).normalized;
3880
+ }
3881
+ function normalizeStringWithStatus(uri, opts) {
3882
+ const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
3883
+ return {
3884
+ normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
3885
+ malformedAuthorityOrPort
3886
+ };
3887
+ }
3888
+ function normalizeComparableURI(uri, opts) {
3889
+ if (typeof uri === "string") {
3890
+ const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
3891
+ return malformedAuthorityOrPort ? void 0 : normalized;
3892
+ }
3893
+ if (typeof uri === "object") {
3894
+ return serialize(uri, opts);
3895
+ }
3809
3896
  }
3810
3897
  var fastUri = {
3811
3898
  SCHEMES,
@@ -6808,6 +6895,7 @@ var require_componentMetadata = __commonJS({
6808
6895
  xy: [
6809
6896
  "LineChart",
6810
6897
  "AreaChart",
6898
+ "DifferenceChart",
6811
6899
  "StackedAreaChart",
6812
6900
  "Scatterplot",
6813
6901
  "QuadrantChart",
@@ -6839,6 +6927,7 @@ var require_componentMetadata = __commonJS({
6839
6927
  network: [
6840
6928
  "ForceDirectedGraph",
6841
6929
  "SankeyDiagram",
6930
+ "ProcessSankey",
6842
6931
  "ChordDiagram",
6843
6932
  "TreeDiagram",
6844
6933
  "Treemap",
@@ -6934,6 +7023,7 @@ var require_componentMetadata = __commonJS({
6934
7023
  var require_chartSuggestions = __commonJS({
6935
7024
  "ai/chartSuggestions.cjs"(exports2, module2) {
6936
7025
  "use strict";
7026
+ var path2 = require("path");
6937
7027
  var VALID_INTENTS = [
6938
7028
  "comparison",
6939
7029
  "trend",
@@ -6945,6 +7035,69 @@ var require_chartSuggestions = __commonJS({
6945
7035
  "hierarchy"
6946
7036
  ];
6947
7037
  var MAX_SAMPLE_SIZE = 5;
7038
+ var _capabilityMatrix = null;
7039
+ var _capabilityMatrixLoaded = false;
7040
+ function loadCapabilityMatrix() {
7041
+ if (_capabilityMatrixLoaded) return _capabilityMatrix;
7042
+ const candidates = [
7043
+ path2.join(__dirname, "capabilities.json"),
7044
+ path2.join(__dirname, "..", "capabilities.json")
7045
+ ];
7046
+ for (const candidate of candidates) {
7047
+ try {
7048
+ const json2 = require(candidate);
7049
+ if (json2 && json2.charts) {
7050
+ _capabilityMatrix = json2.charts;
7051
+ _capabilityMatrixLoaded = true;
7052
+ return _capabilityMatrix;
7053
+ }
7054
+ } catch {
7055
+ }
7056
+ }
7057
+ _capabilityMatrixLoaded = true;
7058
+ return _capabilityMatrix;
7059
+ }
7060
+ var CAPABILITY_KEY_MAP = {
7061
+ push: "supportsPush",
7062
+ linkedHover: "supportsLinkedHover",
7063
+ ssr: "supportsSSR",
7064
+ selection: "supportsSelection",
7065
+ legend: "supportsLegend"
7066
+ };
7067
+ var VALID_CAPABILITY_KEYS = Object.keys(CAPABILITY_KEY_MAP);
7068
+ function chartSatisfiesCapabilities(chartName, requirements) {
7069
+ if (!requirements || Object.keys(requirements).length === 0) return true;
7070
+ const matrix = loadCapabilityMatrix();
7071
+ if (matrix == null) return false;
7072
+ const spec = matrix[chartName];
7073
+ if (!spec) return false;
7074
+ for (const [shortKey, want] of Object.entries(requirements)) {
7075
+ if (want == null) continue;
7076
+ const matrixKey = CAPABILITY_KEY_MAP[shortKey];
7077
+ if (!matrixKey) continue;
7078
+ const has = spec[matrixKey] === true;
7079
+ if (has !== want) return false;
7080
+ }
7081
+ return true;
7082
+ }
7083
+ function explainCapabilityMismatch(chartName, requirements) {
7084
+ if (!requirements || Object.keys(requirements).length === 0) return null;
7085
+ const matrix = loadCapabilityMatrix();
7086
+ if (matrix == null) return "capability matrix unavailable (run `npm run docs:capabilities`)";
7087
+ const spec = matrix[chartName];
7088
+ if (!spec) return `${chartName} not found in capability matrix`;
7089
+ const mismatches = [];
7090
+ for (const [shortKey, want] of Object.entries(requirements)) {
7091
+ if (want == null) continue;
7092
+ const matrixKey = CAPABILITY_KEY_MAP[shortKey];
7093
+ if (!matrixKey) continue;
7094
+ const has = spec[matrixKey] === true;
7095
+ if (has !== want) {
7096
+ mismatches.push(`requires ${shortKey}=${want} but ${matrixKey}=${has}`);
7097
+ }
7098
+ }
7099
+ return mismatches.length > 0 ? mismatches.join("; ") : null;
7100
+ }
6948
7101
  function summarizeFields(data, keys) {
6949
7102
  const numericFields = [];
6950
7103
  const stringFields = [];
@@ -7003,16 +7156,38 @@ var require_chartSuggestions = __commonJS({
7003
7156
  function suggestCharts2(args = {}) {
7004
7157
  const data = args.data;
7005
7158
  const intent = args.intent;
7159
+ const capabilities = args.capabilities;
7006
7160
  if (intent && !VALID_INTENTS.includes(intent)) {
7007
7161
  return {
7008
7162
  ok: false,
7009
7163
  error: `Unknown intent "${intent}". Expected one of: ${VALID_INTENTS.join(", ")}.`
7010
7164
  };
7011
7165
  }
7166
+ if (capabilities) {
7167
+ if (typeof capabilities !== "object" || Array.isArray(capabilities)) {
7168
+ return {
7169
+ ok: false,
7170
+ error: "capabilities must be an object like { push: true, linkedHover: true, ssr: true, selection: true, legend: true }."
7171
+ };
7172
+ }
7173
+ const unknown2 = Object.keys(capabilities).filter((k) => !VALID_CAPABILITY_KEYS.includes(k));
7174
+ if (unknown2.length > 0) {
7175
+ return {
7176
+ ok: false,
7177
+ error: `Unknown capability key(s): ${unknown2.join(", ")}. Expected: ${VALID_CAPABILITY_KEYS.join(", ")}.`
7178
+ };
7179
+ }
7180
+ if (loadCapabilityMatrix() == null) {
7181
+ return {
7182
+ ok: false,
7183
+ error: "Capability matrix unavailable: ai/capabilities.json is missing. Run `npm run docs:capabilities` to generate it. (Capability filtering requires the matrix; suggestions without a `capabilities` arg still work.)"
7184
+ };
7185
+ }
7186
+ }
7012
7187
  if (!data || !Array.isArray(data) || data.length === 0) {
7013
7188
  return {
7014
7189
  ok: false,
7015
- error: "Pass { data: [{ ... }, ...] } with 1-5 sample data objects. Optionally include intent: 'comparison' | 'trend' | 'distribution' | 'relationship' | 'composition' | 'geographic' | 'network' | 'hierarchy'."
7190
+ error: "Pass { data: [{ ... }, ...] } with 1-5 sample data objects. Optionally include intent: 'comparison' | 'trend' | 'distribution' | 'relationship' | 'composition' | 'geographic' | 'network' | 'hierarchy', or capabilities: { push, linkedHover, ssr, selection, legend }."
7016
7191
  };
7017
7192
  }
7018
7193
  if (data.length > MAX_SAMPLE_SIZE) {
@@ -7169,22 +7344,44 @@ var require_chartSuggestions = __commonJS({
7169
7344
  props: { data: jsxExpression("data"), xAccessor: jsxString(xField), yAccessor: jsxString(yField), valueAccessor: jsxString(valueField) }
7170
7345
  });
7171
7346
  }
7347
+ const filteredSuggestions = capabilities ? suggestions.filter((s) => chartSatisfiesCapabilities(s.component, capabilities)) : suggestions;
7172
7348
  return {
7173
7349
  ok: true,
7174
7350
  intent,
7351
+ capabilities,
7175
7352
  fieldSummary: `Fields: ${keys.join(", ")} (${numericFields.length} numeric, ${stringFields.length} categorical, ${dateFields.length} date)`,
7176
7353
  fields,
7177
- suggestions
7354
+ suggestions: filteredSuggestions,
7355
+ // Surface the pre-filter set when a capability constraint was
7356
+ // applied — caller can see which suggestions were dropped and
7357
+ // whether to relax the constraint.
7358
+ ...capabilities && filteredSuggestions.length < suggestions.length && {
7359
+ filteredOut: suggestions.filter((s) => !chartSatisfiesCapabilities(s.component, capabilities)).map((s) => ({
7360
+ component: s.component,
7361
+ // The `reason` here is the capability mismatch (which
7362
+ // constraint failed), not the original data-shape rationale
7363
+ // — callers debugging an empty result need to know which
7364
+ // capability to relax, not why the chart was originally
7365
+ // suggested.
7366
+ reason: explainCapabilityMismatch(s.component, capabilities) || "did not satisfy capability constraints"
7367
+ }))
7368
+ }
7178
7369
  };
7179
7370
  }
7180
7371
  function formatSuggestionReport2(result) {
7181
7372
  if (!result.ok) return result.error;
7182
7373
  if (result.suggestions.length === 0) {
7183
- return `Could not confidently recommend a chart type.
7374
+ const tail = result.capabilities && result.filteredOut && result.filteredOut.length > 0 ? `
7375
+
7376
+ Dropped by capability filter (${formatCapabilityConstraints(result.capabilities)}):
7377
+ ${result.filteredOut.map((s) => `- ${s.component}: ${s.reason}`).join("\n")}
7184
7378
 
7185
- ${result.fieldSummary}
7379
+ Relax the capability constraints, or use getSchema to browse alternatives.` : `
7186
7380
 
7187
7381
  Try providing intent ('${VALID_INTENTS.join("', '")}') to narrow recommendations, or use getSchema to browse available components.`;
7382
+ return `Could not confidently recommend a chart type.
7383
+
7384
+ ${result.fieldSummary}${tail}`;
7188
7385
  }
7189
7386
  const lines = result.suggestions.map((suggestion, i) => {
7190
7387
  const propsStr = Object.entries(suggestion.props).map(([k, v]) => `${k}=${v}`).join(" ");
@@ -7216,10 +7413,18 @@ Or use \`<ThemeProvider theme="dark">\` / \`<ThemeProvider theme={{ colors: {...
7216
7413
  For accessibility, use \`colorScheme={COLOR_BLIND_SAFE_CATEGORICAL}\` (import from \`semiotic/themes\`) - 8-color palette safe for all forms of color blindness.`;
7217
7414
  return lines.join("\n\n") + themingTip;
7218
7415
  }
7416
+ function formatCapabilityConstraints(capabilities) {
7417
+ return Object.entries(capabilities).filter(([, v]) => v != null).map(([k, v]) => `${k}=${v}`).join(", ");
7418
+ }
7219
7419
  module2.exports = {
7220
7420
  VALID_INTENTS,
7421
+ VALID_CAPABILITY_KEYS,
7221
7422
  formatSuggestionReport: formatSuggestionReport2,
7222
- suggestCharts: suggestCharts2
7423
+ suggestCharts: suggestCharts2,
7424
+ // Exported for tests + callers that want to filter their own
7425
+ // chart-name set without re-running the suggestion pipeline.
7426
+ chartSatisfiesCapabilities,
7427
+ explainCapabilityMismatch
7223
7428
  };
7224
7429
  }
7225
7430
  });
@@ -32147,6 +32352,7 @@ var import_geo = require("semiotic/geo");
32147
32352
  var COMPONENT_REGISTRY = {
32148
32353
  LineChart: { component: import_ai.LineChart, category: "xy" },
32149
32354
  AreaChart: { component: import_ai.AreaChart, category: "xy" },
32355
+ DifferenceChart: { component: import_ai.DifferenceChart, category: "xy" },
32150
32356
  StackedAreaChart: { component: import_ai.StackedAreaChart, category: "xy" },
32151
32357
  Scatterplot: { component: import_ai.Scatterplot, category: "xy" },
32152
32358
  BubbleChart: { component: import_ai.BubbleChart, category: "xy" },
@@ -32175,6 +32381,7 @@ var COMPONENT_REGISTRY = {
32175
32381
  ForceDirectedGraph: { component: import_ai.ForceDirectedGraph, category: "network" },
32176
32382
  ChordDiagram: { component: import_ai.ChordDiagram, category: "network" },
32177
32383
  SankeyDiagram: { component: import_ai.SankeyDiagram, category: "network" },
32384
+ ProcessSankey: { component: import_ai.ProcessSankey, category: "network" },
32178
32385
  TreeDiagram: { component: import_ai.TreeDiagram, category: "network" },
32179
32386
  Treemap: { component: import_ai.Treemap, category: "network" },
32180
32387
  CirclePack: { component: import_ai.CirclePack, category: "network" },
@@ -32660,10 +32867,21 @@ function createServer2() {
32660
32867
  );
32661
32868
  srv.tool(
32662
32869
  "suggestChart",
32663
- "Recommend Semiotic chart types for a given data sample. Pass { data: [...] } with 1-5 sample objects. Optionally pass intent to narrow suggestions. Returns ranked recommendations with example props.",
32870
+ "Recommend Semiotic chart types for a given data sample. Pass { data: [...] } with 1-5 sample objects. Optionally pass intent to narrow suggestions, or capabilities to require/forbid features (push API, linked hover, SSR, selection, legend). Returns ranked recommendations with example props; charts that don't satisfy the capability constraints are dropped.",
32664
32871
  {
32665
32872
  data: external_exports3.array(external_exports3.record(external_exports3.string(), external_exports3.unknown())).min(1).max(5).describe("1-5 sample data objects"),
32666
- intent: external_exports3.enum(["comparison", "trend", "distribution", "relationship", "composition", "geographic", "network", "hierarchy"]).optional().describe("Visualization intent to narrow suggestions")
32873
+ intent: external_exports3.enum(["comparison", "trend", "distribution", "relationship", "composition", "geographic", "network", "hierarchy"]).optional().describe("Visualization intent to narrow suggestions"),
32874
+ capabilities: external_exports3.object({
32875
+ push: external_exports3.boolean().optional().describe("Require ref-based push API (live streaming via ref.current.push())"),
32876
+ linkedHover: external_exports3.boolean().optional().describe("Require cross-chart linked hover support"),
32877
+ ssr: external_exports3.boolean().optional().describe("Require server-side rendering via renderChart()"),
32878
+ selection: external_exports3.boolean().optional().describe("Require named selection / cross-filter support"),
32879
+ legend: external_exports3.boolean().optional().describe("Require a top-level legend")
32880
+ // `.strict()` so the MCP surface rejects unknown capability
32881
+ // keys at the schema layer rather than silently stripping
32882
+ // them — keeps the cjs-level "Unknown capability key(s)"
32883
+ // validation from being unreachable from MCP callers.
32884
+ }).strict().optional().describe("Capability constraints \u2014 set a key to true to require, false to forbid. Unset keys are ignored.")
32667
32885
  },
32668
32886
  suggestChartHandler
32669
32887
  );
package/ai/examples.md CHANGED
@@ -199,6 +199,42 @@ const data = [
199
199
 
200
200
  Key props: `y0Accessor` defines band bottom, `yAccessor` defines band top, `showLine={false}` hides the top edge stroke. Layer a `LineChart` on top for the main metric.
201
201
 
202
+ ### DifferenceChart
203
+
204
+ ```jsx
205
+ import { DifferenceChart } from "semiotic/ai"
206
+
207
+ const tempData = [
208
+ { month: 1, actual: 38, normal: 32 },
209
+ { month: 2, actual: 41, normal: 36 },
210
+ { month: 3, actual: 45, normal: 48 },
211
+ { month: 4, actual: 55, normal: 57 },
212
+ { month: 5, actual: 61, normal: 66 },
213
+ { month: 6, actual: 70, normal: 75 },
214
+ { month: 7, actual: 79, normal: 79 },
215
+ { month: 8, actual: 81, normal: 78 },
216
+ { month: 9, actual: 74, normal: 70 },
217
+ { month: 10, actual: 64, normal: 58 },
218
+ { month: 11, actual: 52, normal: 47 },
219
+ { month: 12, actual: 42, normal: 37 }
220
+ ]
221
+
222
+ <DifferenceChart
223
+ data={tempData}
224
+ xAccessor="month"
225
+ seriesAAccessor="actual"
226
+ seriesBAccessor="normal"
227
+ seriesALabel="Actual"
228
+ seriesBLabel="Normal"
229
+ xLabel="Month"
230
+ yLabel="°F"
231
+ />
232
+ ```
233
+
234
+ Fills the region between two series with a color that switches based on which series is higher at each x — `seriesAColor` where A > B, `seriesBColor` where B > A. Crossover x-values are linearly interpolated so adjacent segments meet at zero-width vertices (no jagged seams). Classic uses: temperature anomaly, forecast vs. actual, budget variance, any A/B comparison.
235
+
236
+ Key props: `seriesALabel` / `seriesBLabel` (legend + tooltip), `seriesAColor` / `seriesBColor` (defaults to `var(--semiotic-danger)` / `var(--semiotic-info)`), `showLines` (default `true` — draws both series on top of the fill), `areaOpacity` (0.6), `gradientFill` (same shape as AreaChart), `windowSize` (max raw rows in push buffer; FIFO eviction). Push API: `ref.current.push({ x, a, b })` — accessor outputs coerce through a `toNumber` helper so `Date` (time series) and numeric strings (CSV/JSON) work transparently.
237
+
202
238
  ### StackedAreaChart
203
239
 
204
240
  ```jsx
@@ -661,6 +697,38 @@ const edges = [
661
697
 
662
698
  Key props: **`edges`** (required, nodes inferred), `valueAccessor` controls band width
663
699
 
700
+ ### ProcessSankey
701
+
702
+ ```jsx
703
+ import { ProcessSankey } from "semiotic/ai"
704
+
705
+ const nodes = [
706
+ { id: "Alice", category: "Person", xExtent: ["2026-01-06", "2026-01-06"] },
707
+ { id: "Bob", category: "Person", xExtent: ["2026-02-01", "2026-02-01"] },
708
+ { id: "Eng", category: "Team" },
709
+ { id: "Release", category: "Milestone", xExtent: ["2026-04-15", "2026-05-30"] },
710
+ ]
711
+
712
+ const edges = [
713
+ { id: "alice-eng", source: "Alice", target: "Eng", value: 8,
714
+ startTime: "2026-01-20", endTime: "2026-02-10" },
715
+ { id: "bob-eng", source: "Bob", target: "Eng", value: 5,
716
+ startTime: "2026-02-15", endTime: "2026-03-15" },
717
+ { id: "eng-rel", source: "Eng", target: "Release", value: 13,
718
+ startTime: "2026-04-15", endTime: "2026-05-15" },
719
+ ]
720
+
721
+ <ProcessSankey
722
+ nodes={nodes}
723
+ edges={edges}
724
+ domain={["2026-01-01", "2026-05-31"]}
725
+ colorBy="category"
726
+ showLegend
727
+ />
728
+ ```
729
+
730
+ Key props: **`edges`** with `startTime`/`endTime`, **`domain`** (required `[t0, t1]`); nodes optionally carry `xExtent: [start, end]` to bound the lane (`min(xExtent[0], earliestEdge)` to `max(xExtent[1], latestEdge)`). Use when flow events have timestamps; use SankeyDiagram for static total-flow snapshots.
731
+
664
732
  ### ChordDiagram
665
733
 
666
734
  ```jsx