fix(carousel): chevron direction follows orientation

Vertical carousel still rendered horizontal chevrons (< / >) for the
prev / next triggers, which read as wrong-direction even though the
click handlers worked correctly. The user complaint "varios clicks" was
the visual mismatch making each click look like it did nothing — value
WAS advancing index 0→1→2 on each click but the icons pointed sideways
in a vertical rail.

- Added `orientation` to the eidos carousel context so the prev / next
  trigger wrappers can pick the right chevron without passing props.
- Re-exported `Direction` + `Orientation` from soma's carousel module
  so the eidos context can type-check them.
- Prev trigger now uses `direction='up'` in vertical, `'left'` in
  horizontal. Next trigger mirrors with `'down'` / `'right'`.

Verified: index goes 0→1→2 on successive clicks; chevrons now show
`^` (top) and `v` (bottom) in vertical orientation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent a6baae0cf1
commit b1b1e39dc0

@ -0,0 +1,388 @@
# Auditoria Codex de Eidos / UIX demos
Fecha: 2026-05-23
Rama auditada: `active-uix`
Ambito principal: `src/uix/eidos/components/*` y demos publicas en
`web/routes/uix/components/*`
## Resumen ejecutivo
El ecosistema Eidos/UIX ya tiene una base visual y documental amplia: todas las
83 demos publicas bajo `web/routes/uix/components` cargan, renderizan `canvas`,
`stage` y seis tabs de documentacion; no se detectaron errores de consola en la
pasada Playwright de escritorio. Tambien hay coherencia visual general: stage
comun, trace strip, controles agrupados, tokens Eidos, props visuales
homogeneas y un patron compound bastante estable.
Pero el resultado no puede considerarse cerrado. La auditoria automatica del
repo marca 12 componentes como `NEEDS-WORK`, hay demos que incumplen el contrato
obligatorio de snippets, existen gaps de Sema/playback en componentes
interactivos y el shell `/uix` no es usable en viewport movil: todas las rutas
auditadas desbordan horizontalmente a 390px por un problema del marco de docs.
El area de mayor riesgo sigue siendo la familia de pickers/calendarios. Han
mejorado respecto a los fallos anteriores: hay controles visibles para
min/max, minDays/maxDays, modal/inline, footer por composicion, clear/cancel/close
y numberOfMonths. Aun asi, DatePicker, DateRangePicker, Tooltip, MonthGrid y
YearGrid no cumplen del todo el contrato de playback Sema/demo y necesitan tests
funcionales especificos para apertura, seleccion, cierre, limites y navegacion.
## Evidencia usada
Comandos y recorridos ejecutados:
- `npm run component:audit`
- Recorrido Playwright desktop `1440x1000` sobre las 83 demos publicas.
- Recorrido Playwright mobile `390x844` para detectar overflow del shell.
- Capturas del stage de todas las demos en `tmp/uix-audit/screens/*.png`.
- Hojas de contacto visuales en:
- `tmp/uix-audit/contact-sheet-1.png`
- `tmp/uix-audit/contact-sheet-2.png`
- `tmp/uix-audit/contact-sheet-3.png`
- Resumen estructural en `tmp/uix-audit/quick-summary.json`.
- Reporte canonico generado en `tmp/component-audit.md`.
Nota: habia cambios previos en `src/uix/soma/components/carousel/*`. Esta
auditoria observa el estado actual sin revertir ni tocar esos cambios.
## Resultado cuantitativo
### `npm run component:audit`
| Metrica | Resultado |
| --- | ---: |
| Componentes auditados por contrato | 92 |
| PASS | 80 |
| NEEDS-WORK | 12 |
| BROKEN | 0 |
### Recorrido visual/funcional Playwright
| Metrica | Resultado |
| --- | ---: |
| Demos publicas recorridas | 83 |
| Rutas con error HTTP/consola | 0 |
| Demos sin `data-uix-canvas-inner` | 0 |
| Demos sin `data-uix-stage` | 0 |
| Demos sin seis tabs declaradas | 0 |
| Overflow desktop | 0 |
| Overflow mobile | 83 |
| Demos con menos de 2 bloques de snippet en Live | 5 |
El dato de overflow mobile es sistemico: los valores se repiten en todas las
rutas, por lo que apunta al shell `/uix` y no a un componente concreto.
## Hallazgos P1
### P1.1 — 12 componentes no pasan el auditor canonico
Estos componentes no deberian declararse cerrados:
| Componente | Errores principales |
| --- | --- |
| `alert-dialog` | Falta selector root `[data-alert-dialog]` en recipe. |
| `button` | Verbo Sema `action` invalido, mismatch `commit.action`, falta README. |
| `carousel` | Falta README y falta `somaSnippet` en demo. |
| `command` | Falta README, no hay `:focus-visible`, falta APG, tipografia literal. |
| `grid-list` | Falta README, color raw `#fff`, falta `somaSnippet`, readonly sin estilo. |
| `link-preview` | Falta README, tipografia literal. |
| `listbox` | Falta README, readonly sin estilo, tipografia literal. |
| `menubar` | Falta README, tipografia literal. |
| `navigation-menu` | Falta README, falta `somaSnippet`, falta APG. |
| `range-calendar` | Falta README, falta `somaSnippet`, readonly sin estilo. |
| `search-field` | No hay `:focus-visible`, falta APG, readonly sin estilo. |
| `time-range-field` | Falta README, falta `somaSnippet`, readonly/invalid sin estilo. |
Accion: cerrar primero estos 12 antes de seguir ampliando componentes. El
script ya da la lista exacta de reglas incumplidas.
### P1.2 — El shell `/uix` no es responsive en mobile
En viewport `390x844`, todas las demos desbordan horizontalmente. El overflow
detectado es practicamente constante (`+495` a `+529px`), lo que indica un
problema del layout documental: rail/sidebar/canvas con anchura minima fija o
grid no colapsado.
Impacto:
- No se puede auditar mobile por componente con fiabilidad.
- Los overlays, popovers y pickers quedan condicionados por un marco que ya
esta roto.
- La web de docs no cumple un minimo de consumo movil.
Accion:
- Revisar `web/routes/uix/+layout@.svelte` y `web/routes/uix/uix.css`.
- Definir breakpoint real para ocultar/convertir sidebar en drawer.
- Asegurar que `data-uix-canvas-inner`, tabs, stage y tablas no fuerzan
`min-width` por encima del viewport.
- Repetir auditoria mobile tras corregir el shell.
### P1.3 — Demos con contrato Live incompleto: falta `somaSnippet`
El contrato de `DEMO_AUTHORING_GUIDE.md` exige snippets Soma y Eidos en Live.
Faltan en:
- `carousel`
- `grid-list`
- `navigation-menu`
- `range-calendar`
- `time-range-field`
Impacto:
- La demo no enseña el contrato headless real.
- El consumidor ve la capa Eidos pero no la composicion Soma equivalente.
- Rompe la promesa de separacion por capas del ecosistema.
Accion: añadir `somaSnippet` reactivo y comprobar que refleja los controles
visibles del stage.
### P1.4 — Gaps de Sema/playback en componentes interactivos
Varias demos interactivas no tienen playback Sema normalizado con
`data-uix-play` o el auditor avisa que no hay `uix.events.emit(...)`.
Casos relevantes:
- `date-picker`: PASS con warning `D-4.3`; no hay `uix.events.emit(...)`.
- `date-range-picker`: PASS con warning `D-4.3`; no hay `uix.events.emit(...)`.
- `tooltip`: PASS con warning `D-4.3`; no hay playback claro.
- `month-grid` y `year-grid`: eventos `nav-step` no siguen la forma
`{verb}-X` o `{family}-X`; el play usa chip/boton no estandar.
- `button`: usa verbo Sema invalido `action`.
- `alert-dialog`: aparece como pasivo aunque la demo muestra acciones de
dialogo heredadas; hay que aclarar si el evento pertenece al wrapper o al
compuesto.
Impacto:
- El usuario no puede verificar eventos desde la tab Sema de forma uniforme.
- La documentacion vuelve ambiguo que eventos son propios, heredados o
compuestos.
- Se rompe la trazabilidad Morfo -> Soma -> Sema -> Eidos.
Accion:
- Normalizar todos los botones de playback a `data-uix-play`.
- Para componentes compuestos, documentar y listar eventos heredados sin decir
que la superficie es 0 si el stage permite acciones visibles.
- Corregir nombres de eventos que no respetan el vocabulario Sema.
### P1.5 — Pickers y calendarios siguen siendo zona de alto riesgo
Estado visual observado:
- `calendar` renderiza uno/dos meses con heading centrado y trace funcional.
- `range-calendar` renderiza seleccion y rango, pero falla auditor por README y
`somaSnippet`.
- `date-picker` muestra field + popover + calendar + footer de acciones.
- `date-range-picker` muestra dos inputs, dos meses, footer y controles de
limites/rango.
- `time-picker` y `time-range-picker` muestran popover con sliders y footer.
Riesgos abiertos:
- DatePicker/DateRangePicker no tienen playback Sema normalizado.
- La apertura por trigger necesita test E2E estable; en una pasada automatizada
el selector `data-date-picker-trigger` no dejo el provider en `open`, aunque
la captura visual previa si lo muestra abierto.
- RangePicker debe tener regresiones automatizadas para:
- seleccionar start sin desplazar al mes siguiente de forma inesperada,
- seleccionar end sin auto-navegar,
- deseleccionar solo endpoint afectado,
- mantener start/end con colores diferenciados,
- respetar `minValue`, `maxValue`, `minDays`, `maxDays`,
- validar min/max invalidos y surfacing via UI/logger,
- clear sin cierre accidental,
- cancel revirtiendo snapshot de apertura,
- modal bloqueando outside click y manteniendo escape/cierre compuesto.
Accion: crear suite Playwright especifica para `calendar`, `range-calendar`,
`date-picker`, `date-range-picker`, `time-picker` y `time-range-picker`.
## Hallazgos P2
### P2.1 — Warnings de CSS/token contract
Hay warnings que no rompen render, pero indican drift con Eidos:
- Literales tipograficos en `command`, `link-preview`, `listbox`, `menubar`,
`code-block`, `field`, `kbd`, `combobox`, `time-range-picker`.
- Raw color en `grid-list` (`#fff`).
- Estados declarados pero no estilados: `readonly`, `invalid`, segun
componente.
- `:focus-visible` ausente en `command` y `search-field`.
Accion:
- Sustituir literales por tokens o justificar con comentario `/* literal: ... */`.
- Eliminar raw color.
- Estilar `readonly`/`invalid` donde Morfo los declara.
- Añadir focus ring visible a componentes interactivos.
### P2.2 — Demos con controles presentes pero valor pedagogico desigual
La plantilla se aplica de forma amplia, pero no siempre con el mismo nivel de
claridad:
- Componentes complejos (`file-upload`, `stepper`, `listbox`, `grid-list`,
`range-calendar`, `date-range-picker`) quedan comprimidos en un stage pequeno.
- Algunas demos muestran mucho estado en trace, pero el stage no evidencia de
un vistazo que cambio produjo cada control.
- Los controles de segmentos en `date-range-picker` existen, pero necesitan
copy y ejemplo visual mas directo para que se entienda por que start/end
segments importan si el usuario tambien elige fechas en calendario.
- `pagedNavigation` existe como control, pero deberia acompañarse de estado
observable: mes actual antes/despues, step esperado y diferencia cuando hay
dos meses.
Accion:
- Para componentes compuestos, ampliar el stage o usar escenarios separados
dentro de Live.
- Añadir microestado visible cuando un control no modifica visualmente de forma
obvia el componente.
- Evitar controles que solo cambian el snippet o una prop interna sin evidencia
visual inmediata.
### P2.3 — Componentes pasivos con 0 eventos necesitan justificacion visible
El ecosistema ya acepta componentes pasivos, pero cada demo debe justificarlo.
Se ven casos correctos en varias paginas, pero hay que revisar que el README de
cada componente pasivo lo deje explicitamente cerrado.
Watchlist:
- `avatar`
- `breadcrumb`
- `field`
- `meter`
- `progress`
- `tooltip`
- `time-range-field`
Accion: cada README debe explicar si el componente es pasivo, estructural o si
sus acciones pertenecen a otro componente compuesto.
## Coherencia visual del ecosistema
### Lo que esta bien
- La shell de demo es consistente: header, meta pills, stage, trace y seis tabs.
- El lenguaje visual Eidos es reconocible: superficies limpias, bordes suaves,
tokens de color, foco morado/primary, estados discretos.
- Los componentes compound respetan en general la forma root + hijos attached.
- El trace strip ayuda a ver eventos y estado en componentes interactivos.
- La mayoria de componentes tienen controles suficientes para explorar size,
variant, color, estado y comportamiento.
- No hay rutas rotas ni errores de consola en desktop.
### Lo que baja el nivel
- El layout movil de docs esta roto.
- La auditoria canonica aun marca 12 componentes como no cerrados.
- Hay demos que no cumplen la plantilla obligatoria.
- Falta una politica cerrada para eventos compuestos/heredados.
- Algunos componentes grandes se ven mas como prueba tecnica que como demo
pedagogica de producto.
- La capa visual depende demasiado de que el usuario sepa interpretar trace y
controles; en varios casos el stage deberia explicar por si mismo el cambio.
## Inventario de demos publicas auditadas
Renderizan sin error y con stage:
`accordion`, `alert-dialog`, `aspect-ratio`, `auto-grid`, `avatar`, `banner`,
`box`, `breadcrumb`, `button`, `calendar`, `carousel`, `checkbox`, `code`,
`code-block`, `collapsible`, `color-field`, `color-picker`, `combobox`,
`command`, `container`, `context-menu`, `date-field`, `date-picker`,
`date-range-field`, `date-range-picker`, `dialog`, `display`, `drawer`,
`dropdown-menu`, `editable`, `field`, `file-upload`, `flex`, `float`, `form`,
`grid`, `grid-list`, `group`, `heading`, `highlight`, `icon`, `kbd`, `link`,
`link-preview`, `listbox`, `mark`, `menubar`, `meter`, `month-grid`,
`navigation-menu`, `number-field`, `pagination`, `pin-input`, `popover`,
`progress`, `radio-group`, `range-calendar`, `rating-group`, `scroll-area`,
`search-field`, `section`, `select`, `separator`, `slider`, `splitter`,
`stack`, `stepper`, `switch`, `tabs`, `tag-group`, `tags-input`, `text`,
`time-field`, `time-picker`, `time-range-field`, `time-range-picker`, `toast`,
`toggle`, `toggle-group`, `toolbar`, `tooltip`, `wrap`, `year-grid`.
## Componentes/contratos sin demo publica en `/uix/components`
El auditor de contratos ve componentes que no tienen demo publica Eidos:
- `announce`
- `clipboard`
- `drag-drop`
- `feed`
- `table`
- `tree-grid`
- `tree-view`
- `virtual-grid`
- `virtual-list`
Esto puede ser correcto si son headless, internos, pendientes o no publicos,
pero debe constar explicitamente en el mapa de componentes de la web.
Tambien existen piezas Eidos internas sin demo publica directa:
- `_layout`
- `picker-shell`
- `svg`
`svg` ya esta documentado como helper interno; `picker-shell` deberia quedar
marcado como interno en README/indice para evitar que parezca una omision.
## Matriz de saneamiento recomendada
### Fase 1 — Bloqueantes de contrato
1. Corregir los 12 `NEEDS-WORK` del `component:audit`.
2. Añadir READMEs faltantes.
3. Añadir `somaSnippet` en las cinco demos incompletas.
4. Corregir `button` Sema verb y eventos `nav-step`.
5. Añadir `:focus-visible` en `command` y `search-field`.
6. Eliminar raw color y literales tipograficos no justificados.
### Fase 2 — Shell de docs
1. Hacer responsive `web/routes/uix`.
2. Repetir auditoria mobile.
3. Asegurar que tablas, snippets y stages se desplazan internamente sin romper
el viewport.
### Fase 3 — Semantica y demos
1. Normalizar `data-uix-play` y `uix.events.emit(...)`.
2. Documentar eventos heredados en componentes compuestos.
3. Revisar todos los componentes marcados como 0 eventos.
4. Convertir los controles no evidentes en cambios observables.
### Fase 4 — Pickers/calendarios
1. Tests E2E para DatePicker y DateRangePicker.
2. Tests E2E para Calendar y RangeCalendar.
3. Tests E2E para TimePicker y TimeRangePicker.
4. Casos obligatorios: uno/dos meses, modal/no-modal, min/max, minDays/maxDays,
clear, cancel, close, seleccion/deseleccion parcial, navegacion paged y
granularidad `date/month/year` o `hour/minute/second`.
### Fase 5 — Visual QA repetible
1. Mantener un script de capturas del stage por componente.
2. Generar hoja de contacto por batch.
3. Comparar desktop y mobile tras cada tanda.
4. No declarar cerrado un componente compuesto sin captura abierta de su
overlay/popup/menu.
## Veredicto
Eidos/UIX esta en una fase avanzada de cobertura, pero todavia no en una fase
cerrada de calidad. La base visual es coherente y la arquitectura por capas se
ve en las demos, pero los gaps actuales son de contrato, demo y semantica, no
solo de CSS.
El siguiente trabajo correcto no es crear mas componentes: es cerrar los 12
`NEEDS-WORK`, arreglar el shell mobile, normalizar Sema/playback y blindar los
pickers con pruebas visuales y funcionales especificas.

@ -1,15 +1,17 @@
import { getContext, setContext } from 'svelte';
import type { CarouselColor, CarouselSize, CarouselVariant } from './types';
import type { Orientation } from '$soma/components/carousel';
/**
* Eidos-only context: visual concerns shared from the Provider down to
* sub-parts (prev/next triggers consume `size`, indicators consume
* `size` + `color`).
* sub-parts (prev/next triggers consume `size` + `orientation` so their
* chevron rotates correctly; indicators consume `size` + `color`).
*/
export type CarouselEidosContext = {
size: CarouselSize;
variant: CarouselVariant;
color: CarouselColor;
orientation: Orientation;
};
const KEY = Symbol('eidos:carousel');

@ -24,10 +24,12 @@
const carousel = getCarouselEidosContext();
const resolvedSize = $derived(size ?? carousel?.size ?? 'md');
// In vertical orientation `next` means "go down" — chevron mirrors.
const chevronDir = $derived(carousel?.orientation === 'vertical' ? 'down' : 'right');
</script>
{#snippet defaultIcon()}
<SvgChevron direction="right" />
<SvgChevron direction={chevronDir} />
{/snippet}
<Carousel.NextTrigger {...rest}>

@ -29,10 +29,12 @@
const carousel = getCarouselEidosContext();
const resolvedSize = $derived(size ?? carousel?.size ?? 'md');
// In vertical orientation `prev` means "go up" — chevron mirrors.
const chevronDir = $derived(carousel?.orientation === 'vertical' ? 'up' : 'left');
</script>
{#snippet defaultIcon()}
<SvgChevron direction="left" />
<SvgChevron direction={chevronDir} />
{/snippet}
<Carousel.PrevTrigger {...rest}>

@ -17,6 +17,7 @@
block = true,
verticalBlockSize,
value = $bindable(0),
orientation = 'horizontal',
style,
children: bodyContent,
...rest
@ -45,6 +46,9 @@
},
get color() {
return color;
},
get orientation() {
return orientation;
}
});
</script>
@ -52,6 +56,7 @@
<Carousel.Provider
{...rest}
bind:value
{orientation}
style={composedStyle}
data-size={resolvedSize}
data-variant={variant}

@ -7,6 +7,8 @@ export { default as NextTrigger } from './components/carousel-next-trigger.svelt
export { default as IndicatorGroup } from './components/carousel-indicator-group.svelte';
export { default as Indicator } from './components/carousel-indicator.svelte';
export type { Direction, Orientation } from '../../types';
export type {
CarouselProps as ProviderProps,
CarouselViewportProps as ViewportProps,

Loading…
Cancel
Save

Powered by TurnKey Linux.