Skip to content

Run the web app on TanStack Redact - #40

Merged
firsttris merged 2 commits into
mainfrom
redact
Nov 4, 2026
Merged

firsttris merged 2 commits into
mainfrom
redact

Conversation

@firsttris

@firsttris firsttris commented Nov 4, 2026 •

Copy link
Copy Markdown
Owner

What and why

Runs the web app on TanStack Redact (@tanstack/redact, pinned to exactly 0.3.1) instead of React. Redact implements the React 19 API, so the code keeps importing react and react-dom; the Vite plugin redact() (after react() in web/vite.config.ts) points those imports at the smaller runtime when building and in vite dev. No TanStack Start here, so no ssr.noExternal is needed. docs/architecture.md says so in one paragraph.

A Redact 0.3.1 bug lost SVG text-anchor and vector-effect. The second commit fixes it in the app, so it works the same under React and Redact (see risks).

Size (gzip -9, production build)

React Redact Change
Initial JS (entry script + modulepreloads of index.html) 171.3 kB 128.4 kB −42.9 kB (−25 %)
All client JS (dist/assets/*.js) 589.0 kB 545.5 kB −43.4 kB (−7.4 %)
Entry chunk index-*.js alone 142.5 kB 49.2 kB

The initial set now has more, smaller modulepreloads (Rolldown split the chunks differently), the sum is what counts. The map chunk (MapLibre, 324 kB) stays as it is.

Checks

Check Result
bun run lint ✅
bun run typecheck ✅
bun run test (Vitest) ✅ 172 passed
bun run build ✅, route tree unchanged
bun run e2e, React (baseline on main) ✅ 52 passed
bun run e2e, Redact ✅ 52 passed (before and after the SVG fix)
text-anchor / vector-effect in Chromium, React and Redact builds ✅ same under both, see below
vite dev with Redact, start page in Chromium ✅ renders, imports point to @tanstack/redact, no page errors (the API was not running: 502s only)
mkdocs build --strict ✅
cargo fmt --all --check ✅
cargo build -p windfenster ✅ (debug, for the e2e)
cargo clippy, cargo test not run locally: no Rust changed; CI runs them once the PR is ready for review (drafts do not build)

The e2e ran locally on other ports (48xx) through an uncommitted change to the config.

Risks found

  • Bug in Redact 0.3.1, fixed in the app: attributeName() in src/core/attributes.ts hyphenates camelCase SVG attributes only when they start with clip, fill or stroke. Everything else got to the DOM in camelCase and did nothing. With React, <text> on /station/edsb carried text-anchor (computed middle); with Redact it carried textAnchor (computed start), so the labels slid to the right. Affected:
    • textAnchor: web/src/ui/wind-compass.tsx (the compass letters and the centre value), web/src/components/live/stats-card.tsx (two places), web/src/components/favorite.tsx;
    • vectorEffect="non-scaling-stroke": web/src/components/water-tile.tsx, web/src/components/map/measured-spark.tsx.
    • Fix: both are CSS properties, so they now go in style (TEXT_CENTRED and NON_SCALING_STROKE in the new web/src/ui/svg-style.ts, with the reason in a comment). React and Redact both write that as style="text-anchor: middle" / style="vector-effect: non-scaling-stroke".
    • Checked in Chromium against the e2e server, once with a React build and once with a Redact build of this branch, with the same results: on /station/edsb all 9 centred labels compute to text-anchor: middle and no camelCase attribute is left. The rose's ring labels compute to start in both builds because they never had an anchor. On the map's spot card (/map?spot=…&station=edsb), the measured sparkline computes to vector-effect: non-scaling-stroke. The water tile's level line uses the same constant; the stand-in has no level history, so it does not draw there.
    • strokeWidth, strokeLinecap, strokeLinejoin, strokeDasharray (including lucide icons) were fine all along. A grep finds no other camelCase SVG attributes (dominantBaseline, stopColor, fontSize, …).
  • Synchronous rendering: the code has no useTransition, useDeferredValue, startTransition, Suspense, class components or flushSync. useSyncExternalStore (prefs, media query) is supported.
  • createPortal (web/src/components/header-slot.tsx, the map's model and layer pills in the header): checked by hand, the pills are in the header on a wide window and back on the map on a narrow one; map.spec.ts also covers the map.
  • Radix dialog (web/src/ui/dialog.tsx): account.spec.ts opens the spot and alarm dialogs; by hand the search dialog opens with Ctrl+K, takes the focus, lists results and closes with Escape.
  • No Radix Slider in the app.

Roll back

Remove redact() (and its import) from web/vite.config.ts, then bun remove --cwd web @tanstack/redact. The app code needs no change.


Generated by Claude Code

@firsttris
firsttris marked this pull request as ready for review November 4, 2026 12:36
@tanstack/redact 0.3.1 (pinned exactly) is a React-19-compatible runtime; its Vite plugin
(redact() after react() in web/vite.config.ts) points the react and react-dom imports at it, so the
code stays unchanged. Initial JS shrinks from 171.3 to 128.4 kB gzip, all client JS from 589.0 to
545.5 kB.

Redact renders synchronously; the app uses no useTransition, useDeferredValue, Suspense or class
components. createPortal (the map's header slot) and the Radix dialogs work in the e2e tests and
by hand. Known gap in 0.3.1: SVG attributes outside clip*/fill*/stroke* (textAnchor, vectorEffect)
reach the DOM in camelCase and have no effect (src/core/attributes.ts, attributeName()).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y3rdfbVn9WQaWcUTzwvU2s
TanStack Redact 0.3.1 hyphenates only clip*, fill* and stroke* attributes (attributeName() in
src/core/attributes.ts): textAnchor and vectorEffect reached the DOM in camelCase and did nothing,
so the compass letters and the rose's labels sat off centre and the sparklines' strokes scaled
with the viewBox. As style (web/src/ui/svg-style.ts) both work the same under React and Redact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y3rdfbVn9WQaWcUTzwvU2s
@firsttris
firsttris merged commit cda6a97 into main Nov 4, 2026
7 checks passed
@firsttris
firsttris deleted the redact branch November 4, 2026 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants