-
Notifications
You must be signed in to change notification settings - Fork 137
Releases: nteract/semiotic
Releases · nteract/semiotic
Semiotic v3.7.4
@github-actions
github-actions
626cc08
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Added
- Lineage DAG recipe helpers.
semiotic/recipesnow exports lineage DAG helpers for
data-flow and KStreams-style network layouts, with tests covering stable topology layout. - KStreams docs demo. The docs add a KStreams recipe page and example data,
plus expanded custom network chart guidance for selection-aware layouts.
Fixed
- Network custom layout selection metadata. Network custom layouts now preserve selection
metadata through pipeline layout and render paths, with regression coverage for selection
actions andNetworkCustomChartbehavior.
Changed
- Refreshed developer/dependency tooling, including Prettier, TypeScript ESLint, Rollup, and Sharp.
Assets 2
Semiotic v3.7.3
@github-actions
github-actions
982b611
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Added
- OpenAI Apps domain verification for hosted MCP. HTTP mode now serves the raw
OPENAI_APPS_CHALLENGE_TOKENfrom/.well-known/openai-apps-challenge, allowing
ChatGPT Apps domain verification against Cloud Run and other HTTPS-hosted MCP origins without
committing the token. - Cloud Run verification playbook.
deploy/cloud-rundocuments the Challenge Base URL shape,
token environment variable, andcurlcheck for ChatGPT Apps verification, and the wrapper now
tracks thesemiotic@^3.7.3release line.
Tests
- Added MCP HTTP regression coverage for the OpenAI Apps challenge route while confirming the
unauthenticated OAuth protected-resource probe continues to return 404.
Assets 2
Semiotic v3.7.2
Added
- Stateless MCP HTTP mode for hosted connectors.
semiotic-mcp --httpnow creates an
ephemeral MCP server + Streamable HTTP transport per request, returns JSON responses instead of
holding long-lived SSE streams open, and exposes/mcp,/healthz, and/healthendpoints. This
makes the read-only MCP tool surface suitable for autoscaling serverless hosts such as Cloud Run. - Cloud Run deployment wrapper.
deploy/cloud-runpackages a minimal public MCP deployment that
runs the publishedsemiotic-mcpbinary, documents unauthenticated read-only deployment, health
endpoints, host allowlisting, and ChatGPT/Claude connector setup, and includes hosted-app privacy
and terms pages for app review.
Changed
- MCP HTTP hardening. HTTP mode normalizes the
Hostheader for optional
MCP_ALLOWED_HOSTSDNS-rebinding protection, returns clean 404s for non-MCP paths including
.well-known/*probes, makes request teardown idempotent, and closes each per-request transport
promptly to avoid serverless keep-alive leaks. - Docs dark-mode polish. The visible
AccessibleNavTreeselected row now resolves through
--semiotic-surface,--semiotic-grid, and--semiotic-bg, with--semiotic-textapplied to
visible rows. The Accessibility / Structured Navigation bidirectional BarChart now mirrors the
docs theme by switching betweencarbonandcarbon-dark. - Annotation blog demo reliability. The "Annotations That Get Contested, and Heard" chart now
uses numeric XY coordinates with month tick formatting, so editorial-status callouts render on the
visible line while the prose and navigation tree keep human-readable month labels. - Cloud Run wrapper release line. The Cloud Run package now depends on
semiotic@^3.7.2so the
hosted wrapper resolves the 3.7.2 MCP server after the npm package is published.
Fixed
- Fixed a light selected-row fallback in the accessible navigation tree when the docs site was in
dark mode but no--semiotic-surfacetoken was present. - Fixed the bidirectional sync demo's BarChart rendering as a light Carbon island inside dark docs.
- Fixed missing visual annotations in the contested-annotations blog demo caused by string month
values being used as XY coordinates.
Assets 2
Semiotic v3.7.1
@github-actions
github-actions
fe4ab1d
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Added
- Render evidence.
renderChartWithEvidence()(semiotic/server) returns the SVG plus a
machine-readableRenderEvidenceobject computed from the rendered scene graph — mark counts by
scene type, resolved axis domains, anemptyflag, category/node/edge counts, annotation count,
and the accessible name — so agent repair loops and CI assertions can verify a chart actually drew
data marks without pixel inspection. The MCPrenderCharttool now returns the same evidence block
alongside its SVG/PNG output. - Misleading-design diagnostics.
diagnoseConfig()gains a deception-check pack: inverted
extents (INVERTED_AXIS), unlabeled dual-axis series (DUAL_AXIS_UNLABELED), trend windows
cropped to a favorable slice (CHERRY_PICKED_WINDOW), negative values in part-to-whole encodings
(PART_TO_WHOLE_NEGATIVE— an error for pie/donut/funnel), non-interpolatingcurve="basis"
smoothing (NON_PASSING_CURVE), slope-distorting aspect ratios (EXTREME_ASPECT_RATIO), and
over-sliced pies (PIE_TOO_MANY_SLICES). These patterns mislead human readers and — per the
chart-deception literature — vision-language models the same way. - Theme contrast conformance gate. Every shipped theme preset is now tested against WCAG-derived
floors (4.5:1 for text/tooltip/annotation roles, 3:1 for the focus indicator), with sub-3:1 mark
colors pinned in an exact-match known-exceptions ledger so palette regressions fail and
improvements must shrink the ledger. The axe integration scan re-enables thecolor-contrast
rule on the strength of the gate. - Scorecard top-1 agreement. The capability quality scorecard now reports strict
top1AgreementRatebeside the lenient top-3 rate (current canonical set: 93% top-1 / 100% top-3),
ranks the top-3 over distinct components rather than variants of one chart, and gains fixtures
for the previously unexercised Heatmap, GaugeChart, FlowMap, and DistanceCartogram descriptors. - ChatGPT Apps widget (experimental). The MCP server gains
renderInteractiveChart, which
renders a static-data chart through the same server render path asrenderChartand returns a
text/html;profile=mcp-appwidget (ui://semiotic/chart-widget.html) with fit/zoom, data,
hover, and render-evidence controls for ChatGPT developer-mode connectors over
semiotic-mcp --http. A deployment playbook lives in the repo asCHATGPT_APPS_DEPLOYMENT.md,
and an MCP protocol test suite covers the tool and widget resource end to end. - Docs prerender route artifacts. The docs build now emits one prerendered HTML file per
route with sanitized, route-specific machine-readable content in each page's<noscript>
fallback, plus adocs/build/llms-routes.jsonroute index for agent readers; the docs route
check verifies the output.
Changed
- Theme legibility fixes (WCAG AA).
pastelstextSecondary/focus/annotation,bi-tool
textSecondary, and thetufte-dark/journalist/playfulannotationcolors were deepened to
clear the contrast floors; the empty-state / BigNumber-empty / data-table-caption fallback color
moved from#999(2.8:1) to#666, matching the default theme'stextSecondary. - Capability descriptor judgment fixes.
DifferenceChartno longer takes fullcompare-series
marks when it would silently drop series beyond its native two; flatBarChartyields on crossed
two-categorical matrices (Grouped/Stacked/Heatmap show the matrix) and on raw-observation data;
ChoroplethMaprequires at least two area features (a one-region choropleth has nothing to
compare). contrastRatio()now parses 3-digit hex shorthand (#333), making the default themes measurable.
Tests
- Colocated tests for the
NetworkCustomChartandOrdinalCustomChartescape hatches (the
XY variant was already covered).
Assets 2
Semiotic v3.7.0
Added
- Accessibility audit, descriptions, and structured reader navigation.
auditAccessibility()/
formatAccessibilityAudit()grade chart configs against Chartability-style heuristics, while
describeChart(),buildNavigationTree(),AccessibleNavTree, anduseNavigationSync()provide
layered chart descriptions, WAI-ARIA tree navigation, bidirectional tree/canvas focus sync, and
annotation-anchor focus for non-visual readers. - IDID reader-grounding and receivability primitives.
describeChart()can emit an optional L4
communicative-act sentence from a chart capability,buildReaderGrounding()combines description,
intent, and structure into one agent-readable payload,AudienceProfile.receptionModalitylets
suggestCharts()penalize charts a non-visual audience cannot receive, and
accessibilityCaveats()feeds audit warnings into recommendation caveats. - Conversation-arc telemetry.
enableConversationArc(),disableConversationArc(),
getConversationArcStore(),useConversationArc(), andsummarizeArc()expose a bounded,
opt-in event stream for suggestion, interrogation, navigation, export, and annotation-status
events, with zero overhead while disabled.registerConversationArcSink(),
createLocalStorageConversationArcSink(),createIndexedDBConversationArcSink(),
createWebhookConversationArcSink(),loadConversationArc(), andreplayConversationArc()add
opt-in durable capture and replay hydration without duplicating sink or analytics events. - Variant-discovery API and MCP tool.
proposeVariant()emits registered variants,
conservative heuristic transforms, and same-intent cross-family alternatives;
evaluateVariantProposal()scores fit, novelty, risk, rubric deltas, and audience bias; and
MCP now exposesproposeChartVariantsfor agent-driven variant exploration. - Chart repair workflow primitive.
repairChartConfig()and the MCPrepairChartConfigtool
use capability fit and chart suggestions to critique a proposed chart choice and return safer
alternatives for agent retry loops. semiotic/valueandBigNumber. A focal-value KPI component now ships as a lightweight value
entry point, with formatting, threshold, comparison/target, staleness, push-buffer, and slot APIs
for embedding trend or chart context.- First-class annotation design assistance for 3.7.0.
autoPlaceAnnotationsnow composes collision-aware placement, curved connector routing, density budgets, progressive disclosure, responsive shedding, redundant association cues, cohesion modes, audience-aware amount, and defensive annotations. Per-annotationemphasisestablishes hierarchy, while provenance confidence supplies a default reading order when hierarchy is not explicit. - Annotation provenance and editorial lifecycle.
AnnotationProvenanceandAnnotationLifecyclecarry actor, evidence, confidence, stable identity, freshness, editorial status, and supersession metadata.applyAnnotationLifecycle,applyAnnotationStatus, andfilterAnnotationsByStatuskeep visual treatment, descriptions, and structured navigation aligned on the current annotation set. - Stable semantic annotation anchors.
anchor: "semantic"/lifecycle.anchor: "semantic"now re-resolves annotations throughprovenance.stableIdafter data refresh, using point scene nodes or matching data rows before falling back to the recorded coordinate when the target is gone. - Annotation reception surfaces.
describeChartleads with author-marked features,buildNavigationTreeadds an Annotations branch, and the accessibility audit checks color-only note-to-target association. - Annotation connector diagnostics.
diagnoseConfig()now warns about far notes without a
connector and very long connectors, keeping placement guidance aligned with the annotation design
assistant. - Annotation design guidance docs. The first-class Annotations docs section now includes Overview, Design Guidance, Advanced Annotations, and Provenance & Lifecycle pages with live examples.
- Linked-hover series mode.
linkedHover={{ mode: "series" }}now resolves each chart's
series-identity field automatically, withseriesFieldavailable as an override for cross-chart
series highlighting. - Capability-driven visual baseline gate.
check:visual-baseline-capabilitiesderives SSR and
linked-hover visual coverage requirements fromchartSpecs.ts, verifies the current Playwright
evidence, and keeps the remaining SSR/CSR parity and linked-hover interaction snapshots in
one-way burn-down maps. It is wired into CI,release:check, andprepublishOnly. - Shareable, restorable docs playgrounds. Every
/playground/*page now
serializes its knob + dataset state into the URL (?sc=...&ds=...) and restores it
on load, with "Copy link" and "Copy config (JSON)" affordances. The round-trip
dogfoods the library's owntoConfig/toURL/fromURL/fromConfig/
copyConfigso a playground configuration becomes a portable, inspectable
ChartConfigartifact; non-serializable composite playgrounds degrade
gracefully (no toolbar, no URL writes). - Faithful "Copy" in docs live examples. The code a docs
LiveExamplecopies to the clipboard
now serializes the real props the chart rendered with, instead of the trimmed/elided display stub —
so copied example code reproduces the example rather than referencing undefined or shortened values.
Display stays readable; copy is runnable. The pure code-generation moved to a React-free
codegenmodule. - Generated, complete
llms.txt.docs:llms(scripts/generate-llms-txt.mjs) regenerates the
rootllms.txtretrieval index with the full chart catalog derived fromchartSpecs(grouped by
family, every charted component), each entry tagged with its communicative act
(resolveCommunicativeAct) so an agent reader gets what a chart is for, not just what it shows.
Replaces the previously hand-maintained (and stale) index; kept fresh by thecheck:llmsgate in CI
andrelease:check, and regenerated duringwebsite:build. - Docs: per-chart "At a glance" grounding panel. A reusable
ChartGroundingcomponent renders,
live for each chart, the communicative act it performs (buildReaderGrounding), a layered L1–L3
description (describeChart), the chart type's reader caveats, and an accessibility badge
(auditAccessibility— hovering the badge lists the specific findings; clicking opens the full
audit) — the reader/agent grounding for the chart, computed from the shipped intelligence APIs so it
can't drift. Now on all 39 static chart pages (props centralized in a reviewed fixtures map;
realtime/push-only charts are exempt), and enforced bycheck:docs-coverage. - Docs: "Reshape to unlock" generative suggestions. The
/choosepicker now goes beyond charts the
data already fits: from a flat table's field profile it proposes the transform that unlocks
Semiotic's distinctive charts — pivot two columns into a Sankey, stamp an event log with time for
a ProcessSankey, nest categories into a Treemap, etc. — each with the reshape and why that
chart is worth it. Surfaces the flow/temporal/hierarchy/geo charts that a fit-only recommender can
never reach. Driven by a puresuggestReshapes(profile)heuristic. - Docs: "Choose a Chart" front door. A new top-level
/choosepage profiles a dataset and ranks
the catalog by fit and communicative act (livesuggestCharts), showing each recommendation's score,
reasons, and caveats with links to the chart pages. An audience selector demonstrates how the ranking
shifts per reader — with the biasing familiarity/targets and their rationale shown — and surfaces
governed stretch picks. Users can arrive with data and intent instead of a component name. - Docs: a11y hooks & theming serialization. The Accessibility docs now cover the preference hooks
useReducedMotion/useHighContrastand theuseNavigationSynctree↔canvas sync hook; the Theming
docs now coverresolveThemePreset,themeToCSS,themeToTokens, and building custom theme objects.
Custom Charts cross-links the related Cookbook recipes. - Docs: AI authoring & tooling pages. The Intelligence docs section gains four pages closing the
previously-undocumented AI surface: CLI & MCP (everynpx semiotic-aiflag and every
npx semiotic-mcptool, with agent setup), Variant Discovery & Repair (proposeVariant,
evaluateVariantProposal,registerVariantDiscovery,repairChartConfig), Capability Authoring
(theChartCapabilitydescriptor,registerChartCapability,registerIntent, and the intent
taxonomy), and Audience Profiles (theAudienceProfileshape, suggestion bias, stretch picks,
reception modality, and governance). - Playground control-drift gate.
check:docs-playground-controlschecks each playground's
selectknobs againstchartSpecs.ts: a knob bound to an enum-typed prop can no longer offer an
option the chart doesn't accept. It only gates enum-typed props (and treatsmapProps-transformed
pages as informational), so an enum member renamed in the API now fails the build instead of
leaving a dead knob. Wired into CI,release:check, andprepublishOnly. - Prop-table drift gate.
check:docs-prop-tablesresolves each chart's prop surface from
chartSpecs.ts(ownPropsover resolvedPROP_BAGS) and AST-checks every chart page's
documented prop names against it, failing if a statically-required prop is undocumented. It keeps
the hand-authored tables (and their curation) verifiable against the canonical registry rather than
replacing them;--verbosereports props documented but absent fromchartSpecsas a follow-up
backlog. Wired into CI,release:check, andprepublishOnly. - Docs coverage gate + per-page quality bar. `check:docs-...
Assets 2
Semiotic v3.6.0
Added
semiotic/aisubpath — the AI-facing API surface as a first-class entry point. 211 KB gzip; the heuristic engine works without any LLM call, but every primitive returns LLM-friendly structured context so a model can ride on top. The entry covers four families of capability:- Recommendation.
suggestCharts(data, options?)returns ranked chart suggestions for a profiled dataset and optional intent; each suggestion carries a runnablepropsobject, an intent-score breakdown, the chart's rubric (familiarity / accuracy / precision), human-readablereasons[], andcaveats[].suggestDashboardreturns a multi-panel composite covering distinct analytical intents (withintentsMissingfor honesty about what the data can't show).suggestStretchChartsreturns the literacy-growth surface — charts the audience is unfamiliar with but the data actually supports.scoreChartandexplainCapabilityFitgive single-chart introspection.useChartSuggestionsis the React hook wrapping the same engine for live UI. - Profiling.
profileData(data)returns aChartDataProfilewith candidate fields per role (x / y / size / category / series / time), distinct counts, monotonicity, structure detection (hierarchy / network / geo).diffProfilereports schema changes between two profiles.inferIntentis a zero-dependency regex classifier that maps natural-language phrases ("why is X different?","compare these","trend over time") to one of 13 built-in intents. - Audience calibration.
AudienceProfileis a serializable per-organization config —familiarity(chart → 1-5 number map) andtargets(chart →{direction: "increase" | "decrease", weight, reason}) — that biases recommendations toward what a specific audience already knows AND toward charts the organization is trying to grow into. Three built-in personas (executivePersona,analystPersona,dataScientistPersona) ship as starting points; bias is meaningful (target weight 2 = ±2.0 on a 5-point composite score) and visible (the audience's verbatim rationale string lands onreasons[]so the policy is auditable in the UI). - Capability descriptors per chart. Every chart now ships a
<ChartName>.capability.tsnext to its TSX, declaringfamily,rubric,fits(profile) → reason | null,intentScores, optionalvariants,caveats, andbuildProps. The registry is runtime-extensible viaregisterChartCapability/unregisterChartCapabilityso consumers can add their own charts to the recommendation pool without forking the engine. - 13 built-in intents in
intents.ts:trend,compare-series,compare-categories,rank,part-to-whole,distribution,correlation,flow,hierarchy,geo,outlier-detection,composition-over-time,change-detection. Each carries a descriptor with synonyms, alias phrases, and a default scorer;registerIntentextends the taxonomy at runtime.
- Recommendation.
useChartInterrogationanduseChartFocushooks (semiotic/ai) — the headless conversational primitives.useChartInterrogationgives consumers a{ ask, history, summary, annotations, loading, error, reset }surface; the consumer brings their own LLM viaonQuery, and the hook supplies it with the profiled summary, the suggestion list, and the current focus datum as structured context. Returned annotations route directly to the chart's standardannotationsprop so the AI's response can render as callouts, threshold lines, and bands, not just text.useChartFocussubscribes to the chart's observation store and returns the current point-of-focus ({ datum, x, y, source }), with configurable event-type filtering for sticky-focus UIs.semiotic-mcpserver — Model Context Protocol server (npx semiotic-mcp) exposingrenderChart,interrogateChart,suggestCharts, anddiagnoseConfigas MCP tools so agents inside Claude Code, Cursor, Windsurf, and other MCP-aware environments can drive Semiotic directly. The interrogation tool returns the same statistical summary and AI-facing instructions the hook produces; the suggestion tool returns ranked structured content with runnable props.semiotic-aiCLI extensions —--doctorvalidates a{component, props, data}JSON spec againstvalidateProps+diagnoseConfig;--schemaemits the chart-schema JSON;--compactand--examplesproduce LLM-prompt-sized context. Pair with the MCP server for agent workflows that need both schema and validation in one place.- Three case-study blog posts —
/blog/charts-that-know-what-theyre-for(the recommendation engine and audience layer),/blog/anchored-conversations(point-anchored AI conversation viauseChartFocus+useChartInterrogation), and/blog/live-conversational-dashboard(the streaming + interrogation + annotation composition). The three together describe the product surface 3.6.0 makes possible.
Changed
- AreaChart is now a single-series chart. Multi-series area overlays are an occlusion nightmare; the capability rejects the multi-series intent scores it previously claimed and
buildPropssubselects to the leading series (largest cumulative y) when the input has 2+ groups, surfacing acaveats[]line so the reader knows they're looking at one slice. Gradient (gradientFill: true,areaOpacity: 0.55) is the baseline default.trendscore is 5 for clean single-series and 3 when subselected.LineChart.trendyields to AreaChart on single-series (4 vs AreaChart's 5) but still wins on multi-series (5 vs AreaChart's 3) because LineChart shows the whole dataset. - DifferenceChart accepts 2+ series via top-2 subselection. Previously rejected anything other than
seriesCount === 2; now picks the two series with the highest cumulative y from the input and emits acaveats[]line when subselecting from 3+ series. Same ordered-x guard the other time-series capabilities apply (xProvenance === "scatter" && !monotonicXis rejected) so the chart no longer shows up for scatter-shaped data with two categorical groups. - Scatterplot and ConnectedScatterplot prefer the canonical 2-numeric form when a sequence axis is present. With a strong-x (time or named) AND 2+ other numerics in the dataset, both charts plot the two numerics against each other (revenue ×ばつ profit) instead of recapitulating a line chart on the sequence axis. ConnectedScatterplot threads the sequence as
orderAccessorso the path encodes temporal progression. ConnectedScatterplot'scorrelationintent scores 5 when canonical (vs 4 otherwise), and Scatterplot'scorrelationsteps back to 4 when canonical is available so ConnectedScatterplot wins the tiebreak — both charts fit, but the one with the temporal annotation is strictly more informative. X_FIELD_HINTrecognizes calendar-segment field names. The profiler's x-axis name regex now matchesquarter,qtr,fiscal, andweekin addition to the existingyear/month/day/date/time/timestamp. Without this, data shaped as{quarter, revenue, region}fell into scatter-fallback provenance and series detection never fired —lineBy/areaBywere silently dropped and multi-series time-series charts zigzagged across regions.
Assets 2
Semiotic v3.5.4
@github-actions
github-actions
c9561da
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Added
bandprop onLineChartandAreaChart(andStreamXYFrame) — asymmetric min/max envelope drawn under the lines/areas, driven by independenty0Accessor/y1Accessor. Distinct from the existingboundsAccessor(symmetric ±offset) and fromAreaChart.y0Accessor(which replaces the area baseline). Pass a singleBandConfigor an array of them for percentile fans (e.g. p25/p75 stacked on top of p10/p90). Per-series by default — one ribbon perlineBy/colorBygroup, colored from the parent line at 0.2 fillOpacity. PassperSeries: falsefor an aggregate min/max envelope across all series. Non-interactive by default (hovers pass through to the line on top); setinteractive: trueif the band should participate in hit testing. Band y0/y1 values feedyExtentauto-derivation so a tall envelope can never clip, with explicityExtentstill winning.- Tooltip enrichment now covers every interaction surface: the hovered datum carries
band: { y0, y1 }(first band) andbands: [...](all bands) on the pointer hover path, eachallSeries[i].datumin multi-mode, and the keyboard-navigation datum — one sharedenrichDatumWithBandhelper drives all three. Bounds-sourced ribbons stay decorative and are excluded from the contract. - Default tooltip surfaces band values automatically: configure
bandwithout supplying a customtooltipfunction and the default tooltip gains one row pair per band (low + high). String accessors become labels; function accessors fall back tolow/high. Custom tooltips still readdatum.band/datum.bandsdirectly. Live demo at/charts/line-chart#band.
- Tooltip enrichment now covers every interaction surface: the hovered datum carries
tickAnchor: "edges"onframeProps.axes[i]— flips the leftmost tick'stext-anchortostartand the rightmost toendon horizontal axes (anddominant-baselinetohanging/autoon vertical axes) so edge tick labels can't overflow the plot area. Default"middle"keeps existing behavior. Pairs naturally withaxisExtent: "exact": exact pins the domain to the literal data min/max, edges keeps the labels readable at those bounds. Edge detection is pixel-based, not array-index-based, so inverted y scales (the default[height, 0]) and reversed x scales (streamingarrowOfTime: "left") anchor the right edges. Closes a common wrapper-library workaround that previously routed throughxFormatreturning ReactNodes withtranslateXmath.--semiotic-tick-font-sizeand--semiotic-axis-label-font-sizeCSS variables — emitted from the canonical theme typography fields (tickSize,labelSize) alongside the existing--semiotic-tick-font-familyand--semiotic-title-font-size. BoththemeToCSS(raw string serialization) andThemeProvider(inline style) write them;themeToTokensexports them as DTCGdimensiontokens. SVG axes consume the variables viastyle={{ fontSize: "var(--semiotic-tick-font-size, 10px)" }}so a CSS-var override on any ancestor (<div style={{ "--semiotic-tick-font-size": "14px" }}>) flows down without consumers needing!important. Landmark ticks bump bycalc(... + 1px)so the +1 size stays relative to the var.data-orientattributes and per-axis class names on axis groups — each axis now renders as its own<g class="semiotic-axis semiotic-axis-{bottom|left|right|top}" data-orient="...">inside the.stream-axeswrapper. Consumers can target one axis at a time from external CSS without affecting the others:[data-orient='left'] text { font-size: 14px }works. Tick text carriesclass="semiotic-axis-tick", axis labelsclass="semiotic-axis-label", and chart titlesclass="semiotic-chart-title"for class-based targeting too.loadingContentprop on all HOCs — sibling toemptyContent. Whenloadingis true andloadingContentis set, it renders in place of the default shimmer-bar skeleton (wrapped in the same sized container so the chart slot stays reserved). Passfalseto suppress the loading UI entirely (the early-return becomes null and a consumer's outer loading state takes over). Threaded throughuseChartSetup,useNetworkChartSetup, anduseCustomChartSetup; all 47 HOCs accept it viaBaseChartProps.
Fixed
website:buildparcel resolution for the Atom feed link —docs/public/index.htmlpreviously declared<link rel="alternate" href="/blog/feed.xml">with an absolute path so prerendered nested routes (e.g./charts/line-chart/) wouldn't resolve it as/charts/line-chart/blog/feed.xml. Parcel's HTML packager couldn't resolve the absolute path during build, so the website build failed. Moved the link injection intoscripts/prerender.mjsalongside the existing/llms.txtalternate injection — same strip-and-inject pattern, same absolute-URL semantics, and Parcel no longer sees the unresolvable reference in source HTML.
Changed
boundsAccessorandbandnow share one rendering primitive. Both public envelope APIs normalize to a singleresolvedRibbons: ResolvedRibbon[]array at the PipelineStore layer, then flow throughxySceneBuilders/ribbonScene.ts— one scene builder, one y-extent expansion pass, one style cascade.boundsScene.tsandbandScene.tsare deleted; the public prop surfaces stay distinct (asymmetric pairs read better asbandthan as aboundsAccessorunion return type) but the implementation is no longer duplicated. The bounds ribbon also now correctly skips datums with null/NaNy(previously the coerced+null === 0could silently render a ribbon around the implicit-zero "value" of a missing row). Thekind: "bounds" | "band"discriminator on each ribbon lets the hover handler restrictdatum.band/datum.bandsenrichment to band-sourced envelopes — bounds stays decorative-only, matching its prior contract.
Assets 2
Semiotic v3.5.3
Added
DifferenceChart(XY) — two-series A/B comparison chart that fills the area between two series with a color that switches at each crossover (seriesAColorwhere A > B,seriesBColorwhere B > A). Crossover x-values are linearly interpolated so adjacent segments meet at zero-width vertices (no jagged seams). Both series can be drawn as overlay lines on top of the fill viashowLines(defaulttrue). Renders throughchartType: "mixed"with the segment groups inareaGroups— single frame, single set of scales, perfect geometric alignment between fill and overlay. Push API supported (HOC owns internal raw-data state; push triggers segment recomputation). Classic uses: temperature anomaly, forecast vs. actual, budget variance, any A/B comparison where the direction of the difference is the message. Live demos + Quick Start streaming toggle at/charts/difference-chart. New SSR config + validation map entry + chartSpecs entry → schema regenerated.axisExtentprop on all XY and ordinal HOCs —"nice"(default, current behavior) uses d3-scale's rounded tick generator;"exact"pins the first and last tick to the literal data min/max with equidistant intermediate ticks. Applies to XY x/y axes and the ordinal value (r) axis only; no-op on network/geo/hierarchy. In exact mode the pipeline ALSO skipsextentPaddingso the domain reflects the literal data bounds, not a padded version — explicittickValues,xExtent/yExtent/rExtentstill win over both modes. Three demos at/features/axes#axis-extent(temporal LineChart, Scatterplot, SwarmPlot). CentralizedequidistantTicks+ticksForModehelpers insrc/components/charts/shared/axisExtent.ts.roundedTopon SwimlaneChart — pixel radius rounds the outermost ends of each lane (left+right for horizontal, top+bottom for vertical). Middle segments stay square so adjacent pieces butt against each other; single-segment lanes round all four corners. Implemented via a newcornerRadii?: { tl, tr, br, bl }field onRectSceneNodeand shared shape utilities insrc/components/stream/renderers/cornerRadii.ts(canvas and SVG renderers share the geometry; each owns its drawing language). Live demo at/charts/swimlane-chart#rounded-corners.buildHistogramTooltiphelper — histogram-specific default tooltip forRealtimeHistogram, sibling tobuildWaterfallTooltip/buildHeatmapTooltip. Surfacesrange: <binStart>–<binEnd>,count: <total>, andcategory: <category>instead of the canonicalx:/y:shape, which produced empty strings on aggregated bin datums. Falls back to the canonical shape when a non-binned datum sneaks through.tickValueson XY axes (frameProps.axes[i].tickValues) — explicit per-axis tick positions, mirroring the ordinal frame'srTickValues. Previously the field appeared in the docs and the docs LiveExample but was silently ignored bySVGOverlay's tick computation; now it bypasses both d3's "nice" generator andaxisExtent: "exact"and wins overincludeMax. Pixel-distance filtering still drops overlapping labels. AcceptsArray<number | Date>. Pinning regression test inSVGOverlay.tickValues.test.tsx.- ProcessSankey
systemInTimeAccessor/systemOutTimeAccessorandshowLabels— optional per-edge lifecycle timestamps let a source band show mass waiting before the edge departs and a target band show mass retained after the edge arrives. The band outline now extends to those lifecycle bounds and paints per-edge gradient stubs;showLabels={false}suppresses dense band labels without dropping the legend. The docs page adds a helpdesk-ticket example plus the Process Sankey vs. classic Sankey recipe. - Docs blog —
/blognow has article and index routes, a distinct no-sidebar shell, Atom feed generation, social-card generation, route prerender metadata, and seven launch entries covering release notes, chart explainers, and case studies.
Fixed
- Area canvas renderer respects CSS-variable fills —
areaCanvasRenderer.tsnow resolvesstyle.fillthrough the existingresolveCanvasFillhelper (same primitive bars use), sovar(--...)references resolve from the canvas DOM ancestor. Previously the fill path skipped this resolution while the stroke path included it — passing a CSS variable as the area fill produced no visible color (canvas silently rejects unresolved CSS vars) and the gradient path fell back to the sentinel blue regardless of the requested color. Affects any chart that emits area-type scene nodes withvar(--...)fills. - DifferenceChart accessor coercion —
xAccessor/seriesAAccessor/seriesBAccessoroutputs now flow through atoNumbercoercer that handlesDate(→getTime()ms) and numeric strings ("5"→5) before theNumber.isFinitefilter. Previously, time-series data (Date objects inxAccessor) and CSV-style numeric strings were silently dropped at the segment-algorithm guard, producing an empty chart. - DifferenceChart crossover detection across non-finite rows — the segment algorithm now tracks the last VALID point (not
sorted[i - 1]) for crossover comparison, so a NaN gap between two valid rows no longer suppresses the segment break that should sit between them. - DifferenceChart
remove()/update()synchronous return values — both methods compute results from auseRef-backed live buffer and return them synchronously at call time. The earlier pattern built results inside thesetStateupdater, which could return empty arrays under React 18+ concurrent batching if the updater was deferred or replayed. - DifferenceChart bounded push buffer — new top-level
windowSizeprop caps the raw-row buffer with FIFO eviction. Long-running streams no longer accumulate unbounded rows that the segment algorithm has to re-sort and re-segment on every render. The previousframeProps.windowSizerecommendation in the docs had no effect (the underlying frame receives static data from this chart, not streaming inputs), so the docs streaming example and Quick Start were updated to use the new prop. - ProcessSankey hover and tooltip regressions — decorative gradient stubs opt out of hit testing, filled bezier-body hits now return finite pointer coordinates for ProcessSankey's custom datum shape, and the default tooltip no longer turns short numeric domains (day/month indices) into 1970 dates.
- ProcessSankey SSR parity for lifecycle stubs —
renderChart("ProcessSankey", ...)now threadssystemInTimeAccessorandsystemOutTimeAccessorinto the shared scene builder, so static SVG output uses the same lifecycle band bounds as the client HOC. - Blog metadata correctness — blog entry dates are formatted at UTC midnight so US timezones do not display the previous day, and the Atom feed link is absolute so prerendered nested routes do not point at
/charts/blog/feed.xml.
Changed
extentPaddingskipped inaxisExtent="exact"mode — bothPipelineStoreandOrdinalPipelineStorenow treatextentPaddingas 0 whenconfig.axisExtent === "exact", so the scale domain pins to the literal data min/max and the first/last ticks read as the actual bounds. Trade-off documented: glyphs at the extremes can sit at the plot edge in exact mode. Default"nice"keeps the existing padded domain.- Per-corner radius geometry centralized —
hasAnyCornerRadiusand corner-clamping logic extracted frombarCanvasRenderer.tsandSceneToSVG.tsxto a sharedcornerRadii.tsmodule. Each renderer keeps its own path-tracing primitives (arcTovs SVGA); the geometry agrees by construction. - Capability matrix regenerated and release-gated —
ai/capabilities.jsonanddocs/capabilities.mdnow index all 45 chart schemas, includingDifferenceChart;QuadrantChartis markedsupportsSSR: trueto match itsrenderChartregistration;check:capabilitiesis wired into CI,release:check, andprepublishOnly. - Blog registry drift is release-gated — new
check:blog-entrieskeepsdocs/src/blog/entries.jsandentries-meta.jsin sync, and runs duringwebsite:build, CI,release:check, andprepublishOnly. - Server-rendered QuadrantChart officially supported — the SSR config emits quadrant fills, centerlines, and labels via
svgPreRenderers, which also powers the QuadrantChart blog OG card.
Assets 2
Semiotic v3.5.2
Added
useSeriesFeatureshook —forecast+anomalyprops are now first-class on AreaChart, Scatterplot, and ConnectedScatterplot (previously LineChart-only). Each consumer collapses from ~85 LOC of synthetic-key + lazy-load + state-management boilerplate to ~10 lines via the shared hook.series-features,forecast, andanomalycapability tags surface throughchartSpecs.ts/ai/capabilities.jsonfor agent discovery.useEncodingDomainhook — generic[min, max]tracker over bounded data + push-mode values, extracted from BubbleChart'ssizeBylogic. Scatterplot'ssizeBynow picks up correctly-scaled radii in push mode (previously a latent bug that returned the rawsizeByvalue as the pixel radius). String-field accessors hitting numeric-string values ("5","12") coerce cleanly instead of leaking strings into downstream math.useStreamStatushook — user-facing observer for push-API charts. Wraps a ref, interceptspush/pushMany, and exposes a reactivestatusenum ("idle"|"active"|"stale") pluslastPushTime. Surfaced viasemiotic/utilsandsemiotic/realtime. Wrap-once symbol guard prevents StrictMode double-wrap.useXYLineStylehook (Phase 2 step 5 of the HOC/Frame audit) — the line-side analogue ofuseXYPointStyle. LineChart, MultiAxisLineChart, and MinimapChart (both main + overview lines) all collapse to a single hook call covering the five-step recipe: base stroke width → color resolution → optional group-aware fill →mergeShapeStyleprimitives overlay →wrapStyleWithSelection. AresolveStroke(d, group?)override absorbs MultiAxisLineChart's per-series colorMap; unset selection-hook / primitives args keep MinimapChart's main + overview paths intact (no-ops on the wraps preserve referential identity). LineChart's forecast/anomaly segment-aware wrap stays HOC-side as a post-pass over the hook's output — the lazy-load + state-management contract has no counterpart in the other two consumers. Net ~65 LOC removed across the three HOCs. 15 unit tests pin the recipe.- Bundle-size truth source —
scripts/sync-bundle-sizes.mjsreadspackage.json#exports, gzips each*.module.min.js, and upserts marker-block sections in README.md, CLAUDE.md, andai/system-prompt.md(the synced.cursorrules/.windsurfrules/.github/copilot-instructions.md/.clinerules/docs/public/llms-full.txtfollow from CLAUDE.md).check:bundle-sizesis wired intorelease:check,prepublishOnly, and the CI workflow alongside the other doc-correctness gates, so dependency bumps that nudge a bundle past its rounded KB boundary now fail CI when the docs haven't been regenerated. Drops the stale// 200 KB gziphero comment in the README — the autogenerated table is the only source of truth. radialGeometryhelpers —sweepToAngles,valueToAngle,computeArcBoundingBoxextracted from GaugeChart into a shared module, exposed viasemiotic/utils. Custom radial-chart authors (XYCustomChart, bespoke layouts) no longer have to re-derive the gauge sweep math.- ProcessSankey temporal validators —
validateProcessSankeyandformatProcessSankeyIssueexported fromsemioticandsemiotic/network. External code (data pipelines, AI agents, server-side validators) can pre-check graphs against the same value-conservation + endpoint-resolution rules the chart enforces. regressionprop on Scatterplot, BubbleChart, ConnectedScatterplot, BarChart, DotPlot — sugar over the trend annotation. Acceptstrue| method string ("linear"|"polynomial"|"loess") | fullRegressionConfig. Ordinal charts treat categories as integer indices and project the regression line through the band scale (with linear interpolation between band centers for LOESS fractional indices).- FlowMap push API — joined the realtime-capable HOC family. The frame gained a
geo-linesvariant onuseFrameImperativeHandlepluspushLine/pushManyLines/removeLine/getLines/lineIdAccessoronGeoPipelineStore.supportsPush: truein capabilities; docs streaming demo flipped fromsetState(flows)toref.current.push(flow). - Capability matrix at
ai/capabilities.json— 44 charts indexed across 5 categories withrenderModes/supportsPush/supportsSSR/supportsLegend/supportsSelection/supportsLinkedHover/colorModel/layoutMode/specialFeaturesfields. Generated alongsidedocs/capabilities.mdbynpm run docs:capabilities; locked againstchartSpecs.tsbycheck:capabilities.suggestCharts({ capabilities })accepts push/linkedHover/ssr/selection/legend constraints and surfaces afilteredOutlist with reasons. New/features/capabilitieswebsite page renders an interactive filterable matrix.
Changed
- ProcessSankey particles unified with SankeyDiagram — particles now ride the canvas +
ParticlePoolpath. The HOC writes pre-computed cubic bezier control points onto each ribbon spec;NetworkPipelineStore's particle-pool gate broadened fromchartType === "sankey"to also acceptcustomNetworkLayout. SVG particle overlay deleted (~80 LOC, including the<circle>-per-particle allocation per frame). Prop surface aligned:showParticles+particleStyle(sameParticleStyleshape as SankeyDiagram). IndividualparticleRadius/particleDuration/particleDensity/particleMaxPerEdgeprops removed. Particles inherit source-band colors vianodeColorMapbinding through invisible color-binding scene nodes. - Ribbon geometry unified — new
src/components/geometry/ribbonGeometry.tsis the single source of truth for the M-C-L-C-Z ribbon path emission. SankeyDiagram passescp1X = xi(curvature),cp2X = xi(1-curvature)(d3-sankey S-curve); ProcessSankey passescp1X = cp2X = cx(lane-aware single-point bend). Both buildScenes.ts (SSR) and the HOCs (CSR) call the same helper. algorithm.js→algorithm.ts— last JS file in the chart source tree migrated to TypeScript with all types inlined as the canonical source (algorithm.d.tsdeleted). 7 import sites updated to drop the.jsextension; test file converted with type-annotated fixtures.getSizeclamps tosizeRange— normalized position is now clamped to[0, 1]before mapping into the size range, so a pushed point whosesizeByvalue falls outside the running domain (most common in push-mode initial state) renders at the boundary radius instead of producing an arbitrarily large pixel value.- Push-mode bezier carry-through —
NetworkPipelineStore.ingestBoundednow copies pre-computedbezierfrom raw edges onto internalRealtimeEdgerecords, validated against theBezierCacheshape (object +circular: boolean+ 4-point or non-empty-segments + finitehalfWidth). Malformed shapes are silently dropped instead of crashing the particle pipeline. - Edge value preservation — bounded ingestion now uses
Number.isFinite(numValue) ? numValue : 1instead ofNumber(v) || 1, so a legitimatevalue: 0edge survives end-to-end (e.g. suppressed-flow markers in particle pipelines). - Particle CSS variable resolution —
networkParticleRendererruns all colors throughresolveCSSColorsoparticleStyle.color="var(--semiotic-primary)"and theme-token-returningedgeColorFnresults paint correctly (canvas'sfillStylesilently rejects CSS custom properties otherwise). - Particle color resolution moved out of the renderer — functional
particleStyle.coloris now invoked ingetParticleColorwith aresolveEdgeEndpoint-resolvedRealtimeNode. Custom-layout charts (ProcessSankey) whereedge.sourceis a string id now correctly invoke the user's color function instead of falling back to a hardcoded default. - Keyboard nav skips invisible scene nodes —
extractNetworkNavPointsskipsr <= 0circles andw <= 0 || h <= 0rects (matching the canvas renderer's own skip gates). Keyboard focus on ProcessSankey now lands on a real band/ribbon instead of an off-canvas color-binding placeholder. - Bundle counts —
semiotic/aicovers 40 HOCs (XY + ordinal + network + realtime);semiotic/recipesadded to the table; full schema covers 44 charts. - Documentation refreshed — README,
CLAUDE.md,ai/system-prompt.md, and the synced.cursorrules/.windsurfrules/.github/copilot-instructions.md/.clinerules/docs/public/llms-full.txtall carry current bundle sizes, chart counts, and entry-point inventory. - Bundle-size docs corrected — earlier in this release cycle the README/CLAUDE.md/ai/system-prompt.md bundle table briefly carried inflated numbers because
npm run dist(no--production) writes non-minified output todist/*.module.min.js. The published artifacts come fromnpm run dist:prod, which terser-minifies. Bothscripts/sync-bundle-sizes.mjsandsize-limitreaddist/*.module.min.jsdirectly, so a localdistbuild silently substituted unminified bytes into the docs. The corrected numbers (xy 81 KB gz, ordinal 66 KB, network 62 KB, etc.) now reflect actual published artifacts. The bundle-size check tolerates ±3 KB build-machine variance so local↔CI minor differences don't fail without real growth.
Fixed
- Fixed ProcessSankey particles not flowing when
showParticleswas toggled on (root cause:customNetworkLayoutcharts skippedfinalizeLayout, so pre-computed bezier never reachedstore.edges). - Fixed ProcessSankey particles rendering as light grey instead of inheriting source-node category color.
- Fixed
Number.isFinitecoercion inuseEncodingDomainso the running domain is always numeric.
Assets 2
Semiotic v3.5.1
Added
- Added explicit extent examples and tests covering chart-level
xExtent/yExtentpass-through.
Fixed
- Fixed
yExtenthandling so explicit user bounds continue to control the rendered domain instead of being overridden by envelope-derived extents. - Fixed realtime heatmap tooltip metadata so bin-center values are available and
agg="sum"tooltips report summed values.