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
5 changes: 5 additions & 0 deletions .changeset/drillable-sunburst-motion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/charts': minor
---

Add controlled sunburst drill-down roots, bounded visible depth, and hierarchy-aware polar motion that keeps animated sectors centered. Add funnel and drillable sunburst catalog examples.
90 changes: 89 additions & 1 deletion 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-08-10
Last updated: 2026-08-11

## Triage rule

Expand Down Expand Up @@ -300,6 +300,10 @@ Each entry records:
| F-261 | Cartesian bars cannot round only exposed corners | API | open |
| F-262 | Mark inference accepted an unsupported style option | API | open |
| F-263 | Chromium transport suspension interrupted catalog previews | Tooling | resolved |
| F-264 | Drillable sunbursts required rebuilding hierarchy rows | API/Documentation | resolved |
| F-265 | Sunburst motion lost hierarchy across enter and exit | API | resolved |
| F-266 | Path-token motion distorted polar sectors | API/Tooling | resolved |
| F-267 | Stress timeouts entered a class temporal dead zone | Tooling | resolved |

## Findings

Expand Down Expand Up @@ -7804,3 +7808,87 @@ Each entry records:
failures, and retained repeated-failure evidence. A full browser-backed run
generated all 115 previews in both themes without changing any SVG asset;
only the source hash changed.

### F-264 — Drillable sunbursts required rebuilding hierarchy rows

- Status: resolved
- Severity: high
- Owner: API/Documentation
- Observed in: drillable Flare sunburst catalog case 126
- Friction: focusing a branch while limiting visible rings required the
application to filter and re-parent flat rows, re-aggregate boundary values,
and preserve canonical IDs manually. That duplicated hierarchy work already
owned by the mark and made animated continuity depend on case preparation.
- Decision: add `rootId` and `visibleDepth` to the optional `sunburst` mark.
The mark copies the selected hierarchy node as its structural layout root,
retains the complete hierarchy for aggregation and internal-node metadata,
and keeps canonical node keys across root changes. Application state still
owns selection, breadcrumbs, and drill-up controls.
- Verification: focused mark tests cover relative depth, hidden descendant
aggregation, stable keys, validation, and ring allocation. Case 126 passes
the full flat Flare source directly, limits the displayed window in the mark,
and verifies that one retained leaf path interpolates from the outer ring to
the inner ring during a root update.

### F-265 — Sunburst motion lost hierarchy across enter and exit

- Status: resolved
- Severity: high
- Owner: API
- Observed in: drill-down and drill-up transitions in the Flare sunburst case
- Friction: stable keys animated nodes visible under both roots, but newly
revealed descendants had no identity in common with their disappearing
parent. Generic enter and exit opacity made a hierarchy change look
intermittent even though the retained-node paths were moving.
- Decision: sunburst sectors carry an internal ancestry relationship into the
motion scene. An entering descendant begins at the live geometry of its
nearest disappearing ancestor, and an exiting descendant collapses into the
live geometry of its nearest appearing ancestor. Unrelated nodes keep the
normal enter and exit behavior, and reduced-motion updates still snap.
- Verification: case 126 asserts the initial, intermediate, and final path for
retained descendants, drill-down entries, and drill-up exits. The live
catalog case confirms the entering `cluster` sector begins in the departing
`analytics` sector and separates over the authored tween.

### F-266 — Path-token motion distorted polar sectors

- Status: resolved
- Severity: high
- Owner: API/Tooling
- Observed in: retained, entering, and exiting arcs in the drillable Flare
sunburst case
- Friction: generic SVG path interpolation paired numeric tokens from two `d`
strings. Intermediate endpoints therefore left their common circles, making
sectors skew around the chart even though both endpoint layouts were valid
concentric arcs.
- Decision: optional marks may attach an opaque numeric geometry vector and a
stable projector to a scene path. Sunburst supplies start angle, end angle,
inner radius, and outer radius; motion interpolates those four values and the
sunburst-owned projector regenerates a valid sector each frame. The shared
contract contains no polar or D3 import, so ordinary motion consumers retain
none of the hierarchy implementation.
- Verification: focused temporal tests measure every intermediate outer and
inner endpoint against its declared radius for retained, entering, and
exiting sectors. Interrupted transitions retain their live numeric state,
reduced motion snaps, and retained-input bundle gates require only the small
scene-motion contract while forbidding sunburst, hierarchy, polar-sector,
d3-shape, and d3-path inputs from the isolated motion bundle.

### F-267 — Stress timeouts entered a class temporal dead zone

- Status: resolved
- Severity: high
- Owner: Tooling
- Observed in: release pull request stress partition 1
- Friction: the stress runner began its top-level browser workload before
evaluating a later `CellTimeoutError` class declaration. When one cell
reached the intended 120-second outer limit, the timeout callback threw a
`ReferenceError` instead of returning the retryable timeout result, aborting
the complete partition before its fresh-context retry.
- Decision: define the timeout error in an imported benchmark module. Module
dependencies finish evaluation before the stress runner starts top-level
work, so the timeout path cannot observe an uninitialized class.
- Verification: the focused timeout regression constructs the imported error
with the expected prototype, name, and duration message. The retry suite,
stress-runner syntax check, full repository validation, and rerun GitHub
stress partition pass.
8 changes: 4 additions & 4 deletions benchmarks/comparison/bundle-baseline.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"schemaVersion": 4,
"generatedAt": "2026-08-10T22:52:47.426Z",
"generatedAt": "2026-08-11T00:43:02.982Z",
"packageVersions": {
"tanstack": "0.9.0",
"tanstack": "0.10.0",
"chartjs": "4.5.1",
"echarts": "6.1.0",
"recharts": "3.10.1",
Expand All @@ -11,8 +11,8 @@
"sources": {
"tanstack": {
"kind": "workspace",
"revision": "49b9f1e00567e6b2ef0ec77850befc96c1e7e7fb",
"inputDigest": "sha256:765b23a32b175d1f52e9b655a571c0334501d482c3d981fd181fe83d4c6cf5bd"
"revision": "7ac3c321ff253f51d2c7df7be6a63c6edb7b771f",
"inputDigest": "sha256:44f03725e05260cba794012df73385ad7df1ccd9a7cd44ad2e560a05ebf682e7"
},
"chartjs": {
"kind": "package",
Expand Down
22 changes: 12 additions & 10 deletions benchmarks/conformance/DEFINITION-COVERAGE-AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

Date: 2026-08-10

Scope: all 115 catalog directories. The 67 cases previously classified as
Scope: all 117 catalog directories. The 67 cases previously classified as
strict custom authoring, preparation review, or shell-only are reviewed beside
the former 42-case definition-native control group from
[the custom authoring audit](./CUSTOM-AUTHORING-AUDIT.md). That audit remains
the historical before-state; Cases 119–124 were added afterward and are
the historical before-state; Cases 119–126 were added afterward and are
classified by the same ownership test. This document records the current
disposition of every case.

Expand All @@ -16,10 +16,10 @@ live in the [definition coverage plan](./DEFINITION-COVERAGE-PLAN.md) and its

## Decision

One hundred twelve of the 115 cases present their visualization as a normal
chart definition. Sixty-three use the definition API without a new visualization
One hundred fourteen of the 117 cases present their visualization as a normal
chart definition. Sixty-four use the definition API without a new visualization
primitive. Thirty-five use a reusable first-party primitive, including Case
70's final-scale bar thickness cap. Fourteen use a tree-shakeable first-party
70's final-scale bar thickness cap. Fifteen use a tree-shakeable first-party
adapter around a heavier layout or gesture algorithm.

Only two application shells and one custom mark justify case-owned custom
Expand All @@ -31,12 +31,12 @@ work:

| Disposition | Cases | Meaning |
| --------------------- | ------: | ------------------------------------------------------------------------ |
| Definition now | 63 | Current marks and eager transforms are sufficient |
| Definition now | 64 | Current marks and eager transforms are sufficient |
| First-party primitive | 35 | Add a reusable mark, transform, layout, guide, or controlled signal |
| Optional primitive | 14 | Keep a heavy dependency granular, but hide its layout DTOs and lifecycle |
| Optional primitive | 15 | Keep a heavy dependency granular, but hide its layout DTOs and lifecycle |
| Application boundary | 2 | The remaining work is product state, DOM layout, or data arrival |
| Inline custom mark | 1 | The geometry is intentionally case-specific |
| **Total** | **115** | |
| **Total** | **117** | |

A normal definition does not require every implementation algorithm to live
in Charts core. D3 may implement an optional `sankeyDiagram`, `densityContour`,
Expand Down Expand Up @@ -249,6 +249,8 @@ ownership boundary.
| [122 — KPI sparklines](./cases/122-premium-kpi-sparklines/chart.ts) | Definition now | Three guide-free definitions use ordinary area and line marks, gradients, CSS-variable paint, and keyed spring updates; metric copy and responsive card layout remain application content. |
| [123 — Active donut](./cases/123-active-donut-metric/chart.ts) | Definition now | Native pie allocation, rounded arcs, selected wedge and ring layers, center text, tooltip, and spring motion own the visualization; legend buttons own persistent selection. |
| [124 — Theme matrix](./cases/124-theme-palette-matrix/chart.ts) | Definition now | The same area-and-line definition is rendered through three scoped CSS-variable themes with identical geometry; only the three-card matrix and labels are application composition. |
| [125 — Sales funnel](./cases/125-sales-funnel/tanstack.ts) | Definition now | Derive centered stage boundaries from the ordered raw values, then compose one native `areaX` group per stage with direct labels and stable semantic keys. |
| [126 — Drillable sunburst](./cases/126-drillable-sunburst/tanstack.ts) | Optional primitive | The optional sunburst mark owns focused-root partitioning, bounded visible depth, hierarchy-aware polar motion, and stable node geometry; the application owns selected-root state and the center back control. |

## Reference evidence

Expand Down Expand Up @@ -280,8 +282,8 @@ bundle.

## Delivery result

All 115 catalog directories now have one roadmap record and case-local
evidence. All 112 normal-definition cases are verified against their current
All 117 catalog directories now have one roadmap record and case-local
evidence. All 114 normal-definition cases are verified against their current
boundary; only cases 85, 86, and 116 retain accepted application or bespoke
geometry work. The roadmap validator compares its IDs with the live catalog
directories so a new case cannot silently remain outside this review.
14 changes: 8 additions & 6 deletions benchmarks/conformance/DEFINITION-COVERAGE-OVERVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,27 @@

Date: 2026-08-10

The catalog has 115 case directories. One hundred twelve visualizations now use
The catalog has 117 case directories. One hundred fourteen visualizations now use
normal chart definitions. Cases 85 and 86 retain application shells, and Case
116 retains one deliberately bespoke inline mark. No other case-owned layout
or renderer is an accepted endpoint.

| Disposition | Cases | Result |
| --------------------- | ------: | ------------------------------------------------- |
| Definition now | 63 | Normal definition |
| Definition now | 64 | Normal definition |
| First-party primitive | 35 | Normal definition |
| Optional primitive | 14 | Normal definition |
| Optional primitive | 15 | Normal definition |
| Application boundary | 2 | Accepted boundary |
| Inline custom mark | 1 | Accepted boundary |
| **Total** | **115** | **112 normal definitions; 3 accepted boundaries** |
| **Total** | **117** | **114 normal definitions; 3 accepted boundaries** |

## Shared decisions

| Seam | Cases | Decision |
| ---------------------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Eager typed transforms | 02, 14, 18–22, 24, 26–31, 50–64, 71, 75, 78, 94, 96, 98–99, grouped and stacked bars | Keep row work eager and visible beside its consuming marks; do not add a chart-owned reactive preparation graph. |
| Resolved mark layout | 33, 37, 39, 41, 43, 52, 74, both 111 cases | Schedule only work that needs final scales or bounds. Reuse internal projection, grouping, child adoption, and composition contracts without exposing a universal layout callback. |
| Spatial, hierarchy, and network adapters | 36–39, 40-force, 43, 65, 74, 101, both 111 cases | Keep heavy algorithms in exact optional subpaths while definitions accept semantic source rows. |
| Spatial, hierarchy, and network adapters | 36–39, 40-force, 43, 65, 74, 101, both 111 cases, 126 | Keep heavy algorithms in exact optional subpaths while definitions accept semantic source rows. |
| Composite and distribution marks | 15, 31, 33, 62–64 | Couple statistics and geometry only where they form a stable named contract; otherwise keep public transforms explicit. |
| Polar allocation and radial geometry | 75–78, 93–100 | Share value allocation, label anchoring, sector geometry, and radial bars. Case-specific selection and presentation remain explicit. |
| View composition | 57, 87 | Use composed child definitions when tracks truly share one chart lifecycle. Case 83 correctly retains two hosts because detail and overview differ in data, domains, axes, margins, sizes, and behavior. |
Expand Down Expand Up @@ -63,7 +63,7 @@ presentation policy.

## Verification

- The roadmap validator compares all 115 roadmap and audit IDs with the live
- The roadmap validator compares all 117 roadmap and audit IDs with the live
catalog directories, requires unique IDs, validates the capability DAG, and
requires every verified or accepted case to cite evidence under its own
directory.
Expand Down Expand Up @@ -187,6 +187,8 @@ presentation policy.
| [122-premium-kpi-sparklines — Premium KPI sparklines](./cases/122-premium-kpi-sparklines/chart.ts) | Definition now | `current-definition-api` | `chart.ts`, `tanstack.test.ts`, `view.tsx` |
| [123-active-donut-metric — Active donut metric](./cases/123-active-donut-metric/chart.ts) | Definition now | `current-definition-api` | `chart.ts`, `tanstack.test.ts`, `view.tsx` |
| [124-theme-palette-matrix — Theme palette matrix](./cases/124-theme-palette-matrix/chart.ts) | Definition now | `current-definition-api` | `chart.ts`, `tanstack.test.ts`, `view.tsx` |
| [125-sales-funnel — Sales conversion funnel](./cases/125-sales-funnel/tanstack.ts) | Definition now | `current-definition-api` | `tanstack.ts`, `model.test.ts`, `case.json` |
| [126-drillable-sunburst — Drillable Flare sunburst](./cases/126-drillable-sunburst/tanstack.ts) | Optional primitive | `hierarchy-sunburst` | `tanstack.ts`, `tanstack.test.ts`, `model.test.ts` |
| [bar-grouped — Grouped bars](./cases/bar-grouped/tanstack.ts) | Definition now | `current-definition-api` | `tanstack.ts` |
| [bar-horizontal-ranking — Horizontal ranking with long labels](./cases/bar-horizontal-ranking/tanstack.ts) | Definition now | `current-definition-api` | `tanstack.ts` |
| [bar-stacked — Stacked bars](./cases/bar-stacked/tanstack.ts) | Definition now | `current-definition-api` | `tanstack.ts` |
Expand Down
Loading