Skip to content

feat(frontend): land Tamagui toolchain, swap ProgressBar off Radix, fix Storybook a11y violations - #786

Merged
gaidheal1 merged 9 commits into
developmentfrom
claude/issue-580-progressbar-tamagui
Aug 15, 2026
Merged

feat(frontend): land Tamagui toolchain, swap ProgressBar off Radix, fix Storybook a11y violations#786
gaidheal1 merged 9 commits into
developmentfrom
claude/issue-580-progressbar-tamagui

Conversation

@gaidheal1

@gaidheal1 gaidheal1 commented Aug 14, 2026

Copy link
Copy Markdown
Member

Summary

  • Lands the Tamagui toolchain and ports ProgressBar off Radix onto Tamagui's Progress primitive (Replace Radix Progress in ProgressBar (or delete the component) #580).
  • Fixes a wave of Storybook a11y/test-runner failures found via the a11y addon and a full axe-core sweep across all 90 stories: broken decorators (double <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).
  • Cleans up a stray js-yaml version bump and Map.test.tsx.

Test plan

  • npx tsc --noEmit
  • npx eslint on touched files
  • npx vitest run (unit tests)
  • npx vitest run --project storybook (Storybook interaction tests, 26 files / 90 tests passing)
  • Full axe-core scan across all 90 Storybook stories — no remaining color-contrast or 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

claude and others added 9 commits August 5, 2026 21:39
)

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
…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
@gaidheal1 gaidheal1 changed the title Claude/issue 580 progressbar tamagui feat(frontend): land Tamagui toolchain, swap ProgressBar off Radix, fix Storybook a11y violations Aug 15, 2026
@gaidheal1
gaidheal1 merged commit 9601013 into development Aug 15, 2026
2 checks passed
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