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
6 changes: 6 additions & 0 deletions .changeset/accurate-svg-pointers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@tanstack/charts': patch
---

Correct SVG pointer hit testing when the rendered viewport and chart scene use
different aspect ratios.
102 changes: 101 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-01
Last updated: 2026-08-02

## Triage rule

Expand Down Expand Up @@ -226,6 +226,10 @@ Each entry records:
| F-188 | Paired interaction assertions assumed equal timing | Tooling | monitoring |
| F-189 | The motion spike exposed duplicate configuration surfaces | API | resolved |
| F-190 | Static conformance sampled active motion | Tooling | resolved |
| F-191 | Axis tick styling and edge alignment required shell work | API/Application | monitoring |
| F-192 | SVG letterboxing shifted pointer hit testing | API | resolved |
| F-193 | Fixed catalog height hid compact responsive examples | Tooling/App | resolved |
| F-194 | Behavior runs omitted the interactive input | Tooling | resolved |

## Findings

Expand Down Expand Up @@ -4742,3 +4746,99 @@ Each entry records:
- Verification: the quick 112–117 browser matrix passes all six visual cases,
the focus/crosshair scenario, both revisions, and 320/640px viewports. Mean
final-frame geometry similarity is 94.2%, with clean strict types.

### F-191 — Axis tick styling and edge alignment required shell work

- Status: monitoring
- Severity: low
- Owner: API/Application
- Observed in: matching the token activity calendar to a supplied visual
reference
- Friction: the reference required larger, quieter month labels than the
default axis typography. Axis presentation exposes tick values, formatting,
spacing, thinning, size, and padding, but not label font size or opacity. The
example therefore needed a shell-scoped `.ts-chart__axes text` rule to reach
the requested presentation. It also needed the first label to align with the
painted calendar's leading edge, while every unrotated band tick label
currently uses a middle anchor with no per-tick anchor or offset. The shell
measures the first cell after each render and adjusts only that generated
label; the final label stays at its month position so the preceding gap does
not widen. The cell mark also defaults to a 0.75-pixel inset, so omitting the
authored inset still recessed the first and final columns from a flush scale
range.
- Current decision: keep the override local to the application shell and avoid
expanding the public axis API from one styling and edge-alignment case. Use
an explicit zero cell inset with zero band outer padding and horizontal chart
margins when flush calendar edges are intended.
Revisit if production migrations or unrelated examples repeat the need for
authored tick-label typography or per-tick anchoring.
- Verification: browser inspection at gallery widths confirms twelve 13px
month labels, with Aug starting at the first cell edge and Jul retaining its
normal month position, 364 approximately square daily cells, and no
application, page, or overlay errors. Workspace typecheck and the focused
quick conformance matrix pass initial and updated data at 320px and 640px.

### F-192 — SVG letterboxing shifted pointer hit testing

- Status: resolved
- Severity: high
- Owner: API
- Observed in: daily token usage calendar tooltip verification
- Friction: the SVG surface converted browser pointer coordinates with the
element's complete bounding rectangle. When the responsive viewport and
scene had different aspect ratios, SVG's default `xMidYMid meet` transform
added letterboxing that the conversion ignored. A 640-by-480 scene in the
604-by-480 gallery viewport therefore resolved calendar cells roughly one
weekday row below the pointer near the top of the chart.
- Decision: convert client coordinates through the inverse SVG screen matrix.
This delegates view-box, aspect-ratio, CSS transform, and viewport placement
semantics to the browser instead of duplicating them with bounding-rectangle
arithmetic. Preserve out-of-scene coordinates for overflowing marks and
retain the previous bounds conversion only for incomplete DOM
implementations such as jsdom, which do not expose `getScreenCTM`.
- Verification: unit regressions reproduce the gallery dimensions and its
13.5-pixel vertical letterbox, verify the exact scene coordinate, and cover
the incomplete-DOM fallback. Browser conformance forces a mismatched SVG
viewport and proves the hovered cell retains its expected tooltip. The SVG
surface, renderer, and workspace tests pass; the reviewed shared-path cost is
recorded in the updated universal bundle baseline.

### F-193 — Fixed catalog height hid compact responsive examples

- Status: resolved
- Severity: low
- Owner: Tooling/Application
- Observed in: calendar heatmap responsive sizing
- Friction: the calendar could derive a compact height from its available width
and preserve square day cells, but the catalog renderer retained a generic
480-pixel minimum height. The resulting blank panel made the example appear
fixed-height even after its SVG had correctly shrunk.
- Decision: keep the global catalog sizing contract unchanged for charts that
need the full benchmark height. The calendar shell temporarily sets its host
minimum height to the width-derived chart height and restores the previous
value when destroyed. Width remains fully fluid; no example-specific maximum
is imposed.
- Verification: shell tests cover fluid 320- and 960-pixel widths plus teardown.
Browser measurements confirm square day cells at each width, with no
horizontal overflow or fixed maximum-width behavior.

### F-194 — Behavior runs omitted the interactive input

- Status: resolved
- Severity: medium
- Owner: Tooling
- Observed in: automating the SVG letterbox pointer regression
- Friction: `ConformanceInput` exposed an `interactive` flag and catalog embeds
supplied it, but the browser behavior runner did not. Examples therefore had
to force interactive behavior at definition setup and could not scope a
viewport mismatch to semantic interaction checks without affecting static
visual measurements.
- Decision: mark behavior-run inputs as interactive and expose a separate
`behavior` flag for interaction-only layout. Remove the token calendar's
duplicated always-interactive shell option.
- Verification: the pointer-tooltip scenario creates a 120-pixel SVG viewport
mismatch only during the token calendar's behavior runs, targets the painted
Aug 3 cell, and asserts the exact focused date and tooltip. The fixed runtime
passes at 320 and 640 pixels across both revisions; reverting the runtime fix
makes the same scenario report Aug 5 or Aug 6. Static visual measurements and
public catalog mounts retain their original dimensions.
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": 21632
},
"TanStack DOM host": {
"bytes": 44394,
"gzip": 16197
"bytes": 44570,
"gzip": 16278
},
"React adapter": {
"bytes": 46562,
"gzip": 16900
"bytes": 46738,
"gzip": 16971
},
"React line consumer": {
"bytes": 69228,
"gzip": 25972
"bytes": 69404,
"gzip": 26052
},
"Compact-scale line scene": {
"bytes": 22019,
"gzip": 8326
},
"React compact-scale line consumer": {
"bytes": 51739,
"gzip": 18875
"bytes": 51915,
"gzip": 18954
},
"Custom-scale line scene": {
"bytes": 20204,
Expand Down
126 changes: 63 additions & 63 deletions benchmarks/comparison/bundle-baseline.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"schemaVersion": 3,
"generatedAt": "2026-08-02T18:36:02.890Z",
"generatedAt": "2026-08-02T19:11:27.130Z",
"packageVersions": {
"tanstack": "0.5.1",
"tanstack": "0.6.0",
"chartjs": "4.5.1",
"echarts": "6.1.0",
"recharts": "3.10.1",
Expand All @@ -11,7 +11,7 @@
"sources": {
"tanstack": {
"kind": "workspace",
"revision": "515da25d87b2c2eb4ded774ce45e4050e03bf5e7"
"revision": "eaa96a340cde6f07a1b7edb6a111e2a52d7d1c03"
},
"chartjs": {
"kind": "package",
Expand Down Expand Up @@ -44,88 +44,88 @@
},
"bundles": {
"tanstack-line-basic": {
"minifiedBytes": 78035,
"gzipBytes": 29392,
"brotliBytes": 26029,
"incrementalGzipBytes": 29392,
"incrementalBrotliBytes": 26029
"minifiedBytes": 78211,
"gzipBytes": 29465,
"brotliBytes": 26139,
"incrementalGzipBytes": 29465,
"incrementalBrotliBytes": 26139
},
"tanstack-line-interactive": {
"minifiedBytes": 83206,
"gzipBytes": 31035,
"brotliBytes": 27376,
"incrementalGzipBytes": 31035,
"incrementalBrotliBytes": 27376
"minifiedBytes": 83382,
"gzipBytes": 31104,
"brotliBytes": 27459,
"incrementalGzipBytes": 31104,
"incrementalBrotliBytes": 27459
},
"tanstack-line-advanced": {
"minifiedBytes": 90391,
"gzipBytes": 33385,
"brotliBytes": 29378,
"incrementalGzipBytes": 33385,
"incrementalBrotliBytes": 29378
"minifiedBytes": 90567,
"gzipBytes": 33457,
"brotliBytes": 29507,
"incrementalGzipBytes": 33457,
"incrementalBrotliBytes": 29507
},
"tanstack-bar-basic": {
"minifiedBytes": 85046,
"gzipBytes": 32063,
"brotliBytes": 28287,
"incrementalGzipBytes": 32063,
"incrementalBrotliBytes": 28287
"minifiedBytes": 85222,
"gzipBytes": 32128,
"brotliBytes": 28411,
"incrementalGzipBytes": 32128,
"incrementalBrotliBytes": 28411
},
"tanstack-bar-interactive": {
"minifiedBytes": 89072,
"gzipBytes": 33257,
"brotliBytes": 29274,
"incrementalGzipBytes": 33257,
"incrementalBrotliBytes": 29274
"minifiedBytes": 89248,
"gzipBytes": 33326,
"brotliBytes": 29367,
"incrementalGzipBytes": 33326,
"incrementalBrotliBytes": 29367
},
"tanstack-bar-advanced": {
"minifiedBytes": 89411,
"gzipBytes": 33389,
"brotliBytes": 29442,
"incrementalGzipBytes": 33389,
"incrementalBrotliBytes": 29442
"minifiedBytes": 89587,
"gzipBytes": 33458,
"brotliBytes": 29488,
"incrementalGzipBytes": 33458,
"incrementalBrotliBytes": 29488
},
"tanstack-area-basic": {
"minifiedBytes": 81844,
"gzipBytes": 30875,
"brotliBytes": 27354,
"incrementalGzipBytes": 30875,
"incrementalBrotliBytes": 27354
"minifiedBytes": 82020,
"gzipBytes": 30953,
"brotliBytes": 27423,
"incrementalGzipBytes": 30953,
"incrementalBrotliBytes": 27423
},
"tanstack-area-interactive": {
"minifiedBytes": 87023,
"gzipBytes": 32562,
"brotliBytes": 28759,
"incrementalGzipBytes": 32562,
"incrementalBrotliBytes": 28759
"minifiedBytes": 87199,
"gzipBytes": 32637,
"brotliBytes": 28802,
"incrementalGzipBytes": 32637,
"incrementalBrotliBytes": 28802
},
"tanstack-area-advanced": {
"minifiedBytes": 94390,
"gzipBytes": 34858,
"brotliBytes": 30726,
"incrementalGzipBytes": 34858,
"incrementalBrotliBytes": 30726
"minifiedBytes": 94566,
"gzipBytes": 34929,
"brotliBytes": 30869,
"incrementalGzipBytes": 34929,
"incrementalBrotliBytes": 30869
},
"tanstack-scatter-basic": {
"minifiedBytes": 77710,
"gzipBytes": 29291,
"brotliBytes": 25985,
"incrementalGzipBytes": 29291,
"incrementalBrotliBytes": 25985
"minifiedBytes": 77886,
"gzipBytes": 29367,
"brotliBytes": 26035,
"incrementalGzipBytes": 29367,
"incrementalBrotliBytes": 26035
},
"tanstack-scatter-interactive": {
"minifiedBytes": 82881,
"gzipBytes": 30933,
"brotliBytes": 27290,
"incrementalGzipBytes": 30933,
"incrementalBrotliBytes": 27290
"minifiedBytes": 83057,
"gzipBytes": 31005,
"brotliBytes": 27357,
"incrementalGzipBytes": 31005,
"incrementalBrotliBytes": 27357
},
"tanstack-scatter-advanced": {
"minifiedBytes": 82897,
"gzipBytes": 30938,
"brotliBytes": 27342,
"incrementalGzipBytes": 30938,
"incrementalBrotliBytes": 27342
"minifiedBytes": 83073,
"gzipBytes": 31010,
"brotliBytes": 27387,
"incrementalGzipBytes": 31010,
"incrementalBrotliBytes": 27387
},
"chartjs-line-basic": {
"minifiedBytes": 137909,
Expand Down
51 changes: 51 additions & 0 deletions benchmarks/conformance/cases/118-token-usage-calendar/case.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"schemaVersion": 1,
"order": 1190,
"id": "118-token-usage-calendar",
"title": "Token use calendar heatmap",
"family": "time",
"intent": "Show twelve months of token activity in contribution-style week columns, with recent usage emerging in the right half and quiet days kept visually consistent.",
"support": "native",
"features": [
"calendar time bin transform",
"calendar layout",
"cell mark",
"two band scales",
"categorical intensity color scale",
"keyboard focus and tooltip",
"letterboxed interaction viewport"
],
"geometry": [{ "role": "cell", "count": 364 }],
"interactionScenarios": [
{
"id": "letterboxed-cell-tooltip",
"steps": [
{
"type": "pointerMove",
"target": { "anchor": "date:2025-08-03" }
},
{
"type": "assert",
"assertions": [
{ "path": "focus.date", "equals": "2025-08-03" },
{ "path": "tooltip.visible", "equals": true },
{ "path": "tooltip.text", "includes": "Aug 3" }
]
},
{ "type": "pointerLeave" },
{
"type": "assert",
"assertions": [{ "path": "tooltip.visible", "equals": false }]
}
]
}
],
"source": {
"title": "D3 calendar time intervals",
"url": "https://d3js.org/d3-time"
},
"ai": {
"create": "Create a deterministic twelve-month token-activity panel from August through July. Aggregate raw usage events with binTimeX and utcDay, place days in Sunday-based week columns and weekday rows, keep the first half mostly quiet, and build recent intensity in the right half. Keep the visible tooltip to a compact token total and date, and render every zero-usage day with one consistent neutral color.",
"maintain": "Preserve the explicit UTC day domain, 364 complete daily cells, transform lineage, deterministic revisions, Sunday week alignment, twelve month labels, weekday order, accessible color meaning, late-timeline activity shape, and consistent zero-usage treatment."
}
}
Loading