# button — ficha de auditoría de componente - **Fecha**: 2026-07-07 · **Auditoría**: componentes (piloto 1 — calibra el formato) - **Método**: solo análisis — los fixes se ejecutan tras auditar TODO el catálogo; esta ficha registra hallazgos + **propuestas de solución** (locales y las que escalan al sistema). - **Tier**: control interactivo · **Morfo scope**: `['soma','sema','eidos']` · **Familia**: acciones - **Capas**: [morfo](../../../src/uix/morfo/components/button.ts) · [soma](../../../src/uix/soma/components/button/) · [eidos](../../../src/uix/eidos/components/button/) · sema: **sin pack** (cascada por defecto) - **Máquina** (`component:audit --only button`): **NEEDS-WORK** — `E-2.3` (falta README a nivel eidos). R-rules (color/tipografía/recipe-contract): **limpias**. _(Reglas D-\* de demo excluidas — corrección de alcance 2026-07-07: la auditoría cubre solo las capas del componente.)_ - **Vigencia re-auditoría**: ficha piloto — YA a profundidad de referencia (todas las capas leídas con evidencia); este retrofit solo extrae las demos del alcance. ## Tabla de dimensiones | # | Dimensión | Estado | Evidencia / nota | | --- | --------------------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 | Composición vs declaración | ✓ | API compositiva; `child` (asChild) ✓; Spinner morfo-declarado con razón documentada (derivado de estado, no composición) | | 2 | Naming de props | ✓ con hallazgo | Props canónicas (`disabled/loading/type/color/intent`); ver **S-1** (hooks CSS mixtos clase/data) | | 3 | Coherencia semántica ecosistema/theming | ✓ ejemplar | `intent` = SOLO visual (cap. 22 §11 citado); contact sin intent; cascada intent-gana-sobre-color en provider; anti-patrón `contact+fulfill` documentado (cap. 22 §8) | | 4 | API normalizada | ✓ | `OptsFromProps`; defaults documentados; `color` tipado laxo para paleta per THEMING §25.5 con re-type en eidos; `onPress` = feedback diagnóstico (no side-effect) | | 5 | Tokens usados | ✓ | Piloto de la cascada `--button-palette-*`; recipe R-limpio; focus por `--focus-ring-*`; 0 usos de state-layer = **correcto** (todas las variantes son valenced — swap de paleta; sin tier neutro) | | 6 | Parte semántica | validar | **F-6**: sin pack sema — `contact.activate` suena por el default de familia → ver S-3 | | 7 | Animaciones | ✓ | Sin `@keyframes` perceptuales locales; press vía sistema (`--press-*`); spinner = periodo funcional | | 8 | Contrato morfo | ✓ con hallazgo | Ejemplar: APG enlazado, keyboard declarado, textos localizados, `data-state` eliminado con la razón del clobbering (audit 05-27) documentada. **F-1**: Spinner con `archetype: 'item'` siendo parte display | | 9 | Accesibilidad | ✓ | `aria-busy` en loading, `aria-disabled`, iconos `aria-hidden`, `iconOnly` conserva label sr-only, `loadingText` mantiene children sr-only, Enter/Space nativos, touch coarse por arquetipo trigger | | 10 | Adopción theming | ✓ | Bundle de size vía recipe; depth n/a (no flota); focus modelo superficies (outline) ✓; palette cascade ✓ | | 11 | Composición interna | ✓ | Participa en `Field.Provider` (disabled OR-merge); consume contexto `ButtonGroup` para defaults (prop explícita gana); es el COMPUESTO por IconButton/triggers | | 12 | Estados y ciclo | ✓ | disabled/loading completos (click gated en ambos); `type` default 'button' (anti-submit implícito); controlled n/a (stateless) | | 13 | Disciplina runtime | ✓ | Runtime-direct; sin DOM manual; sin timers; `$derived` limpios | | 14 | ~~Demo testbed~~ | fuera de alcance | Demos excluidas de la auditoría (corrección 2026-07-07) — sus reglas D-\* viven en tmp/component-audit.md, no aquí | | 15 | Docs + tests | **gap doble** | **F-4**: falta README eidos (el de soma completo con `## Sema events` ✓) · **F-7**: **CERO tests** de provider/wrapper · **F-2**: doc de `aria-label` en soma types referencia `iconOnly` (prop de eidos — fuga de capa) · **F-3**: docblock del wrapper cita `data-state` que el morfo eliminó | | 16 | i18n | ✓ | `texts` del morfo localizados (`#?components.button.*`); `loadingText` del consumidor; sin strings hardcoded | ## Hallazgos y propuestas de solución — del componente | ID | Hallazgo | Propuesta de solución | | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **F-1** | Spinner declara `archetype: 'item'` siendo parte display (`aria-hidden`, no interactiva) — el gotcha conocido: item arrastra estilo interactivo | Quitar el archetype de la parte Spinner (omitirlo, como manda la doctrina para partes display); verificación visual antes/después del spinner en los 3 placements | | **F-2** | En soma `types.ts`, el doc de `aria-label` dice "Required when `iconOnly` is true" — `iconOnly` es prop de EIDOS, no existe en soma (fuga de capa en docs) | Reescribir el JSDoc sin referencia cross-capa ("override del nombre accesible; obligatorio cuando el botón no tiene texto visible") | | **F-3** | Docblock de eidos `button.svelte` dice "Soma owns … `data-state`" — button eliminó `data-state` (el propio morfo documenta por qué) | Actualizar el docblock (data-loading/data-disabled por presencia; sin data-state) | | **F-4** | Falta `eidos/components/button/README.md` (convención que la máquina exige, `E-2.3`); el README de soma está completo | Crear el README eidos siguiendo el patrón de los existentes (css-field/number-field): API visual, variantes, tokens públicos del recipe, composición | | ~~F-5~~ | _(retirado — hallazgo de demo, fuera del alcance de la auditoría)_ | — | | **F-6** | Sin pack sema propio: `contact.activate` materializa el default de la familia | Ver **S-3** (es cuestión doctrinal de catálogo, no de button) | | **F-7** | **Cero tests** de provider/wrapper — el componente insignia sin suite | Suite browser del provider (click→contact-activate, gate disabled/loading, Field-merge, cascada intent/color, snippetProps) + wrapper (spinner placements, iconOnly sr-only, loadingText swap). Alcance/momento: ver **S-2** | ## Hallazgos que escalan al sistema / theming | ID | Hallazgo (visto en button, alcance = catálogo) | Propuesta | | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **S-1** | **Hooks estructurales del wrapper como clases** (`.eidos-button-{body,icon,sr-only,loading-text}`, 6 selectores) conviviendo con data-attrs (`[data-button-icon]`). Las clases **escapan a eidos-lint** (solo valida `[data-*]`) y crean segunda gramática de hooks | **Norma de catálogo**: hooks de selector SIEMPRE data-attrs (`data-{c}-{kebab}`), nunca clases; clase permitida solo como utility sin selector en CSS (p. ej. contenedor neutro). + Guard: lint que detecte `\.eidos-` / clases con estilos en CSS de componentes. Aplicar en el fix-phase a todos los que salgan en el barrido | | **S-2** | Ausencia de tests no es exclusiva de button — política de alcance pendiente | **Decisión de catálogo** (checkpoint post-barrido): (a) suite por componente durante fase de fixes, o (b) batch de testing como fase propia posterior. La ficha de cada componente registra su cobertura actual para dimensionar | | **S-3** | Componentes de contacto genérico sin pack sema: ¿el default de familia ES la firma, o el catálogo quiere carácter per-componente en los buques insignia? | **Decisión doctrinal** (checkpoint): criterio de cuándo un componente MERECE pack (hoy: 38 packs, sesgo a fields/pickers). Propuesta: default-de-familia = firma legítima para contact genérico (button, link); pack solo donde el patrón añade significado (ya es el statu quo — canonizarlo por escrito en CANON/sema doctrine) | | **S-4** | El README vive en DOS niveles (soma completo aquí; la máquina exige el de eidos) — heterogeneidad de dónde vive la doc del componente | **Norma**: definir el nivel canónico (propuesta: eidos = puerta del consumidor, con API visual + tokens; soma README = contrato headless). La máquina ya empuja a eidos (`E-2.3`) — ratificar y documentar en component-guide | ## Veredictos _(pendiente — checkpoint único post-barrido; las propuestas de arriba se ejecutan en la fase de fixes)_