fix(webui): ARIA-valid hover text, alias metadata guard, context-chip polish

CompactNumber.vue and ProviderRateBadge.vue both put aria-label on a
plain <span> — axe's aria-prohibited-attr rule: a <span>'s implicit
ARIA role ("generic") does not support accessible-name computation at
all, so the attribute was simply invalid there, not just discouraged.
Both now use the visible-text-hidden-plus-sr-only-full-text pattern
instead: the compact/label text is marked aria-hidden="true", and a
sr-only sibling span carries the exact value/detail for assistive tech.
title stays on the outer element for mouse hover, unchanged. Folded
review minor.

ProvidersView.vue: the alias table's ModelChip cell read
row.original.modelMeta.contextTokens without a guard, unlike the
provider path's own modelMetaFor fallback — a response from a server
build older than this field (version skew) would throw instead of just
omitting the hover detail. Now optional-chained to match. The visible
context chip beside each model gains title="context window" and
text-xs (was an arbitrary text-[10px]), matching the rest of the panel's
muted-label sizing convention. Folded review minors.
This commit is contained in:
2026-08-22 02:13:12 +01:00
parent 5e3cf919d9
commit 9b60c0afae
3 changed files with 44 additions and 18 deletions
+20 -8
View File
@@ -5,13 +5,22 @@ import { formatCompactCount, formatExactInt } from '@/lib/format'
/**
* CompactNumber renders a large raw token/request count in its compact
* SI-style form ("1.23M") while carrying the exact value via both
* `title` (mouse hover) and `aria-label` (keyboard/screen-reader parity
* — the same established pattern ProviderRateBadge.vue and ModelChip.vue
* already apply to their own hover detail). Used anywhere a usage count
* renders: DataTable numeric columns (usage-columns.ts, target-
* columns.ts, via `h(CompactNumber, { value })`), the Usage tab's Total
* card, and a group accordion's trigger/detail stat grid (UsageView.vue).
* SI-style form ("1.23M") while carrying the exact value via `title`
* (mouse hover) and a visually-hidden sibling span (keyboard/screen-
* reader parity). Used anywhere a usage count renders: DataTable numeric
* columns (usage-columns.ts, target-columns.ts, via
* `h(CompactNumber, { value })`), the Usage tab's Total card, and a
* group accordion's trigger/detail stat grid (UsageView.vue).
*
* Review fix (folded minor, ARIA): `aria-label` on a plain `<span>` is
* an axe `aria-prohibited-attr` violation — a `<span>`'s implicit ARIA
* role ("generic") does not support name computation at all, so
* `aria-label`/`aria-labelledby` there is simply invalid, not merely
* discouraged. The visible, compact text is marked `aria-hidden="true"`
* so assistive tech skips the abbreviated form, and a `sr-only` sibling
* span (visually hidden, still in the accessibility tree) carries the
* exact value instead — the same visible-abbreviation-plus-hidden-full-
* text pattern ProviderRateBadge.vue now applies too.
*
* Cost figures are NOT this component's concern — formatCost already
* shows full 4-decimal precision, so there is nothing to compact or
@@ -24,5 +33,8 @@ const exact = computed(() => formatExactInt(props.value))
</script>
<template>
<span :title="exact" :aria-label="exact">{{ compact }}</span>
<span :title="exact">
<span aria-hidden="true">{{ compact }}</span>
<span class="sr-only">{{ exact }}</span>
</span>
</template>