Nine years ago I argued that annotations should be first-class citizens in data visualization — produced with the same rigor as the marks they sit on, not hand-drawn in Illustrator after the chart is "done." That argument didn't fully land at the time, because reading a chart was treated as a solitary act. It isn't anymore. An AI is always on, always ready to mark a point and tell you why. The moment a chart becomes a surface that an agent, a watcher, and a human all write to, the annotation stops being decoration and becomes the unit of the conversation. This release gives annotations what that role requires: hierarchy, better connectors, and — most importantly — a record of where a note came from and how it ages.
Why this matters
In theconversational substratean annotation is the thing that persists. A user clicks a mark and asks "why did this spike?"; an answer comes back and a callout is added to the chart. A latency watcher flags an anomaly at 2 a.m. and leaves a note. The next reader opens a chart that has accumulated interpretive history. That history is only trustworthy if each note carries its provenance — who wrote it, on what basis, with what confidence — and its lifecycle — whether it still applies as the data moves, and whether the team still believes it. Without that, a conversational chart accumulates truth, error, speculation, and institutional folklore in one undifferentiated pile. First-class annotations are the precondition for telling them apart.
None of the additions below are breaking. Every existing annotation array renders exactly as before; the new capabilities are opt-in fields that ride along on the annotation objects you already pass.
Hierarchy: one note leads
When a chart carries several annotations, they shouldn't all shout at the same volume. Any annotation now acceptsemphasis: "primary" | "secondary" — the same token charts already use for dashboard hierarchy, now per-note. A secondaryannotation dims and drops behind; a primary one paints at full weight and on top. It's applied type-agnostically, so it works for every annotation type, and the dim composes with the freshness treatment — a stale, secondary note becomes the quietest thing on the chart.
Two callouts, one emphasis: "primary" and one"secondary". The secondary note dims and yields z-order; the primary paints at full weight, on top.
Connectors that swoop
Connectors were straight lines only. They're still the default, but you can now opt into a curved, swoopy connector — the kind hand-drawn annotation layers have always reached for — withconnector: { type: "curve" }. The curvevalue controls how far it bows; the arrowhead follows the curve's tangent so it still points cleanly at its subject.
The same callout with a default straight connector and an opt-inconnector: { type: "curve" }. The arrowhead re-aligns to the curve's tangent.
Where a note came from
The bigger change is metadata. An annotation can now carry aprovenance block that separates three things the field usually collapses: who made it (authorKind: human, agent, watcher, system),how its claim was derived (basis: a hand note, a statistical test, a rule, an LLM inference, an external source), and a coarse origin label (source). A human can relay a statistical-test basis; an agent can relay a human note. Keeping the actor and the evidence separate lets a reader — or another agent — weight a note by the strength of its evidence, not just by who left it.
import { withProvenance } from "semiotic/ai" const note = withProvenance( { type: "callout", x: "2026-W14", y: 9, label: "Deploy-correlated spike" }, { provenance: { authorKind: "watcher", basis: "statistical-test", confidence: 0.78, createdAt: "2026-04-02T14:32:00Z", dataVersion: "2026-W14", }, } )This is the schema from the Intent-Driven Information Design framework'sChartAnnotationProvenance, now shipping in the reference implementation — so a reviewer who clones the repo finds what the paper describes.
Two ways a note ages
"How does this note age?" turns out to be two independent questions. Thetemporal axis — freshness, derived fromcreatedAt + ttlHint — answers does this still apply as the data moves? Aging notes dim, stale notes dash, expired notes drop out. The editorial axis —status: proposed, accepted, disputed, retracted, with asupersedes link — answers do we still believe it?A note can be fresh but disputed, or stale but accepted; the two don't collapse into one. A watcher leaves a note "proposed"; a human accepts or disputes it; a revision supersedes it. That's how a conversational chart's accumulated notes stay legible instead of turning into sediment.
Notes stay bound to their data
Underneath all of this is the promise that makes annotations worth treating as first-class: a note is bound to its data, not to a position or an array index. Sort the data, filter it, stream new points past it, zoom the axis — a value-anchored annotation resolves through the scale every render and lands on the same data point. That guarantee is now locked by a regression test, because it's the one property we most want to never silently break.
Where this goes
The same pattern shows up well beyond a single dashboard. An incident timeline accumulates on-call annotations that later feed a postmortem. A financial chart carries analyst notes that must be distinguishable from model output when the chart is forwarded to a board. A scientific figure travels into a paper stripped of its original context — and a defensive, provenanced caveat is what survives the trip. In every case the question is the same: when a note outlives the moment it was made, what does it need to carry to still be trustworthy? Hierarchy, lifecycle, and provenance are the start of that answer.