docs(uix): audit progress and meter contracts

astra
dev 2 weeks ago
parent 85ff2eaaf5
commit b81ffeaf41

@ -177,6 +177,11 @@ posicionamiento se trasladó al módulo compartido.
| Riesgo funcional | L-140: Select.Value no conserva etiqueta con portal cerrado | Corregir sólo tras decidir owner persistente de value→label |
| Revisión masiva | L-152: reenvío de `bind:ref` en wrappers Eidos/Soma | Auditoría por familia y prueba cliente por wrapper; no sustitución global |
La auditoría individual de la familia de indicadores de rango consta en
[Progress y Meter](./progress-meter.md): contratos separados, duplicación pequeña
sin extracción rentable, referencias actualizadas y el valor ARIA fuera de rango
registrado sin cambiar comportamiento.
## Sema
`engine.ts` y `resolver.ts` concentran las políticas de frecuencia, dominancia,

@ -0,0 +1,69 @@
---
title: UIX — auditoría individual de Progress y Meter
type: notes
audience: human + agent
authority: process — evaluación, no contrato de componente
status: reviewed
---
# Progress y Meter — auditoría de la fase 6
Fecha: 2026-09-23. Alcance autorizado por la continuidad del plan de calidad:
evaluar duplicación y defectos sin cambiar la dinámica de los componentes. Se
releyeron las cuatro guías de pre-flight indicadas en `AGENTS.md`. El árbol
`web/` queda excluido.
## Pre-flight y referencias
| Aspecto | Progress | Meter | Decisión |
| ----------------- | -------------------------------------- | -------------------------------------- | ---------------------------------------- |
| Propósito y rol | Finalización de tarea, `progressbar` | Medición de rango, `meter` | Mantener contratos separados |
| Valor desconocido | `null` omite `aria-valuenow` | No admite `null` | No unificar providers |
| Estado | `indeterminate/loading/loaded` | `below/optimum/above` | Conservar clasificación en Soma |
| Partes | Provider, Label, ValueText, Indicator | Provider, Indicator | No fabricar partes para lograr simetría |
| Eidos | `size=xs…xl`, `shape=linear/circular` | `size=xs…xl`, `shape=linear/circular` | Mantener wrappers pequeños y CSS propios |
| Langs/Sema | Un fallback de nombre; sin evento Sema | Un fallback de nombre; sin evento Sema | Mantener catálogos separados |
| Referencia oficial | Equivalente | Diferencia relevante para UIX |
| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| [WAI-ARIA APG, rangos](https://www.w3.org/WAI/ARIA/apg/practices/range-related-properties/) y [Meter](https://www.w3.org/WAI/ARIA/apg/patterns/meter/) | Roles `progressbar` y `meter` | El valor ARIA conocido debe estar dentro del rango; sólo `progressbar` puede omitirlo cuando es indeterminado |
| [Radix Progress](https://www.radix-ui.com/primitives/docs/components/progress) | Root e Indicator | No ofrece Meter ni mínimo configurable en Progress |
| [Ark UI Progress Linear](https://ark-ui.com/docs/components/progress-linear) | Root, Label, ValueText, Track y Range | UIX pinta el track y el fill en Indicator, sin parte Track separada |
| [Bits UI Progress](https://bits-ui.com/docs/components/progress) y [Meter](https://bits-ui.com/docs/components/meter) | Roots separados | Bits ya tiene Meter; sus labels y fills se componen externamente |
| [React Aria ProgressBar](https://react-aria.adobe.com/ProgressBar) y [Meter](https://react-aria.adobe.com/Meter) | Dos componentes de rango | Ofrece formato de valor y porcentaje como render prop; UIX usa `valueText` y variables CSS |
Morfo declara roles, partes y atributos; Soma calcula estados, porcentajes y
nombre accesible; Eidos resuelve talla/forma y pinta; Langs aporta el fallback.
No hay split container/item, variantes ni color intent nuevos en esta auditoría.
Las diferencias de partes y de estado impiden una extracción de provider común
sin oscurecer contratos. El alcance aquí es revisar y corregir documentación;
ninguna API o receta cambia.
## Fichas individuales
| Componente | Evidencia de duplicación | Calidad y riesgo | Pruebas existentes | Disposición |
| ---------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| Progress | Repite con Meter la fórmula de porcentaje, el patrón de wrapper Eidos y algunas reglas de talla/anillo | El estado indeterminado, la orientación, el Label y ValueText son propios. La documentación de Soma decía `value=0` por defecto aunque el wrapper y el tipo usan `null` | Tres tests cliente del provider; `component:audit --only progress` PASS | Corregir el default documentado. No extraer una fórmula tan pequeña ni introducir una capa CSS compartida sin reducción neta y comparación visual |
| Meter | Misma fórmula y estructura básica de wrapper; umbrales y cuatro porcentajes son propios | La comparación con Bits UI estaba obsoleta. La prueba actual espera `aria-valuenow=150` con `max=100`, mientras el CSS visual se satura en 100; APG pide que el valor ARIA esté dentro del rango | Dos tests cliente del provider, incluido el caso fuera de rango; `component:audit --only meter` PASS | Corregir la comparación documental. Registrar el desacuerdo ARIA como candidato funcional separado, sin cambiar valor ni tests en este lote |
El caso fuera de rango también es posible en Progress: `percentage` se satura,
pero Morfo emite el valor original como `aria-valuenow`. Decidir si se valida,
se satura o se rechaza el valor requiere un contrato explícito para ambos
componentes y pruebas de consumidor; hacerlo como «limpieza» alteraría el
comportamiento ya observado. El caso `min >= max` tiene la misma necesidad de
decisión. No se declara corregido.
La repetición útil es escasa: extraer `Math.max(0, Math.min(100, …))` obligaría
a crear un helper e imports para dos llamadas, sin una reducción material. Los
selectores CSS parecidos usan nombres de tokens, jerarquías DOM y estados
distintos; un selector combinado acoplaría dos recetas sin simplificar su
mantenimiento. Los wrappers visuales son suficientemente pequeños.
## Verificación de esta tanda
- Tras las correcciones documentales: `docs:check` — 0 errores, 0 avisos.
- Tests dirigidos de los dos providers — 2 archivos, 5 tests, todos pasan.
- `component:audit -- --only progress` y `--only meter` — PASS cada uno.
- Sin cambios de producción, CSS, pruebas, API, DOM ni árbol `web/`; ahorro de
código de producción: **0 líneas**. Esta auditoría evita una extracción sin
beneficio demostrado y deja dos discrepancias de documentación corregidas.

@ -32,7 +32,7 @@ referencia.
| `low/high/optimum` attrs | Morfo/Soma | `data-low`, `data-high`, `data-optimum` |
| Zone state | Soma | `below`, `optimum`, `above` |
| CSS percentages | Soma | `--_meter-value-pct`, `--_meter-low-pct`, `--_meter-high-pct`, `--_meter-optimum-pct` |
| Visual size | Eidos | `sm`, `md`, `lg` |
| Visual size | Eidos | `xs`, `sm`, `md`, `lg`, `xl` |
| Visual shape | Eidos | `linear`, `circular` |
## Talla y tema
@ -71,7 +71,7 @@ barre `data-shape` — la demo arranca en `linear` y 14 de 28 no tenían nodo.
| MDN ARIA meter | Exige nombre accesible y `aria-valuenow/min/max`; recuerda que los descendientes del meter son presentational. | Cumple nombre accesible y valor ARIA; evita crear `Label`/`ValueText` Eidos-only porque eso debe vivir en Morfo/Soma si se formaliza. |
| React Aria Meter | Componente dedicado con label, value label, `formatOptions`, `minValue/maxValue` y `percentage` render prop. | Cubre `role`, rango, value text y porcentaje via CSS var; no tiene label/value visual como partes, gap diferido a Soma/Morfo. |
| Ark UI | No tiene Meter dedicado; muestra un strength meter dentro de Password Input como composicion. | Eidos tiene componente dedicado. |
| Radix / Bits | No tienen Meter dedicado. | Eidos tiene componente dedicado. |
| Radix / Bits | Radix no tiene Meter dedicado; Bits UI sí ofrece `Meter.Root`, con fill y textos compuestos por el consumidor. | Eidos conserva `Provider` e `Indicator` propios; no añade partes visuales sólo por paridad. |
Fuentes:
@ -80,7 +80,7 @@ Fuentes:
- [React Aria Meter](https://react-aria.adobe.com/Meter)
- [Ark UI Password Input](https://ark-ui.com/docs/components/password-input)
- [Radix Icons/Primitives index](https://www.radix-ui.com/)
- [Bits UI docs](https://bits-ui.com/docs/introduction)
- [Bits UI Meter](https://bits-ui.com/docs/components/meter)
## Decisiones
@ -137,12 +137,12 @@ esa politica.
## Gaps
| Gap | Disposición | Detalle |
| --------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------- |
| API extendida sobre las referencias externas | **diferir** | Sólo si llega un caso de uso real. Mantener la superficie estable. |
| Cobertura adicional de variantes visuales | **diferir** | El recipe cubre sm/md/lg + solid/outline/ghost. Más variantes requieren caso concreto. |
| Documentación per-prop exhaustiva | **implementar** | Cuando se cierre el ciclo de remediación de cada componente. |
| Tests browser-level del flujo completo (Playwright) | **implementar** | Cobertura visual + interacciones. Se hace en una pasada conjunta de tests. |
| Gap | Disposición | Detalle |
| --------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------ |
| API extendida sobre las referencias externas | **diferir** | Sólo si llega un caso de uso real. Mantener la superficie estable. |
| Cobertura adicional de variantes visuales | **diferir** | La receta cubre tallas xs…xl y formas linear/circular. Más variantes requieren un caso concreto. |
| Documentación per-prop exhaustiva | **implementar** | Cuando se cierre el ciclo de remediación de cada componente. |
| Tests browser-level del flujo completo (Playwright) | **implementar** | Cobertura visual + interacciones. Se hace en una pasada conjunta de tests. |
## Passive justification

@ -83,20 +83,23 @@ Zone resolution:
| Feature | Soma | Radix | Ark UI | bits-ui | react-aria |
| -------------------------------- | :--: | :---: | :----: | :-----: | :--------: |
| Dedicated component | ✅ | ❌¹ | ❌² | ❌ | ✅ |
| `role="meter"` | ✅ | — | — | — | ✅ |
| `low` / `high` / `optimum` props | ✅ | — | — | — | ❌³ |
| `data-state` zone attribute | ✅ | — | — | — | ❌ |
| `--_meter-value-pct` CSS var | ✅ | — | — | — | ❌⁴ |
| Translated default `aria-label` | ✅ | — | ❌ | — | ❌ |
| Indicator part | ✅ | — | — | — | ❌ |
| `aria-valuetext` override | ✅ | — | ✅ | — | ✅ |
| Dedicated component | ✅ | ❌¹ | ❌² | ✅ | ✅ |
| `role="meter"` | ✅ | — | — | ✅ | ✅ |
| `low` / `high` / `optimum` props | ✅ | — | — | ❌ | ❌³ |
| `data-state` zone attribute | ✅ | — | — | ❌ | ❌ |
| `--_meter-value-pct` CSS var | ✅ | — | — | ❌ | ❌⁴ |
| Translated default `aria-label` | ✅ | — | ❌ | ❌ | ❌ |
| Indicator part | ✅ | — | — | ❌ | ❌ |
| `aria-valuetext` override | ✅ | — | ✅ | ✅ | ✅ |
¹ Radix does not ship a Meter primitive.
² Ark UI does not currently ship a dedicated Meter component; its password-input docs show a strength meter example assembled by the user.
³ react-aria uses `minValue` / `maxValue` and value formatting, but does not model low/high/optimum zones.
⁴ react-aria exposes a `percentage` render prop instead of a CSS var.
Bits UI now ships [`Meter.Root`](https://bits-ui.com/docs/components/meter); its visual fill and
labels are composed by the consumer. The earlier comparison incorrectly listed it as absent.
## Usage
### Battery indicator

@ -30,7 +30,7 @@ For a value rendered inside a known range (disk usage, score), use `Meter` inste
| Prop | Type | Default | Description |
| ----------------- | ---------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------- |
| `id` | `string` | auto | DOM id. |
| `value` | `number \| null` | `0` | Current progress. `null` → indeterminate (no ARIA value, `data-state='indeterminate'`). |
| `value` | `number \| null` | `null` | Current progress. `null` → indeterminate (no ARIA value, `data-state='indeterminate'`). |
| `min` | `number` | `0` | Lower bound. |
| `max` | `number` | `100` | Upper bound. |
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Data/CSS orientation. |

Loading…
Cancel
Save

Powered by TurnKey Linux.