semiotic 3.6.0 → 3.7.1

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 (122) hide show
  1. package/CLAUDE.md +192 -228
  2. package/README.md +70 -17
  3. package/ai/cli.js +41 -0
  4. package/ai/componentMetadata.cjs +11 -2
  5. package/ai/dist/mcp-server.js +625 -10
  6. package/ai/examples.md +98 -0
  7. package/ai/schema.json +581 -1
  8. package/ai/system-prompt.md +7 -4
  9. package/dist/components/AccessibleNavTree.d.ts +26 -0
  10. package/dist/components/Annotation.d.ts +41 -15
  11. package/dist/components/CategoryColors.d.ts +1 -1
  12. package/dist/components/ChartContainer.d.ts +32 -2
  13. package/dist/components/ChartGrid.d.ts +1 -1
  14. package/dist/components/ContextLayout.d.ts +1 -1
  15. package/dist/components/DataSummaryContext.d.ts +1 -1
  16. package/dist/components/DetailsPanel.d.ts +1 -1
  17. package/dist/components/Legend.d.ts +3 -2
  18. package/dist/components/LinkedCharts.d.ts +1 -1
  19. package/dist/components/ThemeProvider.d.ts +1 -1
  20. package/dist/components/Tooltip/FlippingTooltip.d.ts +1 -1
  21. package/dist/components/Tooltip/Tooltip.d.ts +2 -2
  22. package/dist/components/ai/annotationProvenance.d.ts +349 -0
  23. package/dist/components/ai/audienceProfile.d.ts +60 -3
  24. package/dist/components/ai/chartCapabilityTypes.d.ts +60 -2
  25. package/dist/components/ai/chartRoles.d.ts +27 -0
  26. package/dist/components/ai/conversationArc.d.ts +379 -0
  27. package/dist/components/ai/dataScaleProfile.d.ts +320 -0
  28. package/dist/components/ai/describeChart.d.ts +114 -0
  29. package/dist/components/ai/navigationTree.d.ts +45 -0
  30. package/dist/components/ai/qualityScorecard.d.ts +11 -0
  31. package/dist/components/ai/readerGrounding.d.ts +70 -0
  32. package/dist/components/ai/suggestCharts.d.ts +34 -1
  33. package/dist/components/ai/useConversationArc.d.ts +89 -0
  34. package/dist/components/ai/useNavigationSync.d.ts +61 -0
  35. package/dist/components/ai/variantDiscovery.d.ts +168 -0
  36. package/dist/components/charts/geo/ChoroplethMap.d.ts +2 -1
  37. package/dist/components/charts/network/CirclePack.d.ts +2 -1
  38. package/dist/components/charts/network/OrbitDiagram.d.ts +1 -1
  39. package/dist/components/charts/network/TreeDiagram.d.ts +2 -1
  40. package/dist/components/charts/network/Treemap.d.ts +2 -1
  41. package/dist/components/charts/realtime/RealtimeHeatmap.d.ts +3 -0
  42. package/dist/components/charts/realtime/RealtimeHistogram.d.ts +4 -1
  43. package/dist/components/charts/realtime/RealtimeLineChart.d.ts +3 -0
  44. package/dist/components/charts/realtime/RealtimeSwarmChart.d.ts +3 -0
  45. package/dist/components/charts/realtime/RealtimeWaterfallChart.d.ts +3 -0
  46. package/dist/components/charts/shared/ChartError.d.ts +2 -1
  47. package/dist/components/charts/shared/annotationHierarchy.d.ts +42 -0
  48. package/dist/components/charts/shared/annotationResolvers.d.ts +3 -2
  49. package/dist/components/charts/shared/annotationRules.d.ts +16 -0
  50. package/dist/components/charts/shared/annotationTypes.d.ts +14 -0
  51. package/dist/components/charts/shared/auditAccessibility.d.ts +90 -0
  52. package/dist/components/charts/shared/chartSpecs.d.ts +2 -3
  53. package/dist/components/charts/shared/diagnoseConfig.d.ts +4 -6
  54. package/dist/components/charts/shared/selectionUtils.d.ts +5 -2
  55. package/dist/components/charts/shared/streamPropsHelpers.d.ts +2 -0
  56. package/dist/components/charts/shared/types.d.ts +5 -1
  57. package/dist/components/charts/shared/withChartWrapper.d.ts +1 -1
  58. package/dist/components/charts/value/BigNumber.capability.d.ts +13 -0
  59. package/dist/components/charts/value/BigNumber.d.ts +14 -0
  60. package/dist/components/charts/value/formatting.d.ts +40 -0
  61. package/dist/components/charts/value/thresholdSparkline.d.ts +40 -0
  62. package/dist/components/charts/value/types.d.ts +292 -0
  63. package/dist/components/charts/xy/MinimapChart.d.ts +2 -1
  64. package/dist/components/charts/xy/ScatterplotMatrix.d.ts +2 -1
  65. package/dist/components/realtime/lifecycleBands.d.ts +44 -0
  66. package/dist/components/realtime/types.d.ts +23 -8
  67. package/dist/components/recipes/annotationDensity.d.ts +69 -0
  68. package/dist/components/recipes/annotationLayout.d.ts +93 -0
  69. package/dist/components/semiotic-ai.d.ts +38 -15
  70. package/dist/components/semiotic-realtime.d.ts +2 -0
  71. package/dist/components/semiotic-recipes.d.ts +4 -0
  72. package/dist/components/semiotic-server.d.ts +2 -1
  73. package/dist/components/semiotic-utils.d.ts +8 -0
  74. package/dist/components/semiotic-value.d.ts +55 -0
  75. package/dist/components/semiotic.d.ts +7 -0
  76. package/dist/components/server/renderEvidence.d.ts +92 -0
  77. package/dist/components/server/renderToStaticSVG.d.ts +14 -1
  78. package/dist/components/server/staticAnnotations.d.ts +2 -0
  79. package/dist/components/stream/AccessibleDataTable.d.ts +15 -6
  80. package/dist/components/stream/FocusRing.d.ts +2 -1
  81. package/dist/components/stream/MarginalGraphics.d.ts +2 -1
  82. package/dist/components/stream/NetworkSVGOverlay.d.ts +13 -6
  83. package/dist/components/stream/OrdinalBrushOverlay.d.ts +19 -1
  84. package/dist/components/stream/OrdinalSVGOverlay.d.ts +4 -2
  85. package/dist/components/stream/SVGOverlay.d.ts +5 -2
  86. package/dist/components/stream/XYBrushOverlay.d.ts +21 -1
  87. package/dist/components/stream/geoTypes.d.ts +3 -0
  88. package/dist/components/stream/networkTypes.d.ts +2 -0
  89. package/dist/components/stream/ordinalTypes.d.ts +2 -0
  90. package/dist/components/stream/types.d.ts +2 -0
  91. package/dist/geo.min.js +1 -1
  92. package/dist/geo.module.min.js +1 -1
  93. package/dist/network.min.js +1 -1
  94. package/dist/network.module.min.js +1 -1
  95. package/dist/ordinal.min.js +1 -1
  96. package/dist/ordinal.module.min.js +1 -1
  97. package/dist/realtime.min.js +1 -1
  98. package/dist/realtime.module.min.js +1 -1
  99. package/dist/semiotic-ai.d.ts +38 -15
  100. package/dist/semiotic-ai.min.js +1 -1
  101. package/dist/semiotic-ai.module.min.js +1 -1
  102. package/dist/semiotic-realtime.d.ts +2 -0
  103. package/dist/semiotic-recipes.d.ts +4 -0
  104. package/dist/semiotic-recipes.min.js +1 -1
  105. package/dist/semiotic-recipes.module.min.js +1 -1
  106. package/dist/semiotic-server.d.ts +2 -1
  107. package/dist/semiotic-themes.min.js +1 -1
  108. package/dist/semiotic-themes.module.min.js +1 -1
  109. package/dist/semiotic-utils.d.ts +8 -0
  110. package/dist/semiotic-utils.min.js +1 -1
  111. package/dist/semiotic-utils.module.min.js +1 -1
  112. package/dist/semiotic-value.d.ts +55 -0
  113. package/dist/semiotic-value.min.js +2 -0
  114. package/dist/semiotic-value.module.min.js +2 -0
  115. package/dist/semiotic.d.ts +7 -0
  116. package/dist/semiotic.min.js +1 -1
  117. package/dist/semiotic.module.min.js +1 -1
  118. package/dist/server.min.js +1 -1
  119. package/dist/server.module.min.js +1 -1
  120. package/dist/xy.min.js +1 -1
  121. package/dist/xy.module.min.js +1 -1
  122. package/package.json +19 -5
@@ -1,4 +1,18 @@
1
1
  import type { ChartRubric } from "./chartCapabilityTypes";
2
+ import type { AccessibilityAuditResult } from "../charts/shared/auditAccessibility";
3
+ /**
4
+ * The channel an audience receives a chart through. Orthogonal to familiarity:
5
+ * a reader can be highly familiar with a chart type yet unable to *receive* it
6
+ * in their channel (an 8-slice pie is familiar but illegible to a screen
7
+ * reader). Defaults to `"visual"` when unset — no receivability bias applied.
8
+ *
9
+ * • `visual` — sighted reader; the historical default, no bias.
10
+ * • `screen-reader` — non-visual; meaning must survive the data table / nav tree.
11
+ * • `sonified` — audio; density and ordering matter more than color.
12
+ * • `agent` — an LLM reading via the grounding payload; same non-visual
13
+ * "is the meaning recoverable without pixels?" question.
14
+ */
15
+ export type ReceptionModality = "visual" | "screen-reader" | "sonified" | "agent";
2
16
  /**
3
17
  * A serializable description of who's reading the charts and what the
4
18
  * organization is trying to grow.
@@ -8,8 +22,6 @@ import type { ChartRubric } from "./chartCapabilityTypes";
8
22
  * telemetry, manager judgment, training records) and pass it to the
9
23
  * suggestion APIs. The library applies the bias and returns rankings that
10
24
  * reflect the audience instead of a generic data-literate baseline.
11
- *
12
- * Strategy memo: docs/strategy/audience-profiles.md
13
25
  */
14
26
  export interface AudienceProfile {
15
27
  /**
@@ -47,6 +59,15 @@ export interface AudienceProfile {
47
59
  * widening the menu
48
60
  */
49
61
  exposureLevel?: 0 | 1 | 2;
62
+ /**
63
+ * The channel this audience receives charts through. When set to a non-visual
64
+ * modality, `suggestCharts` runs the accessibility audit on each candidate and
65
+ * `applyAudienceBias` down-ranks charts whose meaning doesn't survive that
66
+ * channel (and surfaces the receivability findings as caveats). Unset /
67
+ * `"visual"` keeps the historical behavior — familiarity and targets only.
68
+ * See {@link receivabilityBias}.
69
+ */
70
+ receptionModality?: ReceptionModality;
50
71
  }
51
72
  export interface AudienceTarget {
52
73
  direction: "increase" | "decrease";
@@ -62,7 +83,34 @@ export interface AudienceBiasResult {
62
83
  rubric: ChartRubric;
63
84
  /** Reason string to append to the suggestion when a target fired. */
64
85
  appliedReason?: string;
86
+ /** Reason string to append when a receivability penalty fired (non-visual modality). */
87
+ receivabilityReason?: string;
88
+ }
89
+ export interface ReceivabilitySignal {
90
+ /** Score delta (≤ 0) — the receivability penalty for this channel. */
91
+ delta: number;
92
+ /** One-line reason, present only when a penalty applied. */
93
+ reason?: string;
94
+ /**
95
+ * Caveat messages for the findings that drove the penalty — the receivability
96
+ * slice of the audit, ready to merge into a suggestion's `caveats[]` alongside
97
+ * the perceptual ones (so both channels read from the same array).
98
+ */
99
+ caveats: string[];
65
100
  }
101
+ /**
102
+ * Translate an accessibility audit into a receivability penalty for a non-visual
103
+ * channel. Pure. `fail` findings on receivability-critical heuristics weigh most;
104
+ * `warn` findings weigh less; `manual`/`pass`/`not-applicable` never penalize
105
+ * (we don't punish what we can't prove). Penalty is clamped to a −3 floor so it
106
+ * reorders rankings without overriding data-shape correctness.
107
+ *
108
+ * ```ts
109
+ * const audit = auditAccessibility("PieChart", props)
110
+ * const { delta, reason } = receivabilityBias(audit, "screen-reader")
111
+ * ```
112
+ */
113
+ export declare function receivabilityBias(audit: AccessibilityAuditResult, modality: ReceptionModality): ReceivabilitySignal;
66
114
  /**
67
115
  * Apply an AudienceProfile's bias to a chart's composite score and rubric.
68
116
  * Pure function — used by both `suggestCharts` and `suggestStretchCharts`.
@@ -75,8 +123,17 @@ export interface AudienceBiasResult {
75
123
  * not so strong that it overrides chart correctness for the data shape.
76
124
  *
77
125
  * Score is left unclamped so internal sorting reflects the magnitude of bias.
126
+ *
127
+ * A third term — *receivability* — composes in when the audience declares a
128
+ * non-visual `receptionModality` and the caller passes a precomputed
129
+ * {@link ReceivabilitySignal} (from {@link receivabilityBias}). Familiarity and
130
+ * receivability are different axes: a chart can be familiar yet unreceivable in
131
+ * the target channel, and this folds that signal into the same score the
132
+ * recommender ranks by. The caller computes the signal once and reuses it for
133
+ * the suggestion's caveats too, so the audit is scanned a single time per
134
+ * candidate.
78
135
  */
79
- export declare function applyAudienceBias(baseScore: number, baseRubric: ChartRubric, component: string, audience: AudienceProfile | undefined): AudienceBiasResult;
136
+ export declare function applyAudienceBias(baseScore: number, baseRubric: ChartRubric, component: string, audience: AudienceProfile | undefined, receivability?: ReceivabilitySignal): AudienceBiasResult;
80
137
  /**
81
138
  * Resolve the effective familiarity for a chart under an audience. Used by
82
139
  * the stretch surface to decide whether a chart qualifies as "unfamiliar."
@@ -1,14 +1,15 @@
1
1
  import type { Datum } from "../charts/shared/datumTypes";
2
2
  import type { DataSummary } from "../data/DataSummarizer";
3
3
  import type { IntentId } from "./intents";
4
+ import type { ScaleFitFn, QualityFitFn, ScaleBand, CardinalityBand, EffectiveScale } from "./dataScaleProfile";
4
5
  /**
5
6
  * Chart family — high-level taxonomy used for filtering and intent matching.
6
7
  */
7
- export type ChartFamily = "time-series" | "categorical" | "distribution" | "relationship" | "flow" | "network" | "hierarchy" | "geo" | "realtime" | "custom";
8
+ export type ChartFamily = "time-series" | "categorical" | "distribution" | "relationship" | "flow" | "network" | "hierarchy" | "geo" | "realtime" | "value" | "custom";
8
9
  /**
9
10
  * Where a chart is imported from. Used by generators to emit correct import paths.
10
11
  */
11
- export type ChartImportPath = "semiotic/xy" | "semiotic/ordinal" | "semiotic/network" | "semiotic/geo" | "semiotic/realtime" | "semiotic/ai" | "semiotic";
12
+ export type ChartImportPath = "semiotic/xy" | "semiotic/ordinal" | "semiotic/network" | "semiotic/geo" | "semiotic/realtime" | "semiotic/value" | "semiotic/ai" | "semiotic";
12
13
  /**
13
14
  * Familiarity/accuracy/precision rubric (1-5 each).
14
15
  * Familiarity = how well-known the chart is to a general audience.
@@ -171,6 +172,38 @@ export interface ChartCapability {
171
172
  * a runnable config (accessor names, etc.) so consumers can `<Component {...props}>`.
172
173
  */
173
174
  buildProps: (profile: ChartDataProfile, variant?: ChartVariant) => Record<string, unknown>;
175
+ /**
176
+ * Optional declaration of this chart's sweet spot under scale. Receives the
177
+ * effective scale (declared `DataScaleProfile` merged with measured profile)
178
+ * and returns a score delta plus optional caveats. Use `scaleHints(...)` from
179
+ * `dataScaleProfile.ts` for the common declarative shape.
180
+ *
181
+ * When omitted, the engine assumes the chart is scale-agnostic and applies
182
+ * no scale bias beyond what `intentScores` already encodes in `profile`.
183
+ */
184
+ scaleFit?: ScaleFitFn;
185
+ /**
186
+ * Optional declaration of this chart's response to data quality (missingness,
187
+ * outliers, type heterogeneity). Returns a score delta plus caveats. When
188
+ * omitted, the engine applies a default heuristic that adds caveats for
189
+ * low completeness on the primary y field.
190
+ */
191
+ qualityFit?: QualityFitFn;
192
+ }
193
+ /**
194
+ * Effective scale range tag on a suggestion. Lets callers group suggestions
195
+ * by their scale sweet spot, detect threshold crossings as data grows, and
196
+ * narrate why a recommendation changed.
197
+ */
198
+ export interface SuggestionScaleRange {
199
+ /** Band classification of the row count this suggestion was evaluated against. */
200
+ band: ScaleBand;
201
+ /** Cardinality band, if known. */
202
+ cardinalityBand?: CardinalityBand;
203
+ /** Effective row count the engine reasoned over (declared or measured). */
204
+ rows: number;
205
+ /** Whether the row count came from a user-declared profile or the measured data. */
206
+ rowsSource: "declared" | "measured";
174
207
  }
175
208
  /**
176
209
  * One suggestion produced by `suggestCharts`. Consumers render this as a card,
@@ -193,4 +226,29 @@ export interface Suggestion {
193
226
  caveats: ReadonlyArray<string>;
194
227
  /** Ready-to-spread props. */
195
228
  props: Record<string, unknown>;
229
+ /**
230
+ * Scale tag — present when scale/quality information is available, either
231
+ * through declared `DataScaleProfile` or through the measured profile.
232
+ * Surfaces what band this chart was scored at so consumers can group by
233
+ * scale, detect threshold crossings, or narrate "the chart for now → at 10×."
234
+ */
235
+ scaleRange?: SuggestionScaleRange;
236
+ }
237
+ /**
238
+ * Multi-tier grouping of suggestions by scale band. Returned by
239
+ * `suggestChartsGrouped()`.
240
+ *
241
+ * Each tier is a ranked list of suggestions whose `scaleRange.band` falls in
242
+ * that tier. A single chart can appear in multiple tiers when its sweet-spot
243
+ * range spans them — that's the "graduation of views" surface: the same data
244
+ * gets a different chart depending on which scale you're optimizing for.
245
+ */
246
+ export interface ScaledSuggestionGroups {
247
+ tiny: Suggestion[];
248
+ small: Suggestion[];
249
+ medium: Suggestion[];
250
+ large: Suggestion[];
251
+ huge: Suggestion[];
252
+ /** The effective scale view the engine reasoned over. */
253
+ effective: EffectiveScale;
196
254
  }
@@ -0,0 +1,27 @@
1
+ import type { Datum } from "../charts/shared/datumTypes";
2
+ /**
3
+ * Shared chart-family classification + measure/dimension role resolution.
4
+ *
5
+ * `describeChart` (prose) and `buildNavigationTree` (structure) both need to
6
+ * know which family a component belongs to and which props carry its measure
7
+ * (quantitative) and dimension (categorical/ordered) accessors. This is the
8
+ * single source of truth so the two surfaces can't drift apart.
9
+ *
10
+ * Pure, dependency-light (types only).
11
+ */
12
+ export declare const XY_FAMILY: Set<string>;
13
+ export declare const BAR_FAMILY: Set<string>;
14
+ export declare const PART_TO_WHOLE: Set<string>;
15
+ export declare const DISTRIBUTION: Set<string>;
16
+ export interface ChartRoles {
17
+ measure?: string;
18
+ measureFallback: string;
19
+ dimension?: string;
20
+ dimensionFallback: string;
21
+ }
22
+ /** The measure (quantitative) and dimension (categorical/ordered) accessors, by family. */
23
+ export declare function roles(component: string, props: Datum): ChartRoles;
24
+ /** First string-valued series-splitting accessor among the known channels, if any. */
25
+ export declare function seriesField(props: Datum): string | undefined;
26
+ /** Format a dimension value (the label at an extremum) — dates as ISO day, numbers compactly. */
27
+ export declare function fmtDim(v: unknown, fmtNum: (n: number) => string): string;
@@ -0,0 +1,379 @@
1
+ import type { AnnotationStatus } from "./annotationProvenance";
2
+ export type ConversationArcEventType = "suggestion-shown" | "suggestion-chosen" | "audience-set" | "chart-rendered" | "chart-edited" | "chart-replaced" | "chart-exported" | "chart-abandoned" | "interrogation-asked" | "interrogation-answered" | "nav-node-focused" | "nav-branch-expanded" | "annotation-status-changed";
3
+ interface ConversationArcEventBase {
4
+ /** Discriminator for the event variant. */
5
+ type: ConversationArcEventType;
6
+ /** `Date.now()` at the moment the event was recorded. Stamped by the store. */
7
+ timestamp: number;
8
+ /** Stable ID for the enabled session — survives until `disableConversationArc()` or `reset()`. */
9
+ sessionId: string;
10
+ /** Optional opaque correlation key that threads a single arc together. */
11
+ arcId?: string;
12
+ /** Free-form bag for context the talk-track doesn't need a typed slot for. */
13
+ meta?: Record<string, unknown>;
14
+ }
15
+ export interface SuggestionShownEvent extends ConversationArcEventBase {
16
+ type: "suggestion-shown";
17
+ /**
18
+ * Intent label fed into `suggestCharts` (e.g. "trend", "distribution").
19
+ * Accepts a single intent or an array — mirrors `SuggestChartsOptions.intent`.
20
+ */
21
+ intent?: string | ReadonlyArray<string>;
22
+ /** Ranked component names in the order the suggester returned them. */
23
+ components: string[];
24
+ /** Top suggestion's composite score, if known. */
25
+ topScore?: number;
26
+ /** Audience target active when the suggestion ran. */
27
+ audience?: string;
28
+ }
29
+ export interface SuggestionChosenEvent extends ConversationArcEventBase {
30
+ type: "suggestion-chosen";
31
+ component: string;
32
+ /** 1-based rank in the matching `suggestion-shown` event, if known. */
33
+ rank?: number;
34
+ /** Who picked the suggestion: a human, an agent loop, or a default-fall-through. */
35
+ source?: "user" | "agent" | "auto";
36
+ }
37
+ export interface AudienceSetEvent extends ConversationArcEventBase {
38
+ type: "audience-set";
39
+ audience: string;
40
+ previous?: string;
41
+ }
42
+ export interface ChartRenderedEvent extends ConversationArcEventBase {
43
+ type: "chart-rendered";
44
+ component: string;
45
+ chartId?: string;
46
+ }
47
+ export interface ChartEditedEvent extends ConversationArcEventBase {
48
+ type: "chart-edited";
49
+ component: string;
50
+ chartId?: string;
51
+ /** Names of props that changed in this edit. */
52
+ changedProps?: string[];
53
+ }
54
+ export interface ChartReplacedEvent extends ConversationArcEventBase {
55
+ type: "chart-replaced";
56
+ from: string;
57
+ to: string;
58
+ /** Why the swap happened — `"repair"`, `"variant"`, `"user-rejected"`, etc. */
59
+ reason?: string;
60
+ }
61
+ export interface ChartExportedEvent extends ConversationArcEventBase {
62
+ type: "chart-exported";
63
+ component: string;
64
+ /** What was exported: `"jsx"`, `"svg"`, `"png"`, `"json"`, `"url"`, etc. */
65
+ format: string;
66
+ }
67
+ export interface ChartAbandonedEvent extends ConversationArcEventBase {
68
+ type: "chart-abandoned";
69
+ component?: string;
70
+ reason?: string;
71
+ }
72
+ export interface InterrogationAskedEvent extends ConversationArcEventBase {
73
+ type: "interrogation-asked";
74
+ /** Chart the question was directed at, if known. */
75
+ component?: string;
76
+ /**
77
+ * Question text. The `useChartInterrogation` instrumentation
78
+ * truncates to ~500 chars before recording so the ring buffer
79
+ * stays bounded; callers stamping their own events should do the
80
+ * same.
81
+ */
82
+ query: string;
83
+ /** Optional payload size hint (e.g. summary token count) for diagnostics. */
84
+ contextSize?: number;
85
+ }
86
+ export interface InterrogationAnsweredEvent extends ConversationArcEventBase {
87
+ type: "interrogation-answered";
88
+ /** Chart the answer was directed at, if known. */
89
+ component?: string;
90
+ /**
91
+ * Answer text. The `useChartInterrogation` instrumentation
92
+ * truncates to ~2000 chars before recording so multi-kilobyte LLM
93
+ * responses don't bloat the ring buffer. Callers stamping their
94
+ * own events should follow the same convention.
95
+ */
96
+ answer?: string;
97
+ /** Number of annotations the response attached, if known. */
98
+ annotationCount?: number;
99
+ /**
100
+ * Round-trip latency in ms from ask to answer, clamped to ≥ 0.
101
+ * The instrumentation measures via `performance.now()` when
102
+ * available; the `Date.now()` fallback can produce negative
103
+ * deltas under clock changes, hence the clamp.
104
+ */
105
+ latencyMs?: number;
106
+ /** Set when the response was an error rather than a successful answer. */
107
+ error?: boolean;
108
+ }
109
+ /**
110
+ * A reader focused a node in an `AccessibleNavTree` (keyboard or click). The
111
+ * first *reception*-side behavioral signal in the arc — which structural nodes
112
+ * a non-visual (or AI) reader actually visits, the dependent measure visualization-
113
+ * literacy studies usually lack. Emitted only on genuine tree interaction, not
114
+ * when the active node is driven externally (canvas → tree sync).
115
+ */
116
+ export interface NavNodeFocusedEvent extends ConversationArcEventBase {
117
+ type: "nav-node-focused";
118
+ /** `chartId` of the chart the tree describes, when correlated. */
119
+ chartId?: string;
120
+ /** Tree node id that gained focus. */
121
+ nodeId: string;
122
+ /** Node role: `"chart" | "axis" | "series" | "datum"`. */
123
+ role: string;
124
+ /** 1-based depth (aria-level). */
125
+ level: number;
126
+ /** The node's announced label (the emitter truncates to ~200 chars). */
127
+ label?: string;
128
+ }
129
+ /** A reader expanded or collapsed a branch in an `AccessibleNavTree`. */
130
+ export interface NavBranchExpandedEvent extends ConversationArcEventBase {
131
+ type: "nav-branch-expanded";
132
+ /** `chartId` of the chart the tree describes, when correlated. */
133
+ chartId?: string;
134
+ /** Tree node id that was toggled. */
135
+ nodeId: string;
136
+ /** Node role of the toggled branch. */
137
+ role: string;
138
+ /** 1-based depth (aria-level). */
139
+ level: number;
140
+ /** `true` on expand, `false` on collapse. */
141
+ expanded: boolean;
142
+ }
143
+ /**
144
+ * An annotation's editorial status transitioned (M7). The accept / dispute /
145
+ * retract / propose flow is what turns an annotation into the durable,
146
+ * observable node of the conversation arc (IDID §13.4): the note is the unit
147
+ * the arc is *about*, not chart chrome.
148
+ *
149
+ * `fromStatus`/`toStatus` are deliberately not named `from`/`to` — `summarizeArc`
150
+ * reads `from`/`to` as chart-component names (the `chart-replaced` shape), so a
151
+ * status value there would pollute `componentsSeen`.
152
+ */
153
+ export interface AnnotationStatusChangedEvent extends ConversationArcEventBase {
154
+ type: "annotation-status-changed";
155
+ /** `provenance.stableId` of the annotation whose status changed, when known. */
156
+ annotationId?: string;
157
+ /** Previous editorial status, if known. */
158
+ fromStatus?: AnnotationStatus;
159
+ /** New editorial status. */
160
+ toStatus: AnnotationStatus;
161
+ /** `chartId` of the chart carrying the annotation, when correlated. */
162
+ chartId?: string;
163
+ }
164
+ export type ConversationArcEvent = SuggestionShownEvent | SuggestionChosenEvent | AudienceSetEvent | ChartRenderedEvent | ChartEditedEvent | ChartReplacedEvent | ChartExportedEvent | ChartAbandonedEvent | InterrogationAskedEvent | InterrogationAnsweredEvent | NavNodeFocusedEvent | NavBranchExpandedEvent | AnnotationStatusChangedEvent;
165
+ /**
166
+ * Input shape accepted by `record()`: the event variant without the
167
+ * stamped fields (`timestamp` and `sessionId`). Callers may still
168
+ * provide them to backfill historical events.
169
+ *
170
+ * Implemented as a distributive conditional so each member of the
171
+ * discriminated union keeps its variant-specific payload (e.g.
172
+ * `SuggestionShownEvent.components`). A non-distributive
173
+ * `Omit<ConversationArcEvent, ...>` collapses to the union's common
174
+ * fields and rejects every variant-specific key.
175
+ */
176
+ export type ConversationArcEventInput = ConversationArcEvent extends infer E ? E extends ConversationArcEvent ? Omit<E, "timestamp" | "sessionId"> & Partial<Pick<E, "timestamp" | "sessionId">> : never : never;
177
+ export type ConversationArcListener = (event: ConversationArcEvent) => void;
178
+ export interface ConversationArcSink {
179
+ /**
180
+ * Persist a newly-recorded event. Called only after the store is enabled and
181
+ * the event is accepted into the ring buffer.
182
+ */
183
+ record?(event: ConversationArcEvent): void | Promise<void>;
184
+ /**
185
+ * Optional hook for consumers that treat `flush()` as an export boundary.
186
+ * The in-memory buffer is still cleared by the store after this call.
187
+ */
188
+ flush?(events: ReadonlyArray<ConversationArcEvent>): void | Promise<void>;
189
+ /** Clear durable state owned by this sink. */
190
+ clear?(): void | Promise<void>;
191
+ /** Load previously persisted events for replay / hydration. */
192
+ load?(): ReadonlyArray<ConversationArcEvent> | Promise<ReadonlyArray<ConversationArcEvent>>;
193
+ }
194
+ export interface ConversationArcStorageLike {
195
+ getItem(key: string): string | null;
196
+ setItem(key: string, value: string): void;
197
+ removeItem(key: string): void;
198
+ }
199
+ export interface LocalStorageConversationArcSinkOptions {
200
+ /** Storage key. Defaults to `semiotic:conversation-arc`. */
201
+ key?: string;
202
+ /** Test hook or alternate Storage implementation. Defaults to `window.localStorage`. */
203
+ storage?: ConversationArcStorageLike;
204
+ /** Maximum events retained in storage. Defaults to 1000. */
205
+ maxEvents?: number;
206
+ }
207
+ export interface IndexedDBConversationArcSinkOptions {
208
+ /** Database name. Defaults to `semiotic-conversation-arc`. */
209
+ dbName?: string;
210
+ /** Object store name. Defaults to `events`. */
211
+ storeName?: string;
212
+ /** Test hook or alternate IndexedDB factory. Defaults to `globalThis.indexedDB`. */
213
+ indexedDB?: IDBFactory;
214
+ /** Maximum events retained in the object store. Defaults to 1000. */
215
+ maxEvents?: number;
216
+ }
217
+ export type ConversationArcWebhookFetch = (input: string, init?: RequestInit) => Promise<unknown>;
218
+ export interface WebhookConversationArcSinkOptions {
219
+ url: string;
220
+ method?: "POST" | "PUT";
221
+ headers?: Record<string, string>;
222
+ fetch?: ConversationArcWebhookFetch;
223
+ mapEvent?: (event: ConversationArcEvent) => unknown;
224
+ }
225
+ export interface LoadConversationArcOptions {
226
+ /** Capacity of the hydrated ring buffer. Defaults to max(existing, events.length, 1000). */
227
+ capacity?: number;
228
+ /** Active session id for future recordings. Replayed event session ids are preserved. */
229
+ sessionId?: string;
230
+ /**
231
+ * Whether the store should accept new events after hydration. Defaults to false
232
+ * so replaying an artifact cannot accidentally start telemetry.
233
+ */
234
+ enabled?: boolean;
235
+ /** Append to the current buffer instead of replacing it. Defaults to false. */
236
+ append?: boolean;
237
+ }
238
+ export interface ConversationArcStore {
239
+ readonly enabled: boolean;
240
+ readonly sessionId: string | null;
241
+ readonly capacity: number;
242
+ /**
243
+ * Records an event. Returns the stamped event on success, or `null`
244
+ * if the store is disabled. Stamps `timestamp` and `sessionId` if
245
+ * the caller didn't.
246
+ */
247
+ record(input: ConversationArcEventInput): ConversationArcEvent | null;
248
+ /** Returns the current buffer (newest last) and clears it. */
249
+ flush(): ConversationArcEvent[];
250
+ /**
251
+ * Returns a frozen, referentially-stable snapshot of the current
252
+ * buffer. Stable across consecutive calls until the next mutation,
253
+ * so it can drive `useSyncExternalStore`. Read-only — callers that
254
+ * need a mutable copy should `.slice()` the result.
255
+ */
256
+ getEvents(): ReadonlyArray<ConversationArcEvent>;
257
+ /**
258
+ * Subscribe to new events. Returns an unsubscribe function.
259
+ *
260
+ * Subscriptions persist across enable/disable transitions — a
261
+ * subscriber registered before `enableConversationArc()` still
262
+ * receives events once recording starts. Cleared by `reset()`.
263
+ */
264
+ subscribe(listener: ConversationArcListener): () => void;
265
+ /** Empties the buffer without disabling the store. */
266
+ clear(): void;
267
+ /** Disables the store and drops the buffer + subscribers. */
268
+ reset(): void;
269
+ }
270
+ export interface EnableConversationArcOptions {
271
+ /** Maximum events retained in the ring buffer. Defaults to 1000. */
272
+ capacity?: number;
273
+ /** Override the generated session ID. Useful for cross-tab correlation. */
274
+ sessionId?: string;
275
+ }
276
+ /**
277
+ * Subscribe to *any* state mutation in the conversation-arc store —
278
+ * including `clear`, `flush`, `reset`, and `enable`, in addition to
279
+ * recorded events. Intended for React hooks that need to re-render
280
+ * on snapshot changes; event sinks should use `subscribe()` instead.
281
+ *
282
+ * Returns an unsubscribe callback.
283
+ */
284
+ export declare function subscribeToConversationArcChange(listener: () => void): () => void;
285
+ /**
286
+ * Register an opt-in persistence sink. Sinks receive only accepted events:
287
+ * disabled telemetry remains a no-op and replay hydration does not re-emit
288
+ * historical events into durable stores.
289
+ */
290
+ export declare function registerConversationArcSink(sink: ConversationArcSink): () => void;
291
+ /**
292
+ * Browser-local durable sink backed by `localStorage`. It appends each accepted
293
+ * event to a JSON array and exposes `load()` so a session can be replayed later.
294
+ * When `localStorage` is unavailable (SSR, private browser failures), operations
295
+ * degrade to no-ops and `load()` returns `[]`.
296
+ */
297
+ export declare function createLocalStorageConversationArcSink(options?: LocalStorageConversationArcSinkOptions): ConversationArcSink & {
298
+ load(): ConversationArcEvent[];
299
+ };
300
+ /**
301
+ * Browser durable sink backed by IndexedDB. Writes are asynchronous and
302
+ * fire-and-forget from the recorder; callers that need replay can await
303
+ * `sink.load()`.
304
+ */
305
+ export declare function createIndexedDBConversationArcSink(options?: IndexedDBConversationArcSinkOptions): ConversationArcSink & {
306
+ load(): Promise<ConversationArcEvent[]>;
307
+ };
308
+ /**
309
+ * Minimal webhook sink for app-owned analytics ingestion. The sink posts one
310
+ * JSON payload per accepted event; retry, batching, authentication refresh, and
311
+ * sampling policy remain application concerns.
312
+ */
313
+ export declare function createWebhookConversationArcSink(options: WebhookConversationArcSinkOptions): ConversationArcSink;
314
+ /**
315
+ * Hydrate the in-memory store from a saved arc without replaying side effects.
316
+ * The loaded events become visible through `getEvents()` / `useConversationArc`,
317
+ * but listeners and sinks are not called. Pass `{ enabled: true }` when you want
318
+ * to resume recording after loading; the default is replay-only.
319
+ */
320
+ export declare function loadConversationArc(events: ReadonlyArray<ConversationArcEvent>, options?: LoadConversationArcOptions): ReadonlyArray<ConversationArcEvent>;
321
+ /** Alias with the word replay for fixture-driven callers. */
322
+ export declare function replayConversationArc(events: ReadonlyArray<ConversationArcEvent>, options?: LoadConversationArcOptions): ReadonlyArray<ConversationArcEvent>;
323
+ /**
324
+ * Record an audience-set event. Convenience wrapper around `record`
325
+ * for the common pattern: "the user picked a new audience profile
326
+ * and I want to put that in the arc."
327
+ *
328
+ * `previous` is optional but recommended — it lets downstream
329
+ * analytics see the transition rather than just the new state. Pass
330
+ * `null` when there was no prior audience.
331
+ *
332
+ * Returns the stamped event, or `null` if recording is disabled.
333
+ *
334
+ * ```ts
335
+ * import { recordAudienceChange } from "semiotic/ai"
336
+ *
337
+ * function AudiencePicker({ value, onChange }) {
338
+ * return (
339
+ * <select value={value} onChange={(e) => {
340
+ * const next = e.target.value
341
+ * recordAudienceChange(next, value)
342
+ * onChange(next)
343
+ * }} />
344
+ * )
345
+ * }
346
+ * ```
347
+ */
348
+ export declare function recordAudienceChange(audience: string, previous?: string | null, extra?: {
349
+ arcId?: string;
350
+ meta?: Record<string, unknown>;
351
+ }): ConversationArcEvent | null;
352
+ /**
353
+ * Sugar for an `annotation-status-changed` event (M7). Call it from the
354
+ * accept / dispute / retract / propose UI so the arc records how an
355
+ * annotation's editorial standing moved — the durable, observable node of the
356
+ * conversation. No-op (returns null) until `enableConversationArc()`.
357
+ */
358
+ export declare function recordAnnotationStatusChange(toStatus: AnnotationStatus, opts?: {
359
+ annotationId?: string;
360
+ fromStatus?: AnnotationStatus;
361
+ chartId?: string;
362
+ arcId?: string;
363
+ meta?: Record<string, unknown>;
364
+ }): ConversationArcEvent | null;
365
+ /**
366
+ * Opt in to conversation-arc telemetry. Safe to call multiple times —
367
+ * subsequent calls reuse the existing session unless `sessionId` is
368
+ * explicitly provided.
369
+ */
370
+ export declare function enableConversationArc(options?: EnableConversationArcOptions): ConversationArcStore;
371
+ /** Turn the store off without dropping the buffered events. */
372
+ export declare function disableConversationArc(): void;
373
+ /**
374
+ * Always returns a store façade. Methods are safe to call when
375
+ * disabled — `record()` returns null, `getEvents()`/`flush()` return
376
+ * empty arrays, `subscribe()` returns a no-op unsubscriber.
377
+ */
378
+ export declare function getConversationArcStore(): ConversationArcStore;
379
+ export {};