# feat\(web\): migrate charts to TanStack Charts · PR \#131 · flambo/atlantis

[View on GitCafe](https://git.cafe/flambo/atlantis/pull/131)

Repository: [flambo/atlantis](https://git.cafe/flambo/atlantis)

Visibility: public

State: open

Source repository: flambo/atlantis

Source branch: feat/tanstack-charts

Target branch: test/e2e-baseline

Head commit: ab5316e0786e50a468fd0cefd926f8cbf9c4f6f8

[Discussion and reviews](https://git.cafe/flambo/atlantis/pull/131)

[Commits](https://git.cafe/flambo/atlantis/pull/131/commits?format=markdown)

[Files changed](https://git.cafe/flambo/atlantis/pull/131/diffs?format=markdown)

## Description

---

> [!NOTE]
> 🤖 **GPT-6.1-Sol on behalf of Oliver**

## Round 3 review fixes

Restore original midnight/noon/date boundaries, 45-degree desktop time labels and horizontal numeric labels. Two pixel-bounded grid paths preserve bucket boundaries without one DOM node per bucket. Restore 3px traffic/router outlines, 2px file analysis lines, filled purple spectrum points, reference/error-bar opacity, numeric endpoint ticks and legend glyphs that match their marks. Lower-layer adaptation uses the existing Svelte host and literal palette; later layers retain these fixes.

Own-tip verification: format, lint, typecheck, production build, 230 web tests and 71 Playwright tests passed. Human review: compare short/week/long time axes, numeric file plots, legend toggles and compact labels in both themes. The public sheet compares illustrative fixture charts before round 3 and at the combined stack tip. Full v5 Chart.js comparison sheets remain private/local.

All dashboard charts and file spectrum/structure plots now render through the official `@tanstack/charts/svelte` adapter, pinned to 1.0.0. Chart.js, its annotation plugin, and the old renderer adapters are removed. Series, metric units, stacking, partial/unknown coverage, annotations, legend toggles, crosshairs, bucket drilldown, time-range selection, themes, resizing, and deferred card activation retain their existing behavior.

Layer 3 of the stack, based on [#130](https://github.com/flamboh/atlantis/pull/130), following [#129](https://github.com/flamboh/atlantis/pull/129). No merge or Cloudflare deployment. Only the isolated perf stage on :8090 was redeployed; the production test stage on :8080 was not touched.

### Adapter and renderer decisions

The shipped Svelte adapter supports Svelte 5 runes, SSR markup and hydration under this SvelteKit 3 setup. Use it directly through the shared ChartPlot component. Application DOM subscriptions and sizing use attachments; definitions, data, loading and empty state remain derived.

Every chart uses SVG: traffic line/stacked area, characteristic panels, unique ports, IP/protocol breakdown, dimensions, dashboard spectrum, coverage lanes, file spectrum and file structure function. Comparing the same native dot mark and quantitative f(alpha) scale on real v5 points favored SVG at every measured size. At 9,870 marks, median render/presentation was 139/219 ms for SVG versus 398/436 ms for canvas. At 50,000 marks, render time was 808 ms versus 6,581 ms. Canvas reduced DOM nodes from about 50,000 to 51 but its CPU cost was worse, so it is not shipped.

Composed TanStack area/line/dot/rect marks implement the existing cumulative stacking, dashed partial segments, unknown gaps, hollow singleton/partial points, coverage lanes, f(alpha)=alpha reference line, and standard-deviation bars with caps. Decorative marks do not become tooltip or summary observations. Shared DOM overlays mirror crosshairs and selection across charts; pointer clicks retain nearest-bucket behavior throughout the plot. Native keyboard exploration and legend buttons are complemented by Shift+Arrow range selection and Enter/Escape commit/cancel.

### Contract and fixes

The existing chart-surface/render-state/series/axis/tooltip, card lifecycle, normalized interaction targets, and activation/sentinel contract is preserved. Summaries come from finite positioned scene points after rendering. A small attribute observer keeps the publisher's readiness and accessibility attributes through SVG reconciliation and disconnects on teardown.

Resolved baseline findings F01, F02, F10, and the chart-rendering portion of F05: totals use the hovered bucket identity rather than a trimmed index; empty selections explain themselves; singleton domains are padded; legends, value exploration, drilldown and range selection have keyboard equivalents. External findings.md records each resolution. Remaining native card reorder/resize gestures are tracked separately as F11 for redesign.

No baseline e2e assertion was weakened or changed. Five new regressions cover hovered totals, daily singleton characteristics/ports, keyboard legend/value/drilldown, keyboard range cancellation/application, and a 20,001-point spectrum with one accessible interaction target. Unit tests that mocked Chart.js pixel conversion were replaced with bucket-identity checks; the old per-point styling arrays were replaced with coverage-segment tests that assert gaps and partial adjacency.

### Performance before and after

Same v5 dataset, June 1 2025 through October 6 2026, five API runs, three fresh browser contexts per page, one discarded API warmup, no throttling, one-second settling and explicit GC. The readiness selector is `[data-chart-rendered="true"]` because SSR SVG exists before interactive rendering. Layer 1 remains the before baseline; layer 2 hook overhead was not measured separately. Three browser samples have substantial sampling limits, and all-chart timing includes scrolling/settling/GC.

| Metric | Before | After |
| --- | ---: | ---: |
| Total JS, raw / gzip KiB | 711.6 / 228.1 | 631.6 / 202.4 |
| Dashboard with static dependencies, raw / gzip KiB | 682.3 / 208.4 | 641.6 / 195.6 |
| Total CSS, raw / gzip KiB | 60.8 / 10.5 | 61.0 / 10.5 |
| Default dashboard FCP / LCP p50 ms | 136 / 136 | 120 / 120 |
| Default visible charts p50 / p95 ms | 509.1 / 642.6 | 569.3 / 571.4 |
| Default all charts p50 ms | 3258.9 | 4271.3 |
| Default initial observed TBT p50 ms | 130 | 159 |
| Default heap before / after GC MiB | 31.3 / 18.3 | 39.2 / 29.9 |
| Six-month visible / all charts p50 ms | 456.3 / 2722.8 | 489.6 / 3183.8 |
| Filtered-hourly visible / all charts p50 ms | 400.3 / 2691.2 | 435.1 / 3205.1 |
| File detail LCP / chart readiness p50 ms | 744 / 729.7 | 684 / 673.3 |
| File initial observed TBT p50 ms | 411 | 336 |
| File heap after GC MiB | 14.6 | 8.1 |
| Default traffic API p50 / p95 ms | 30.0 / 35.2 | 37.3 / 37.9 |
| Default spectrum API p50 / p95 ms | 61.9 / 87.2 | 76.0 / 90.6 |
| Default structure API p50 / p95 ms | 102.5 / 110.7 | 116.3 / 133.7 |

Bundles and file rendering improve, while dashboard readiness and retained heap regress. Large SVG scenes, point/focus metadata and scene publication should be profiled in layer 4. This PR changes no queries; API timing differences do not demonstrate an API improvement. Spectrum/structure response bodies remain 2,969.7/4,882.4 KiB.

Final reports and renderer samples are in `/tmp/atlantis-frontend-overhaul/perf/layer3/`. The fresh bundle export is `final-build`; earlier merged-directory total bundle inventories are invalid and explicitly labeled in measurement-notes.md. Future exports must use a fresh destination because Docker copy retains old hashed chunks.

### Verification and human review guide

Automated: `bun run format`, `bun run lint`, `bun run typecheck`, `bun run test:web` with 215 tests, and `bun run test:e2e` with 62 tests passed. The architecture documentation formatter check passed. The final benchmark completed all 35 query cases and 18 browser loads with zero errors.

Implementation browser pass: 24 checks across all eight dashboard cards and all 12 file-analysis charts on real read-only v5, including hover, legends, keyboard actions, brush/drilldown, both themes, empty recovery and 390px layouts. Saved 49 private after-screenshots locally in `e2e/layer3-screens/`; they were not uploaded or committed. T3 preview could not reach pinot's loopback port, so this pass used the repository's requested Playwright workflow. The v5 file metadata stayed unchanged and perf's bind mount is read-only.

For human review:

- Open :8090 with the default uoregon window, scroll through all eight cards, and verify deferred activation, axes/units, responsive resizing and both themes.
- Switch traffic line/stacked modes, metrics and family; hide/restore legend entries. On the isolated March 1 fixture at 5-minute grouping, the first tooltip must show TCP 100 and Total Flows 160.
- Inspect partial capture boundaries. Partial observations retain hollow markers/dashed segments; unknown observations remain gaps; known zero remains plotted.
- Focus a chart and use arrows/Home/End, then Enter to open a bucket. Shift+Arrow then Enter must apply a range; Escape must cancel. Pointer brush and whole-plot clicks should preserve the existing URL/grouping and file context.
- Open a populated v5 file and inspect every source/destination spectrum reference line and structure-function SD bar. Recheck MAAD family/measure changes and narrow layouts.
- Review the all-SVG tradeoff and dashboard timing/heap regressions. The separate extensive QA pass is still pending. Native card layout gestures, selection persistence, narrow file text, date validation, file titles and duplicate summary requests remain later-layer work; full screen-reader, real touch, Firefox and WebKit checks are not claimed.

Changes made by GPT-6.1-Sol through the Codex harness in T3 Code.

![Before and after: calendar ticks, stroke weights, filled points and legend glyphs on generated public fixtures](https://github.com/user-attachments/assets/f18f6702-9836-44fd-b6d3-2297d10ffa40)
