Skip to content
Open
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/print-width-redraw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/charts': patch
---

Redraw container-responsive charts synchronously when the page enters or leaves print layout, so printed pages and `page.pdf()` exports use the print container width instead of a scaled screen drawing.
49 changes: 48 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-09-30
Last updated: 2026-10-14

## Triage rule

Expand Down Expand Up @@ -351,6 +351,8 @@ Each entry records:
| F-312 | Tick stubs could not be styled independently | API | resolved |
| F-313 | Initial observer precision cancelled entrance motion | Tooling | resolved |
| F-314 | Framework ticks and immediate focus layers were unclear | Documentation | resolved |
| F-315 | Printed charts kept their screen width | API | resolved |
| F-316 | Catalog previews depend on the local time zone | Tooling | resolved |

## Findings

Expand Down Expand Up @@ -9339,3 +9341,48 @@ Each entry records:
direction changes. A focus dot appears during entrance while the data path
and its running animation remain intact. Concurrent inline states remain a
separate feature; this addresses the documented alternatives requested.

### F-315 - Printed charts kept their screen width

- Status: resolved
- Severity: medium
- Owner: API
- Observed in: a production React application that prints reports and exports
them with Playwright `page.pdf()`. The print layout narrowed the container,
but the resize redraw waited for an animation frame that print never runs.
The browser scaled the screen drawing down, shrinking text and strokes and
letterboxing fixed-height charts.
- Workaround: each application listened for the `print` media change, measured
the container, and passed it as a fixed `width` inside React `flushSync`, then
removed `width` when print ended.
- Decision: the shared DOM host listens for the `print` media change, discards
the last observer size, measures the container, and redraws synchronously
without motion when a container-owned dimension changed. A pending resize
frame later finds the size unchanged and skips. All adapters inherit it. No
public `resize()` was added; no task has needed one yet.
- Verification: a host regression narrows the container from 972 to 420 pixels
and redraws in the same task when the print media changes, before any frame
runs. Leaving print restores 972 despite a stale 420 observer size, unchanged
sizes do not redraw, and `destroy` removes the listener. The test fails
without the change. In Chromium, the `34-pointer-tooltip` first-party
conformance scenario `print-width` holds animation frames, enters print media
with a 420 pixel container, and requires a 420 pixel `viewBox`, then restores
the screen width after print. It passes in all 12 variants and fails in all
12 on the previous renderer.

### F-316 - Catalog previews depend on the local time zone

- Status: resolved
- Severity: low
- Owner: Tooling
- Observed in: refreshing the preview fingerprint for the print redraw change.
`pnpm catalog:previews` in Europe/Warsaw, and again with `TZ=UTC`, shifted
the weekday labels of nine shadcn tooltip previews by one day. The committed
previews had been generated west of UTC.
- Workaround: keep the committed SVGs and their manifest records, and commit
only the new `sourceHash`.
- Decision: #230 pins preview generation to UTC and regenerates the affected
previews.
- Verification: on top of #230, `pnpm catalog:previews` in Europe/Warsaw
reproduced all 188 SVGs from `main` byte for byte. Only `sourceHash` changed,
and `pnpm catalog:previews:check` passes.
16 changes: 8 additions & 8 deletions benchmarks/bundle-size/universal-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,24 @@
"gzip": 30414
},
"TanStack DOM host": {
"bytes": 82994,
"gzip": 29119
"bytes": 83137,
"gzip": 29176
},
"React adapter": {
"bytes": 85319,
"gzip": 30074
"bytes": 85462,
"gzip": 30122
},
"React line consumer": {
"bytes": 108978,
"gzip": 39424
"bytes": 109121,
"gzip": 39446
},
"Compact-scale line scene": {
"bytes": 35859,
"gzip": 12797
},
"React compact-scale line consumer": {
"bytes": 91476,
"gzip": 32307
"bytes": 91619,
"gzip": 32356
},
"Custom-scale line scene": {
"bytes": 34043,
Expand Down
126 changes: 63 additions & 63 deletions benchmarks/comparison/bundle-baseline.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schemaVersion": 4,
"generatedAt": "2026-10-14T04:50:28.604Z",
"generatedAt": "2026-10-16T16:48:15.255Z",
"packageVersions": {
"tanstack": "1.1.0",
"chartjs": "4.5.1",
Expand All @@ -11,8 +11,8 @@
"sources": {
"tanstack": {
"kind": "workspace",
"revision": "69d70960dd6b29f3706a173d348f2fc8271b0dcc",
"inputDigest": "sha256:80f5c5447f39757715be5def33eed28b12869946936369671243436701fb7964"
"revision": "255db65bc31609387da2bac974f4aa7df0810280",
"inputDigest": "sha256:c0840e905be2f2eac417b4d3bce67050f63c999403e50a78eac6a7f11d0b3674"
},
"chartjs": {
"kind": "package",
Expand Down Expand Up @@ -45,88 +45,88 @@
},
"bundles": {
"tanstack-line-basic": {
"minifiedBytes": 119266,
"gzipBytes": 43213,
"brotliBytes": 38182,
"incrementalGzipBytes": 43213,
"incrementalBrotliBytes": 38182
"minifiedBytes": 119409,
"gzipBytes": 43265,
"brotliBytes": 38232,
"incrementalGzipBytes": 43265,
"incrementalBrotliBytes": 38232
},
"tanstack-line-interactive": {
"minifiedBytes": 125013,
"gzipBytes": 45122,
"brotliBytes": 39724,
"incrementalGzipBytes": 45122,
"incrementalBrotliBytes": 39724
"minifiedBytes": 125160,
"gzipBytes": 45182,
"brotliBytes": 39762,
"incrementalGzipBytes": 45182,
"incrementalBrotliBytes": 39762
},
"tanstack-line-advanced": {
"minifiedBytes": 132209,
"gzipBytes": 47460,
"brotliBytes": 41713,
"incrementalGzipBytes": 47460,
"incrementalBrotliBytes": 41713
"minifiedBytes": 132352,
"gzipBytes": 47502,
"brotliBytes": 41858,
"incrementalGzipBytes": 47502,
"incrementalBrotliBytes": 41858
},
"tanstack-bar-basic": {
"minifiedBytes": 131703,
"gzipBytes": 47916,
"brotliBytes": 42081,
"incrementalGzipBytes": 47916,
"incrementalBrotliBytes": 42081
"minifiedBytes": 131846,
"gzipBytes": 47971,
"brotliBytes": 42179,
"incrementalGzipBytes": 47971,
"incrementalBrotliBytes": 42179
},
"tanstack-bar-interactive": {
"minifiedBytes": 136309,
"gzipBytes": 49311,
"brotliBytes": 43248,
"incrementalGzipBytes": 49311,
"incrementalBrotliBytes": 43248
"minifiedBytes": 136452,
"gzipBytes": 49358,
"brotliBytes": 43285,
"incrementalGzipBytes": 49358,
"incrementalBrotliBytes": 43285
},
"tanstack-bar-advanced": {
"minifiedBytes": 136648,
"gzipBytes": 49455,
"brotliBytes": 43379,
"incrementalGzipBytes": 49455,
"incrementalBrotliBytes": 43379
"minifiedBytes": 136791,
"gzipBytes": 49496,
"brotliBytes": 43465,
"incrementalGzipBytes": 49496,
"incrementalBrotliBytes": 43465
},
"tanstack-area-basic": {
"minifiedBytes": 124446,
"gzipBytes": 45243,
"brotliBytes": 39958,
"incrementalGzipBytes": 45243,
"incrementalBrotliBytes": 39958
"minifiedBytes": 124589,
"gzipBytes": 45307,
"brotliBytes": 40026,
"incrementalGzipBytes": 45307,
"incrementalBrotliBytes": 40026
},
"tanstack-area-interactive": {
"minifiedBytes": 130201,
"gzipBytes": 47092,
"brotliBytes": 41523,
"incrementalGzipBytes": 47092,
"incrementalBrotliBytes": 41523
"minifiedBytes": 130344,
"gzipBytes": 47139,
"brotliBytes": 41579,
"incrementalGzipBytes": 47139,
"incrementalBrotliBytes": 41579
},
"tanstack-area-advanced": {
"minifiedBytes": 137573,
"gzipBytes": 49501,
"brotliBytes": 43577,
"incrementalGzipBytes": 49501,
"incrementalBrotliBytes": 43577
"minifiedBytes": 137716,
"gzipBytes": 49551,
"brotliBytes": 43615,
"incrementalGzipBytes": 49551,
"incrementalBrotliBytes": 43615
},
"tanstack-scatter-basic": {
"minifiedBytes": 120319,
"gzipBytes": 43574,
"brotliBytes": 38528,
"incrementalGzipBytes": 43574,
"incrementalBrotliBytes": 38528
"minifiedBytes": 120462,
"gzipBytes": 43637,
"brotliBytes": 38573,
"incrementalGzipBytes": 43637,
"incrementalBrotliBytes": 38573
},
"tanstack-scatter-interactive": {
"minifiedBytes": 126070,
"gzipBytes": 45472,
"brotliBytes": 40050,
"incrementalGzipBytes": 45472,
"incrementalBrotliBytes": 40050
"minifiedBytes": 126213,
"gzipBytes": 45544,
"brotliBytes": 40150,
"incrementalGzipBytes": 45544,
"incrementalBrotliBytes": 40150
},
"tanstack-scatter-advanced": {
"minifiedBytes": 126086,
"gzipBytes": 45479,
"brotliBytes": 40072,
"incrementalGzipBytes": 45479,
"incrementalBrotliBytes": 40072
"minifiedBytes": 126229,
"gzipBytes": 45549,
"brotliBytes": 40099,
"incrementalGzipBytes": 45549,
"incrementalBrotliBytes": 40099
},
"chartjs-line-basic": {
"minifiedBytes": 137909,
Expand Down
33 changes: 33 additions & 0 deletions benchmarks/conformance/cases/34-pointer-tooltip/case.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,39 @@
"assertions": [{ "path": "tooltip.visible", "equals": false }]
}
]
},
{
"id": "print-width",
"renderers": ["tanstack"],
"steps": [
{ "type": "printStart", "width": 420 },
{
"type": "assertRendered",
"assertions": [
{
"target": { "selector": "svg.ts-chart[viewBox^='0 0 420 ']" },
"property": "count",
"equals": 1
}
]
},
{ "type": "printEnd" },
{
"type": "assertRendered",
"assertions": [
{
"target": { "selector": "svg.ts-chart[viewBox^='0 0 420 ']" },
"property": "count",
"equals": 0
},
{
"target": { "selector": "svg.ts-chart" },
"property": "count",
"equals": 1
}
]
}
]
}
],
"source": {
Expand Down
3 changes: 1 addition & 2 deletions benchmarks/conformance/cases/34-pointer-tooltip/tanstack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function mount(
let focusedDates: string[] = []
const options: ChartHostOptions<AaplRow> = {
definition: createExampleChart(input),
width: input.width,
// The runner sizes the container, so its width also drives print layout.
height: input.height,
ariaLabel: exampleAriaLabel,
onFocusGroupChange(points) {
Expand Down Expand Up @@ -87,7 +87,6 @@ export function mount(
host.update({
...options,
definition: createExampleChart(nextInput),
width: nextInput.width,
height: nextInput.height,
})
},
Expand Down
44 changes: 44 additions & 0 deletions benchmarks/conformance/catalog-index.json
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,50 @@
]
}
]
},
{
"id": "print-width",
"renderers": ["tanstack"],
"steps": [
{
"type": "printStart",
"width": 420
},
{
"type": "assertRendered",
"assertions": [
{
"target": {
"selector": "svg.ts-chart[viewBox^='0 0 420 ']"
},
"property": "count",
"equals": 1
}
]
},
{
"type": "printEnd"
},
{
"type": "assertRendered",
"assertions": [
{
"target": {
"selector": "svg.ts-chart[viewBox^='0 0 420 ']"
},
"property": "count",
"equals": 0
},
{
"target": {
"selector": "svg.ts-chart"
},
"property": "count",
"equals": 1
}
]
}
]
}
],
"source": {
Expand Down
Loading