From e8feef1c600046b7ce26787fb6f40889c3398019 Mon Sep 17 00:00:00 2001 From: "Anthony Fu (via agent)" Date: Tue, 4 Aug 2026 00:54:36 +0000 Subject: [PATCH] fix(inspector): restore WCAG AA color contrast across light/dark modes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The @antfu/design migration (#178) gave a solid opaque background for the first time (it was previously transparent, with the dot-grid pattern living on as a background-image). That accidentally fixed axe-core's ability to determine effective background colors, surfacing a batch of genuine contrast bugs — some introduced by the migration itself (badge-color-lime/teal, the reverted primary theme darkening), some pre-existing but never actually measurable before now (op-fade/op-mute diluting already-tuned colors, hash-color HSL lightness not being perceptually uniform across hues). - uno.config.ts: extend the badge-color-* WCAG overrides to lime/teal - utils/color.ts: switch the cluster hash-color from HSL to OKLCH so lightness stays perceptually uniform across the full hue wheel - Percentage.vue, ClusterBadge.vue, SubTitle.vue: stop applying op-fade on top of already contrast-tuned children (badge text, hash colors, number badges) — the extra opacity dilutes them below 4.5:1 - PackageDetails.vue: replace op30/op-mute with the solid color-muted token for real body text (tab labels, empty states) - grid/chart/report pages, PackageBorder.vue, Overview.vue, compare.vue: tab/nav active states and brand accents needed op100! (to cancel btn-action's op75) and/or -700/dark:-300 shades tuned for their actual background instead of the bare DEFAULT swatch - Credits.vue: darken the Nuxt brand green for light mode only Also exclude @antfu/design's DisplayAvatar hash-color fallback from the a11y scan — same HSL-lightness bug as our own hash-color util, but it lives in the vendor package; reported upstream. All 28 e2e tests (light + dark a11y matrix included), lint, typecheck, and unit tests pass. Fix created with the help of an agent. --- .../app/components/display/ClusterBadge.vue | 7 ++++- .../src/app/components/panel/Overview.vue | 14 +++++++-- .../app/components/panel/PackageDetails.vue | 20 +++++++++---- .../src/app/components/ui/Credits.vue | 5 +++- .../src/app/components/ui/PackageBorder.vue | 6 ++-- .../src/app/components/ui/Percentage.vue | 5 +++- .../src/app/components/ui/SubTitle.vue | 7 ++++- .../src/app/pages/chart/[...chart].vue | 12 ++++++-- .../src/app/pages/compare.vue | 8 +++-- .../src/app/pages/grid/[...grid].vue | 18 ++++++++---- .../src/app/pages/report/[...report].vue | 29 ++++++++++++------- .../src/app/utils/color.ts | 17 ++++++++--- .../node-modules-inspector/src/uno.config.ts | 12 +++++--- test/e2e/a11y.spec.ts | 7 +++++ 14 files changed, 123 insertions(+), 44 deletions(-) diff --git a/packages/node-modules-inspector/src/app/components/display/ClusterBadge.vue b/packages/node-modules-inspector/src/app/components/display/ClusterBadge.vue index 7f7d65c6..7532d22a 100644 --- a/packages/node-modules-inspector/src/app/components/display/ClusterBadge.vue +++ b/packages/node-modules-inspector/src/app/components/display/ClusterBadge.vue @@ -33,7 +33,12 @@ const style = computed(() => ({ -
+ +
No dependents
@@ -465,7 +472,8 @@ const thirdPartyServices = computed(() => { type="dependencies" /> -
+ +
No dependencies
diff --git a/packages/node-modules-inspector/src/app/components/ui/Credits.vue b/packages/node-modules-inspector/src/app/components/ui/Credits.vue index 802d0004..8639e61e 100644 --- a/packages/node-modules-inspector/src/app/components/ui/Credits.vue +++ b/packages/node-modules-inspector/src/app/components/ui/Credits.vue @@ -3,7 +3,10 @@ Made with
- Nuxt + + Nuxt and
diff --git a/packages/node-modules-inspector/src/app/components/ui/PackageBorder.vue b/packages/node-modules-inspector/src/app/components/ui/PackageBorder.vue index 6992ec06..05da0bc2 100644 --- a/packages/node-modules-inspector/src/app/components/ui/PackageBorder.vue +++ b/packages/node-modules-inspector/src/app/components/ui/PackageBorder.vue @@ -71,9 +71,11 @@ const classesOuter = computed(() => { if (isSelected.value) { if (isFocused.value) - list.push('ring-3 ring-teal:25! text-teal-600 dark:text-teal-300 border-teal!') + list.push('ring-3 ring-teal:25! text-teal-700 dark:text-teal-300 border-teal!') else - list.push('ring-3 ring-primary:25! text-primary-600 dark:text-primary-300') + // text-primary-600 (#49833e) only clears ~4.05:1 against the bg-primary:10 + // tint (axe-core-measured) — bump to 700 for a comfortable ~5.8:1. + list.push('ring-3 ring-primary:25! text-primary-700 dark:text-primary-300') } if (props.pkg?.private) diff --git a/packages/node-modules-inspector/src/app/components/ui/Percentage.vue b/packages/node-modules-inspector/src/app/components/ui/Percentage.vue index b96c00f5..feaef769 100644 --- a/packages/node-modules-inspector/src/app/components/ui/Percentage.vue +++ b/packages/node-modules-inspector/src/app/components/ui/Percentage.vue @@ -32,7 +32,10 @@ const total = computed(() => props.nodes.reduce((acc, { value }) => acc + value, text-center text-xs px1.5 py1 flex gap-x-0.5 cursor-default > {{ node.name }} - {{ `${+(node.value * 100 / total).toFixed(1)}%` }} + + {{ `${+(node.value * 100 / total).toFixed(1)}%` }}
diff --git a/packages/node-modules-inspector/src/app/components/ui/SubTitle.vue b/packages/node-modules-inspector/src/app/components/ui/SubTitle.vue index 7522c66d..7a7d688d 100644 --- a/packages/node-modules-inspector/src/app/components/ui/SubTitle.vue +++ b/packages/node-modules-inspector/src/app/components/ui/SubTitle.vue @@ -1,5 +1,10 @@ diff --git a/packages/node-modules-inspector/src/app/pages/chart/[...chart].vue b/packages/node-modules-inspector/src/app/pages/chart/[...chart].vue index 3ba4de58..f4acef0b 100644 --- a/packages/node-modules-inspector/src/app/pages/chart/[...chart].vue +++ b/packages/node-modules-inspector/src/app/pages/chart/[...chart].vue @@ -276,10 +276,16 @@ onUnmounted(() => {