feat(frontend): land Tamagui toolchain, swap ProgressBar off Radix, fix Storybook a11y violations - #786
Merged
Conversation
) First real (non-PoC) landing of the Tamagui toolchain decided in #591 for the RN/Expo migration (#578), and the first sub-issue of #578 to ship: ProgressBar (#580) moves off @radix-ui/react-progress onto Tamagui's own Progress primitive (a genuine fork of Radix's, not a runtime wrapper around it - confirmed by reading @tamagui/progress's source and package.json before writing this). Toolchain, wired as permanent config rather than PoC scaffolding: - tamagui, @tamagui/core, @tamagui/config, @tamagui/vite-plugin, react-native-web added; @radix-ui/react-progress removed. - Pinned to the exact Tamagui version (2.6.0) the epic's PoC findings (#629, #583) are based on, rather than drifting to latest - avoids re-litigating verified findings against an untested version. (Latest, 2.7.1, also requires TamaguiProvider's new `defaultTheme` prop, which 2.6.0's types don't - another reason to stay pinned for now.) - tamagui.config.ts: @tamagui/config's default preset with breakpoints aligned to the app's own SCSS scale (sm/md/mdUp), per #629's findings. - vite.config.ts: @tamagui/vite-plugin's compiler wired in alongside (not replacing) the existing maplibre-gl static-copy workaround. - <TamaguiProvider> mounted at the app root (main.tsx) and as a Storybook global decorator, so future Tamagui-based stories don't each need their own wrap. ProgressBar.tsx: swapped Progress.Root for Tamagui's Progress with `unstyled` set, so ProgressBar.module.scss stays the sole source of visual truth - same role Radix's bare Progress.Root played. The fill stays a plain styled <div>, not Progress's own Indicator, matching the original's design (Radix's Indicator was never used either) and avoiding Indicator's transform-based positioning model, which doesn't match this component's percent-width fill. Found and fixed a real bug along the way, not just a test artifact: Map.tsx renders tooltip content (including ProgressBar, via #673's village-progress tooltip) into a second, independent React root (createRoot(host), for imperative position-tracking) that doesn't inherit main.tsx's <TamaguiProvider> context - React context follows the component tree, not DOM nesting. Without re-providing it at that second root, tapping a village marker would throw in production, not just in tests. Fixed by wrapping that root's render in its own <TamaguiProvider>. Verified: 448/448 unit tests pass (incl. Map.test.tsx, updated to wrap renders in TamaguiProvider), tsc --noEmit clean, lint unchanged from development's pre-existing 14 problems, production build succeeds. Bundle delta on the index chunk: +431 kB raw / +121 kB gzip (measured directly, both branches built with the same toolchain) - a bit above #629's PoC estimate (+383/+102), expected since this lands a real component on top of the fixed provider cost, not just the provider. Not verified: Storybook's browser-mode a11y suite (`vitest --project storybook`) - this sandbox lacks the chrome-headless-shell binary that provider needs, confirmed pre-existing by reproducing the same failure on unmodified development. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019tw1wbEwrW3EiTspfBt4RP
) First real (non-PoC) landing of the Tamagui toolchain decided in #591 for the RN/Expo migration (#578), and the first sub-issue of #578 to ship: ProgressBar (#580) moves off @radix-ui/react-progress onto Tamagui's own Progress primitive (a genuine fork of Radix's, not a runtime wrapper around it - confirmed by reading @tamagui/progress's source and package.json before writing this). Toolchain, wired as permanent config rather than PoC scaffolding: - tamagui, @tamagui/core, @tamagui/config, @tamagui/vite-plugin, react-native-web added; @radix-ui/react-progress removed. - Pinned to the exact Tamagui version (2.6.0) the epic's PoC findings (#629, #583) are based on, rather than drifting to latest - avoids re-litigating verified findings against an untested version. (Latest, 2.7.1, also requires TamaguiProvider's new `defaultTheme` prop, which 2.6.0's types don't - another reason to stay pinned for now.) - tamagui.config.ts: @tamagui/config's default preset with breakpoints aligned to the app's own SCSS scale (sm/md/mdUp), per #629's findings. - vite.config.ts: @tamagui/vite-plugin's compiler wired in alongside (not replacing) the existing maplibre-gl static-copy workaround. - <TamaguiProvider> mounted at the app root (main.tsx) and as a Storybook global decorator, so future Tamagui-based stories don't each need their own wrap. ProgressBar.tsx: swapped Progress.Root for Tamagui's Progress with `unstyled` set, so ProgressBar.module.scss stays the sole source of visual truth - same role Radix's bare Progress.Root played. The fill stays a plain styled <div>, not Progress's own Indicator, matching the original's design (Radix's Indicator was never used either) and avoiding Indicator's transform-based positioning model, which doesn't match this component's percent-width fill. Found and fixed a real bug along the way, not just a test artifact: Map.tsx renders tooltip content (including ProgressBar, via #673's village-progress tooltip) into a second, independent React root (createRoot(host), for imperative position-tracking) that doesn't inherit main.tsx's <TamaguiProvider> context - React context follows the component tree, not DOM nesting. Without re-providing it at that second root, tapping a village marker would throw in production, not just in tests. Fixed by wrapping that root's render in its own <TamaguiProvider>. Verified: 448/448 unit tests pass (incl. Map.test.tsx, updated to wrap renders in TamaguiProvider), tsc --noEmit clean, lint unchanged from development's pre-existing 14 problems, production build succeeds. Bundle delta on the index chunk: +431 kB raw / +121 kB gzip (measured directly, both branches built with the same toolchain) - a bit above component on top of the fixed provider cost, not just the provider. Not verified: Storybook's browser-mode a11y suite (`vitest --project storybook`) - this sandbox lacks the chrome-headless-shell binary that provider needs, confirmed pre-existing by reproducing the same failure on unmodified development. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019tw1wbEwrW3EiTspfBt4RP
….com/progressrpg/ProgressRPG into claude/issue-580-progressbar-tamagui # Conflicts: # frontend/.storybook/preview.tsx # frontend/package-lock.json # frontend/package.json # frontend/src/components/Map/Map.test.tsx # frontend/src/components/ProgressBar/ProgressBar.test.tsx # frontend/src/components/ProgressBar/ProgressBar.tsx
…measuring dimensions
…avior for accurate width representation
…low track Being unstyled, Indicator had no height of its own as a flex-column item, so the nested fill's height:100% resolved against an auto/content-sized parent instead of the track, growing past the track's border on tall content. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes surfaced one at a time via the Storybook test-runner across
several stories:
- main.ts: gate the GitHub Pages `base` override to production builds
only - it was also breaking the vitest browser test runner's client
handshake ("Failed to connect to the browser session").
- preview.tsx / Tooltip.stories.tsx: move TooltipProvider to a global
decorator so it also covers the autodocs Docs page.
- List.stories.tsx: pass renderItem so items render their name instead
of `[object Object]`.
- ToastManager.stories.tsx: wait for the toast's fade-in animation
before asserting visibility.
- WaitlistForm.stories.tsx: match the email label non-exactly, since
the required-asterisk span's aria-label changes its accessible name.
- Footer.stories.tsx / NavDrawer.stories.tsx / Navbar.stories.tsx: stop
double-nesting MemoryRouter - Storybook composes story-level
decorators with meta-level ones rather than replacing them, so a
per-story Router decorator on top of a meta-level one threw "You
cannot render a <Router> inside another <Router>". Navbar's router
path varies per story, so its provider tree now reads options from
story parameters instead of re-applying a decorator.
- NavDrawer.stories.tsx: narrow the test viewport to mobile size - the
drawer is CSS-hidden (`display: none`) at the `md` breakpoint and up,
so at Playwright's default desktop viewport its links were 0x0 and
absent from the accessibility tree entirely, not just non-visible.
- Navbar.stories.tsx: query the announcements panel from
document.body, since it portals there via Radix rather than
rendering inside canvasElement.
Also fixes two accessibility violations in EntitySearchInput:
- aria-required-children: role="option" was on a button nested inside
each <li>, but a listbox's immediate children must carry the option
role directly.
- color-contrast: two labels used a translucent color-text-body that
composited under WCAG AA's 4.5:1 minimum - swapped to the opaque
color-text-muted token.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…weep - ProgressBar: add fallback accessible name, fix contrast on warning/paused label text - Achievements: fix tier-4 (purple) heading contrast against its light background - PlayerItemList: fix completed-item meta text contrast - BuildingDetail: add missing ariaLabel to resident/worker lists - Navbar: add missing aria-label to the announcements popover dialog Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhBkm3n6sBhsrgGJ2GJhdi
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ProgressBaroff Radix onto Tamagui'sProgressprimitive (Replace Radix Progress in ProgressBar (or delete the component) #580).<Router>in Navbar/Footer, missing providers), missing/incorrect ARIA roles and names (EntitySearchInput,BuildingDetail, Navbar announcements popover), and several color-contrast violations (ProgressBar,Achievements,PlayerItemList,EntitySearchInput).js-yamlversion bump andMap.test.tsx.Test plan
npx tsc --noEmitnpx eslinton touched filesnpx vitest run(unit tests)npx vitest run --project storybook(Storybook interaction tests, 26 files / 90 tests passing)color-contrastor ARIA naming violations (only expected landmark/heading noise from testing isolated components outside a page shell)Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01UhBkm3n6sBhsrgGJ2GJhdi