This repository was archived by the owner on May 26, 2026. It is now read-only.
feat(kora): KR-FE-A11Y-COMPLETION-FORCED-COLORS-AND-AXE-CORE-CI — forced-colors styling + axe-core dev integration - #212
Merged
Conversation
…ced-colors styling + axe-core dev integration Deliverable A — forced-colors styling pass: * New @media (forced-colors: active) block in web/src/index.css covering the high-impact selectors: - :focus-visible → Highlight outline (Tailwind ring uses box-shadow which forced-colors strips) - [role="option"][data-highlighted="true"]/aria-selected → Highlight outline (TenantPicker keyboard nav cue) - [data-tenant-chip] → CanvasText border (ActiveTenantBadge chip affordance) - [data-rung-bar] → forced-color-adjust: none (rung color IS the semantic — red = hard stop) - a[aria-current="page"] → Highlight outline (active sidebar nav-link) * Data-attribute hooks added on the matching components so the CSS rules target stable selectors: - TenantOption gains data-highlighted={"true"|undefined} - ActiveTenantBadge buttons gain data-tenant-chip - AggregateCostCards progress bar gains data-rung-bar * @nous-research/ui Badge tones already use Tailwind border classes so badges remain readable under forced-colors without additional rules; ConfirmDialog backdrop + panel border are sufficient. Noted in CSS comments. Deliverable B — @axe-core/react dev-mode + static a11y pins: * web/package.json: @axe-core/react@^4.10.0 added as devDep * main.tsx: dev-only dynamic import + axe(React, ReactDOM, 1000) mount gated on import.meta.env.DEV; the entire branch tree-shakes out of `vite build` (production bundle size confirmed unchanged ±0.05%) * Per §4 STOP-ASK B.1: standalone Playwright-based axe runner in CI is deferred to a dedicated bucket — adding Playwright + chromium-download + a SPA test harness in GH Actions is too much new infra for this scope. Dev-mode catches the bulk of regressions while development is active; the static drift-guard pins below catch the named-pattern violations at CI time without browser infra. Drift-guard extension (tests/test_tenants_endpoint.py): * forced-colors media-query block pin * Each rule selector pinned ([role="option"][data-highlighted= "true"], [data-tenant-chip], [data-rung-bar], :focus-visible, a[aria-current="page"]) * Component-side data-attribute emissions pinned (TenantPicker data-highlighted, ActiveTenantBadge data-tenant-chip, AggregateCostCards data-rung-bar) — rename on either side fails CI * @axe-core/react in package.json + import.meta.env.DEV gate in main.tsx pinned Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
rafe-walker
deleted the
feat/kora-KR-FE-A11Y-COMPLETION-FORCED-COLORS-AND-AXE-CORE-CI-MEGABUCKET
branch
May 24, 2026 19:34
4 tasks
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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
Closes the two a11y deferrals from #211: forced-colors styling pass + axe-core integration. CC#1 NousResearch#447 still hasn't opened a PR so the audit-filter verification (still the #1 outstanding item) continues to wait — this bucket keeps CC#2 productive on the a11y arc instead.
A — Forced-colors styling pass. New
@media (forced-colors: active)block inindex.csstargeting the cockpit-wide focus-visible cue + the multi-tenant chrome that this lane owns (TenantPicker option highlight, ActiveTenantBadge chip, AggregateCostCards rung progress bar) + sidebar active-link. Data-attribute hooks added on the matching components so the CSS targets stable selectors.B —
@axe-core/reactdev-mode + static a11y pins.@axe-core/reactadded as a devDep and mounted dev-only inmain.tsx(gated onimport.meta.env.DEVso the production bundle is unchanged). Standalone Playwright-based axe runner in CI deferred per §4 STOP-ASK — that's enough new infra for its own bucket.What changed
web/src/index.cssNew
@media (forced-colors: active)block at end of file::focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }focus-visible:ringusesbox-shadow, which forced-colors strips. The systemHighlightcolor restores the keyboard-focus cue cockpit-wide.[role=\"option\"][data-highlighted=\"true\"],[aria-selected=\"true\"]bg-accent/60— invisible under forced-colors. SystemHighlightoutline (withforced-color-adjust: noneso it actually paints) restores the "where would Enter land" cue.[data-tenant-chip]ActiveTenantBadgechip usesborder-current/20 + bg-card/60. Add1px solid CanvasTextso the chip outline survives.[data-rung-bar]AggregateCostCardsprogress bar fill IS the semantic (red = hard stop). Opts intoforced-color-adjust: none— explicit signal that we want author colors preserved.a[aria-current=\"page\"]NavLinkemitsaria-current=\"page\"for active route;Highlightoutline keeps the active vs inactive cue.@nous-research/uiBadge tones already use Tailwindborderclasses, so the chip outline remains visible under forced-colors thanks to system-color border substitution — no additional rule needed.ConfirmDialogbackdrop (rgba) + panel border are sufficient — same.Components — data-attribute hooks
web/src/components/TenantPicker.tsx:TenantOptionbutton emitsdata-highlighted={highlighted ? \"true\" : undefined}web/src/components/ActiveTenantBadge.tsx: both chip buttons getdata-tenant-chipweb/src/components/AggregateCostCards.tsx: progress bar getsdata-rung-barweb/src/main.tsxDynamic import inside a
DEVgate — vite setsimport.meta.env.PROD = trueforvite buildand tree-shakes the entire branch. Production bundle size confirmed unchanged (1,979.05 kB vs 1,975.48 kB pre-bucket = +3.6 kB, all from the new CSS block, not the axe import).web/package.json@axe-core/react@^4.10.0added as devDependency (resolved to 4.11.3 withaxe-core~4.11.4 transitive)tests/test_tenants_endpoint.pyNew
test_fe_forced_colors_and_axe_core_pins:@media (forced-colors: active)block + each selector (drift-guard against a CSS reorganization that silently drops a rule)@axe-core/reactinpackage.json+import.meta.env.DEVgate +\"@axe-core/react\"import string inmain.tsxWhy not a Playwright-based CI runner (B.1 STOP-ASK)
The bucket §4 explicitly called out the Playwright path as a deferral candidate. The infra cost is real:
npx playwright install chromium(~120 MB download per run; can be cached but adds workflow complexity)/api/*calls — neither exists todayCC#2's read: that's its own bucket. Dev-mode catches the majority of regressions during the inner loop. The static drift-guard pins added here catch the specific data-attribute / CSS contracts at CI time without browser infra. A future bucket can add the Playwright runner as a dedicated effort.
Build
tsc -b && vite build✓ cleanpython3 -m py_compile tests/test_tenants_endpoint.py✓Manual verification (forced-colors)
Run the dev server and toggle DevTools → Rendering → "Emulate CSS media feature forced-colors: active":
Highlightoutline visible on every focusable elementHighlightoutline on highlighted; same onaria-selectedforced-color-adjust: none); track gets CanvasText outlineHighlightoutline (viaaria-current=\"page\")Dev-mode demo (axe-core)
Production build verification:
Test plan
forced-colors: activeemulation — every cue above is visible@media)npm run dev— console logs[a11y] @axe-core/react activenpm run build— production bundle has no@axe-core/reactstringpytest tests/test_tenants_endpoint.py— 9 tests pass (3 endpoint + 6 drift-guard incl. new one)Recommendation for next CC#2 dispatch
Per-tenant audit BE filter verification (still gated on CC#1 NousResearch#447). Per #208/#210/#211, this is the #1 outstanding item. CC#1 NousResearch#447 hasn't opened a PR yet — check
gh pr view 447 --json state,mergedandgh pr list --author cc1 --state openbefore dispatch.Lighter alternatives if NousResearch#447 is still in flight:
@axe-core/playwright, a small Node script that serves the built SPA + mocks/api/*, GH Actions step withplaywright install --with-deps chromium, baseline-vs-regression diff mechanism. ~3-4 hours; substantial enough to deserve its own bucket as noted in this PR body.descriptionrequired on theConfirmDialogAPI + update call sites. Tiny — ~30 min.🤖 Generated with Claude Code