Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ Add a changeset for every user-visible package change:
pnpm changeset
```

All ten public Charts packages release as one fixed version. Documentation,
All eleven public Charts packages release as one fixed version. Documentation,
tests, benchmarks, and build-only changes do not need a changeset unless they
change the published package contract.
10 changes: 10 additions & 0 deletions .changeset/compact-scales.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@tanstack/charts-scales': minor
---

Add compact callable linear, band, point, and ordinal scales through the exact
`@tanstack/charts-scales/linear`, `/band`, `/point`, and `/ordinal` entries.
There is no package root export. Compact linear scales are numeric and two-stop;
use `d3-scale` for time, transformed, piecewise, nonnumeric, locale-aware, and
other full D3 behavior. Unsupported D3 behavior does not warn or fall back at
runtime.
1 change: 1 addition & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"fixed": [
[
"@tanstack/charts",
"@tanstack/charts-scales",
"@tanstack/react-charts",
"@tanstack/octane-charts",
"@tanstack/preact-charts",
Expand Down
9 changes: 9 additions & 0 deletions .changeset/react-tooltip-entry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@tanstack/react-charts': minor
---

Move React tooltip-body composition to `@tanstack/react-charts/tooltip`.
Consumers using `renderTooltipBody` must migrate root `Chart` to `Chart`,
`/canvas` `Chart` to `CanvasChart`, and `/core` `Chart` to `RendererChart` from
that entry. Move wrapper types to the matching `ChartProps`, `CanvasChartProps`,
or `RendererChartProps` names and their `CommonProps` counterparts.
13 changes: 13 additions & 0 deletions .changeset/tooltip-extensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@tanstack/charts': minor
---

Make native tooltips and tooltip portals explicit extensions. Import `tooltip`
from `@tanstack/charts/tooltip`; replace `tooltip: true` with `tooltip`, dynamic
booleans with `enabled ? tooltip : false`, and configured objects with
`{ use: tooltip, ...options }`. Complete definition values use
`ChartTooltipInput`; `ChartTooltipOptions` remains the options-only type.

Import `portal` from `@tanstack/charts/tooltip/portal`; replace `portal: true`
with `portal`, omit `portal: false`, and replace dynamic booleans with
`enabled ? portal : undefined` inside the configured tooltip object.
132 changes: 101 additions & 31 deletions API-FRICTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ observed difficulty from examples, production migrations, tests, and agent
evaluations so later API, documentation, and TanStack Intent skill work is
based on evidence.

Last updated: 2026-07-30
Last updated: 2026-07-31

## Triage rule

Expand Down Expand Up @@ -190,7 +190,9 @@ Each entry records:
| F-152 | Version bumps invalidated workspace bundle evidence | Tooling/Release | resolved |
| F-153 | Changesets left release-facing version claims behind | Tooling/Release | resolved |
| F-154 | Root barrels crossed the browser host boundary | API/Tooling | resolved |
| F-155 | Conformance monitoring blocked unrelated changes | Tooling | resolved |
| F-155 | Optional tooltip code burdened every chart consumer | API | resolved |
| F-156 | Releases stranded manual Unreleased migration notes | Tooling/Release | resolved |
| F-157 | Conformance monitoring blocked unrelated changes | Tooling | resolved |

## Findings

Expand Down Expand Up @@ -487,21 +489,27 @@ Each entry records:

- Status: resolved
- Severity: high
- Observed in: strict migration of fixtures, sandbox, and Stats
- Observed in: strict migration of fixtures, sandbox, and Stats, followed by
the 15 KiB representative React bundle target
- Friction: `scaleUtc`, `scaleTime`, `scaleLog`, `scaleSymlog`, `scaleSqrt`,
`configuredScale`, `ChartScaleTransform`, and inferred scale types remain
exported beside native `d3-scale` values. Their names are easier for both
humans and agents to select accidentally even though the strict compiler no
longer consumes inferred axis options.
- Decision: remove the legacy inferred scale and transform surface after its
historical tests and bundle fixtures have been relabeled or deleted. Keep
D3 imports visibly sourced from `d3-scale`.
D3 imports visibly sourced from `d3-scale` for its complete semantics. Offer
the deliberately smaller linear, band, point, and ordinal subset only from
exact `@tanstack/charts-scales/*` entries, with no root export. The compact
package is a constrained bundle option, not a second general-purpose scale
API.
- Verification: the obsolete scale, radius, color, curve, transform, and
spatial wrappers and subpaths are gone; the inferred-scale builder and its
tests are deleted; fixtures and histogram benchmarks use direct `d3.bin`;
repository search finds only direct D3 imports in product definitions.
TypeScript, the standard test suite, both four-test Octane matrices, and
every bundle budget pass.
differential tests cover the compact subset against D3; packed-consumer
checks resolve every exact entry. The complete compact family is 1,877 gzip
bytes, and a representative compact React line is 14,227 gzip bytes without
retaining `d3-scale`, `d3-format`, or `d3-interpolate`.

### F-016 — Stats animated export still renders through Plot

Expand Down Expand Up @@ -1086,7 +1094,10 @@ Each entry records:
consumers are byte-locked. Optional features have isolated gzip budgets;
comparison and exploratory kernels remain measurement-only. Baseline
changes require an explicit reviewed command, including decreases, so saved
bytes cannot become silent future headroom.
bytes cannot become silent future headroom. Input-boundary checks use only
modules with retained output bytes, so an imported-but-eliminated module
cannot create a false pass or failure. The compact React line has a hard
15 KiB ceiling; tooltip and portal are measured as separate increments.
- Verification: repeated builds match the exact minified and gzip baseline.
`pnpm bundle:check` passes the locked consumers and the tightened histogram,
facet, curve, time, transform, spatial, arrow, frame, link, and tick feature
Expand All @@ -1097,8 +1108,12 @@ Each entry records:
now locked rather than hidden inside unused ceiling headroom. The canonical
byte lock runs on pinned Ubuntu 24.04 and Node 24.18.0; this prevents runner
and compressor upgrades from masquerading as library-size changes. The
current canonical baseline records the unchanged source tree under that
environment.
bundle-reduction work locks the compact scene at 6,711 gzip bytes and its
React consumer at 14,227. Tooltip adds 3,381 bytes and portal adds another 806. Retained-output assertions prove the base excludes tooltip, portal,
React tooltip composition, and the D3 scale/format/interpolate stack.
Replacing the core-owned D3 ordinal default required reviewed 0.05 KiB
adjustments to the tick and polar-composition feature ceilings; all other
policy changes are exact locks or new isolated budgets.

### F-041 — Bounded segments and caps required custom marks

Expand Down Expand Up @@ -1135,9 +1150,12 @@ Each entry records:
`items` array.
- Current decision: ordered items use one uniformly typed
`text(point, context)` callback, preserving contextual datum and coordinate
types for every item kind. For a separately hoisted complete tooltip object,
document an explicit `ChartTooltipOptions` annotation as a normal
type-introduction boundary; never recommend a cast.
types for every item kind. The extension form
`{ use: tooltip, format(point) {} }` remains inside `defineChart`, so the
definition supplies contextual datum and coordinate types at the exact
option location. For a separately hoisted complete tooltip object, document
an explicit `ChartTooltipOptions` annotation as a normal type-introduction
boundary; never recommend a cast.
- Follow-up: if raw-host examples repeat this pattern, add a small
definition-correlated options helper rather than weakening callback types.

Expand Down Expand Up @@ -3207,25 +3225,27 @@ Each entry records:
contexts could clip it or place it below adjacent UI. Escaping those
boundaries required rebuilding focus, placement, collision, pinning, and
cleanup in an application-owned overlay.
- Decision: add definition-owned `tooltip.portal`. The host opens the tooltip
itself as a manual Popover in the browser top layer where supported, keeping
its chart DOM ancestry and inherited styling. If Popover is unavailable or
fails, the tooltip moves directly under the `ownerDocument` body with fixed
high-stack positioning. Both paths map scene anchors to client coordinates,
collide against the viewport, and reposition on scroll, viewport resize,
chart resize, and tooltip content resize. Local positioning remains the
default. Documentation calls out the fallback's CSS inheritance boundary.
- Decision: make portal transport an exact opt-in extension imported from
`@tanstack/charts/tooltip/portal` and installed as the nested `portal` option
on `{ use: tooltip }`. It opens the tooltip as a manual Popover in the
browser top layer where supported, keeping its chart DOM ancestry and
inherited styling. If Popover is unavailable or fails, the tooltip moves
directly under the `ownerDocument` body with fixed high-stack positioning.
Both paths map scene anchors to client coordinates, collide against the
viewport, and reposition on scroll, viewport resize, chart resize, and
tooltip content resize. Local positioning remains the default.
- Verification: DOM-host regressions cover top-layer and fixed fallback
parenting, client-coordinate mapping, viewport collision,
scroll/resize/content repositioning, local-to-portal updates, renderer
replacement, owner-document targeting, and final cleanup. The React
composition regression exercises a pinned custom body inside the portaled
surface and removes it on unmount. Catalog case 35 passes its real-Chromium
quick profile, including both widths and every interaction step. The primary
suite passes 2,459 tests, all framework matrices pass, and type, docs,
suite passes 3,003 tests, all framework matrices pass, and type, docs,
packed-consumer, seven-adapter, formatting, and bundle gates pass. The
reviewed bundle change is 1,486 gzip bytes for the DOM host and 1,947 for the
React adapter; it adds no bundled dependency.
portal transport is absent from retained base and tooltip-only graphs. Its
isolated kernel is 1,580 gzip bytes and its measured increment on the
representative React tooltip consumer is 806 bytes.

### F-134 — Demo fixtures modeled charts instead of source data

Expand Down Expand Up @@ -3675,17 +3695,67 @@ Each entry records:
`@tanstack/charts/portable` for common authoring/runtime values plus
`@tanstack/charts/types` for portable contracts. DOM surface, renderer, host,
and render-context types now live behind an internal module while retaining
their existing root re-exports. Do not conditionally change the root until a
native host can test one coherent platform contract.
their existing root re-exports. Definition inputs retain DOM-free extension
token contracts while typed tooltip and portal lifecycles remain in the DOM
module. Do not conditionally change the root until a native host can test one
coherent platform contract.
- Verification: root typechecking and 61 focused core tests pass. The packed
package gate resolves both new entries from `dist`, compiles their
declarations with Web Worker rather than DOM globals, and proves the
portable bundle excludes the root, adapters, Canvas, DOM host/text, browser
export, reconciliation, renderer, and SVG surface modules. That full
portable barrel measures 55.26 kB minified and 17.04 kB gzip; granular
declarations, including tooltip definition inputs, with Web Worker rather
than DOM globals. Type regressions reject swapping tooltip and portal tokens.
The packed bundle proof excludes the root, adapters, Canvas, DOM host/text,
browser export, reconciliation, renderer, and SVG surface modules. That full
portable barrel measures 53.95 kB minified and 16.60 kB gzip; granular
subpaths remain the bundle-sensitive option.

### F-155 — Conformance monitoring blocked unrelated changes
### F-155 — Optional tooltip code burdened every chart consumer

- Status: resolved
- Severity: high
- Owner: API
- Observed in: reducing the representative React line consumer from 25.11 KiB
to 13.89 KiB gzip
- Friction: the renderer statically owned native tooltip DOM, formatting,
placement, pinning, portal transport, and observers. React's base entries
also statically owned `react-dom` portal composition and the default rich
body. A chart with no tooltip paid for all of it, and tree shaking could not
cross the host's built-in branches.
- Decision: keep focus and pin policy in the host, but move tooltip rendering
behind the `ChartTooltipExtension` lifecycle and exact
`@tanstack/charts/tooltip` token. Move viewport transport behind the nested
portal extension. Move React rich-body composition to drop-in Chart,
CanvasChart, and RendererChart exports from
`@tanstack/react-charts/tooltip`. Base entries export only erased extension
types and never import those runtime modules.
- Verification: the representative compact React line is 14,227 gzip bytes.
Native tooltip adds 3,381 bytes; portal adds 806 more. Retained-output graph
checks prove base renderer and React entries contain none of the tooltip,
portal, or React rich-body modules. Core, Lit, React, export, declaration,
packed-package, and lifecycle tests cover creation, update, disable,
transport switching, custom bodies, and cleanup.

### F-156 — Releases stranded manual Unreleased migration notes

- Status: resolved
- Severity: high
- Owner: Tooling/Release
- Observed in: rebasing the tooltip and compact-scale release onto the
Changesets-based `0.0.2` release flow
- Friction: the root changelog held the complete human- and agent-readable
migration under `## Unreleased`, while Changesets generated package sections
and prepended the new version without consuming that section. The resulting
release would leave the migration under an obsolete heading instead of the
version users were upgrading to.
- Decision: when synchronizing a new root release, move the body of
`## Unreleased` into the generated version section and remove the pending
heading. Package-specific changesets retain the core, React, and compact-scale
migration instructions in their published package changelogs.
- Verification: the focused changelog synchronization regression moves pending
breaking-change notes under the generated version, removes `## Unreleased`,
preserves earlier releases, and includes the migration in extracted GitHub
release notes.

### F-157 — Conformance monitoring blocked unrelated changes

- Status: resolved
- Severity: high
Expand Down
98 changes: 98 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,103 @@
# Changelog

## Unreleased

This section documents migration from `0.0.2` and supersedes tooltip examples
in the historical release entries below.

### Breaking changes

#### Tooltip extensions

Tooltips and tooltip portals are now explicit extensions:

```ts
import { tooltip, type ChartTooltipInput } from '@tanstack/charts/tooltip'
import { portal } from '@tanstack/charts/tooltip/portal'

interface Datum {
value: number
}

const configuredTooltip = {
use: tooltip,
portal,
format(point) {
return String(point.datum.value)
},
} satisfies ChartTooltipInput<Datum>
```

Apply these replacements to chart definition options:

| `0.0.2` input | `0.1.0` input |
| ------------------ | --------------------------------------- |
| `tooltip: true` | `tooltip` |
| `tooltip: false` | `tooltip: false` |
| `tooltip: enabled` | `tooltip: enabled ? tooltip : false` |
| `tooltip: options` | `tooltip: { use: tooltip, ...options }` |
| `portal: true` | `portal` |
| `portal: false` | Omit `portal` |
| `portal: enabled` | `portal: enabled ? portal : undefined` |

`portal` remains a property of a configured tooltip object. `ChartTooltipOptions`
still describes only the options after `use`; it does not contain the extension
discriminator. Type a complete value assigned to `definition.tooltip` as
`ChartTooltipInput`, or wrap reusable `ChartTooltipOptions` with
`{ use: tooltip, ...options }`. The object containing `use: tooltip` is the
contextual typing boundary for inline tooltip callbacks.

#### React tooltip bodies

React consumers that provide `renderTooltipBody` must move the component and
matching prop-type imports to `@tanstack/react-charts/tooltip`:

| `0.0.2` import | `0.1.0` component | `0.1.0` prop types |
| --------------------------------------- | ----------------- | ------------------------------------------------ |
| `@tanstack/react-charts` `Chart` | `Chart` | `ChartProps`, `ChartCommonProps` |
| `@tanstack/react-charts/canvas` `Chart` | `CanvasChart` | `CanvasChartProps`, `CanvasChartCommonProps` |
| `@tanstack/react-charts/core` `Chart` | `RendererChart` | `RendererChartProps`, `RendererChartCommonProps` |

Do not rename `/canvas` or `/core` imports to `/tooltip` while retaining the
name `Chart`; that name selects the default SVG component in the new entry. The
base React entries retain native tooltips without including React DOM's portal
runtime. Other framework adapters retain their existing entry points and only
require the chart-definition migration above.

### Added

- Added the optional compact scale package:

```sh
pnpm add @tanstack/charts-scales
```

Import one exact family; there is no `@tanstack/charts-scales` root export:

```ts
import { scaleLinear } from '@tanstack/charts-scales/linear'
import { scaleBand } from '@tanstack/charts-scales/band'
import { scalePoint } from '@tanstack/charts-scales/point'
import { scaleOrdinal } from '@tanstack/charts-scales/ordinal'
```

These scales are documented subsets, not complete D3 replacements. Compact
linear scales are numeric and two-stop. Use `d3-scale` for time, UTC, log,
power, symlog, radial, sequential, diverging, quantile, quantize, threshold,
piecewise or nonnumeric interpolation, locale-aware format specifiers, and
other full D3 behavior. Unsupported D3 behavior does not trigger a runtime
warning or automatic fallback. See [Scales and D3](docs/concepts/scales-and-d3.md#compact-scales)
for the compatibility boundary.

### Bundle impact

- The representative compact React line consumer is 14,227 bytes gzip
(13.89 KiB), down from 25,708 bytes gzip (25.11 KiB). React, the React JSX
runtime, and React DOM are external in both measurements.
- Opting into the tooltip extension produces 17,608 bytes gzip, an increase of
3,381 bytes.
- Adding the portal extension produces 18,414 bytes gzip, another 806 bytes.

## 0.0.2

### @tanstack/charts
Expand Down
Loading