|
|
# CONTINUE — el eje de dirección (RTL/LTR)
|
|
|
|
|
|
> **Kickoff**: _"Lee `docs/process/CONTINUE-direction.md` y sigue por §8."_
|
|
|
> **Fecha**: 2026-08-03 · Rama `alpha-0.1-dir-prefs` (sale de `alpha-0.1-sec-dom`).
|
|
|
> **§1, §6 y §7 son lo cerrado — no lo rehagas.** Lo abierto está en §2 (lo que
|
|
|
> sobrevive), §3 (ajeno al eje) y **§8, que es la cola priorizada para hoy**.
|
|
|
>
|
|
|
> Hermano de este documento: [`CONTINUE-player-rtl.md`](./CONTINUE-player-rtl.md),
|
|
|
> cuyo §1 cerró este hilo. Sus §2–§5 (disposición de botones del player, iconos de
|
|
|
> seek, escenario mixto, `Captions`, waveform-como-scrubber) siguen abiertos y son
|
|
|
> de otro asunto.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 1. Lo cerrado — 3 commits, verificados
|
|
|
|
|
|
| Commit | Qué |
|
|
|
| --- | --- |
|
|
|
| `013ceac57` | La raíz: `getDir()` reactivo · estampado condicional · `activeDir()` único · 99 ficheros |
|
|
|
| `5469e05df` | 49 demos + arnés `SystemAxes` a `auto·ltr·rtl` · prosa declarada inglesa · 51 ficheros |
|
|
|
| `538f4932b` | El handoff anterior decía que esto seguía roto |
|
|
|
| `ec533845d`…`714a1ddb5` | **§2.1 + §2.2 cerradas** (§6): guard RTL-1 · 23 hallazgos · los 12 `[dir='rtl']` → `:dir()` · el pin del `<main>` · tabs · virtualizadores · float-panel · rating-group · splitter · carousel |
|
|
|
| `8086f21d6`…`01b891b64` | **Las gráficas** (§7): 6 defectos de RTL en el chart + funnel, calendar-heatmap, heatmap matriz, radar, y los bloques de código |
|
|
|
|
|
|
Empezó como «el slider no responde al RTL» y eran **dos defectos independientes**:
|
|
|
|
|
|
**El del slider** — `slider.css` emparejaba `inset-inline-start: 50%` (lógico) con
|
|
|
`translateX(-50%)` (**físico, no se voltea**) en las tres reglas verticales. Medido
|
|
|
en Chrome: raíz/pulgar/ticks en x=961, raíl y relleno en x=955. El `Tick` se libraba
|
|
|
porque ya usaba `margin-inline-start` — ése era el idioma correcto del propio
|
|
|
fichero. Mismo defecto en `accordion.css` (`text-align: left`), que era el **único**
|
|
|
`text-align` físico de todo eidos.
|
|
|
|
|
|
**La causa raíz, que no estaba en ninguna hipótesis** — `makeDimension().get()`
|
|
|
(`arts/prefs/active-prefs.svelte.ts`) leía `engine.snapshot()` esquivando la celda
|
|
|
`$state`. Así que `soma.prefs.getDir()` era una **lectura sin tracking** y los ~64
|
|
|
componentes que resuelven su dirección desde prefs la **congelaban al montarse**.
|
|
|
La proyección DOM se salvaba porque usa `onChange`: misma dimensión, dos caminos de
|
|
|
lectura, uno solo reactivo.
|
|
|
|
|
|
**Y el estampado** — 33 de 37 componentes escribían `dir` con un valor SIEMPRE
|
|
|
concreto, así que una app que ponga `<html dir="rtl">` sin registrar la preferencia
|
|
|
se encontraba 33 islas del revés. Ahora el atributo se omite cuando nadie afirmó
|
|
|
nada. Es el modelo de **Zag** (`prop("dir")`), aplicado uniforme — Zag lo incumple
|
|
|
en 12 de sus 51 máquinas.
|
|
|
|
|
|
### El contrato, ratificado por el usuario
|
|
|
|
|
|
```
|
|
|
prop dir → soma.prefs.getDir() → 'ltr'
|
|
|
```
|
|
|
|
|
|
**SIN paso del padre.** El `dir` del DOM es **proyección, no fuente**
|
|
|
(`docs/architecture/active-architecture.md:407`). La herencia que se obtiene ahora
|
|
|
la hace el navegador por **ausencia** de atributo, no por ninguna lectura del DOM.
|
|
|
|
|
|
Fase posterior que el usuario dejó planteada, sin fecha: **qué propiedades podrían
|
|
|
heredarse implícitamente del padre** y si beneficia al framework.
|
|
|
|
|
|
### La forma única, para no volver a divergir
|
|
|
|
|
|
Había **diez** formas de resolver lo mismo en los wrappers y **cinco** de declarar
|
|
|
el tipo, con un cuarto escalón semántico —heredar del menú padre— enterrado en dos
|
|
|
expresiones sueltas. Ahora:
|
|
|
|
|
|
```ts
|
|
|
// wrapper — idéntico en los 36
|
|
|
dir: activeDir(() => dir, soma)
|
|
|
// el escalón extra se declara donde se ve
|
|
|
dir: activeDir(() => dir ?? parentMenu?.opts.dir.current, soma)
|
|
|
|
|
|
// provider — dos valores, nunca uno
|
|
|
readonly resolvedDir = $derived.by(() => this.opts.dir.current ?? 'ltr'); // la matemática
|
|
|
dir: this.opts.dir.current // el atributo, CRUDO
|
|
|
```
|
|
|
|
|
|
`src/uix/soma/direction.ts` documenta el porqué. **La regla que hay que defender:
|
|
|
el atributo NUNCA tiene valor por defecto.**
|
|
|
|
|
|
### Guards que nacieron en rojo
|
|
|
|
|
|
- `src/arts/prefs/test/active-prefs-reactivity.svelte.test.ts` — intent · derivación
|
|
|
desde el idioma · **granularidad por clave** (el negativo: un commit de `accent` NO
|
|
|
debe despertar al lector de `direction`; con una celda global se pondría rojo).
|
|
|
- `src/uix/active-uix/test/prefs-view.svelte.test.ts` — `getDir()` distingue
|
|
|
«nadie afirmó» de «ltr».
|
|
|
|
|
|
⚠️ **Los dos DEBEN seguir siendo `*.svelte.test.ts`.** El proyecto `server` de vitest
|
|
|
compila el `$state` fuera (transform de servidor de Svelte), así que un `.test.ts`
|
|
|
ahí pasaría hiciera lo que hiciera el código.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 2. Lo que queda — por orden de valor
|
|
|
|
|
|
### 2.1 · El guard — CERRADA (ver §6)
|
|
|
|
|
|
### 2.2 · Geometría lógica en los cinco que calculan píxeles desde JS
|
|
|
|
|
|
| componente | sitios |
|
|
|
| --- | --- |
|
|
|
| `slider` | 6 |
|
|
|
| `carousel` | 2 (signo del `translate3d` + signo del swipe) |
|
|
|
| `number-field` | 2 (signo del arrastre) |
|
|
|
| `css-field` | 2 (signo del arrastre) |
|
|
|
| `dropdown-menu` | colocación flotante |
|
|
|
|
|
|
**Funciona hoy** — no es un bug abierto. Pero es lo que convierte un `dir`
|
|
|
equivocado en catástrofe en vez de en detalle cosmético, y la fila RTL del contrato
|
|
|
manda lógicas para el flujo (`left`/`right` físicos sólo como API de *placement*
|
|
|
flotante, excepción EID-3). Migrar el PINTADO a `inset-inline-*` deja el JS con
|
|
|
`dir` sólo para el signo del gesto y las flechas.
|
|
|
|
|
|
**De uno en uno y con verificación en navegador.** Toca comportamiento ya probado.
|
|
|
|
|
|
⚠️ **Matiz que salió al cerrar §2.1**: el `sliding-indicator` era el mismo defecto y
|
|
|
se resolvió al revés — soma mide `itemRect.left - rootRect.left`, una coordenada
|
|
|
FÍSICA, así que el arreglo fue hacer FÍSICA el ancla (`left: 0`), no lógico el
|
|
|
pintado. Antes de migrar cada sitio, mira de qué lado está la medida: si el JS
|
|
|
mide en físico, lo coherente es el ancla física. Lo mismo vale para el arco polar
|
|
|
del `menu-dial` y los handles de brújula del `cropper`.
|
|
|
|
|
|
### 2.3 · El `secondary-range` vertical en RTL no lo ha visto nadie
|
|
|
|
|
|
Arreglado por simetría con el `range` (misma regla, mismo cambio), y el `range` sí
|
|
|
se midió. Pero **la combinación vertical + RTL + `secondaryValue` no es observable**:
|
|
|
la única demo que expone `secondaryValue` es la del **waveform**, y es horizontal.
|
|
|
O se cablea `secondaryValue` como control en la demo del slider, o se declara como
|
|
|
verificado-por-construcción y no por vista.
|
|
|
|
|
|
✅ **La mitad «no observable» ya no lo es**: el pin del `<main>` (§6.4) era lo que
|
|
|
impedía ver RTL en CUALQUIER demo. Ahora el toggle del topbar y el árabe llegan al
|
|
|
componente. Queda sólo cablear `secondaryValue` en la demo del slider.
|
|
|
|
|
|
### 2.4 · `parts[2]` en la demo del slider
|
|
|
|
|
|
`web/routes/uix/components/slider/+page.svelte:409` indexa `SecondaryRange` en vez
|
|
|
de `Thumb` — la tabla de teclado sale vacía y es el **único** error de tipos del
|
|
|
fichero. Preexistente (venía de cuando `SecondaryRange` se insertó en el índice 2);
|
|
|
señalado y no tocado por ser ajeno al eje.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 3. Encontrado por el camino, NO de este hilo
|
|
|
|
|
|
- **`html lang` no sigue al idioma.** El `dir` sí se proyecta; el `lang` se queda en
|
|
|
`en` con árabe seleccionado. Afecta a selección de fuentes, corte de palabras y
|
|
|
lectores de pantalla.
|
|
|
- **`perm:check` sin ejecutar.** El topbar ya lleva `data-perm-step="90"`, lo que
|
|
|
hace que **toda** ruta bajo el layout tenga paso (antes saltaba 158). La pasada se
|
|
|
vuelve mucho más larga, y como el runner usa **un solo contexto de navegador**, la
|
|
|
dirección persiste en `localStorage` y las rutas alternan ltr/rtl.
|
|
|
- **91 ficheros de test falsifican `Soma.require()`** con `vi.spyOn(Soma, 'require')
|
|
|
.mockReturnValue({ prefs: { getDir: () => dir } } as unknown as Soma)`. Incumple
|
|
|
una Regla Crítica de CLAUDE.md y significa que esa parte de la batería **no
|
|
|
ejercita el código real**. Es otro proyecto entero, pero es el hallazgo más grave
|
|
|
de la lista.
|
|
|
- **El `?` desplazado dentro de un componente RTL no es arreglable desde el
|
|
|
framework.** Es el algoritmo bidi de Unicode sobre texto inglés en un párrafo RTL:
|
|
|
el `?` es neutro y al final de la tirada adopta la dirección del párrafo. El
|
|
|
componente hace bien en estar en `rtl`. Se cierra **traduciendo el contenido de
|
|
|
las demos** o marcándolo — que es exactamente la parte que las cinco librerías de
|
|
|
referencia dejan al consumidor.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 4. Investigación de referencias — hecha, no la repitas
|
|
|
|
|
|
25 agentes, 13 hallazgos confirmados, **5 tumbados** por refutación con la fuente
|
|
|
delante.
|
|
|
|
|
|
| | ¿estampa `dir`? | cómo resuelve |
|
|
|
| --- | --- | --- |
|
|
|
| **MUI** | nunca | tema + contexto `useRtl`; voltea CSS físico con stylis |
|
|
|
| **react-aria** | nunca (salvo portales) | del *locale* por contexto; te manda escribir `<div lang dir>` en tu raíz |
|
|
|
| **bits-ui** | no verificado que escriba | prop con default duro `'ltr'` + `getComputedStyle().direction` en roving-focus |
|
|
|
| **Radix** | **siempre** | prop → contexto → `'ltr'`; sin salida → su `discussions/1405` |
|
|
|
| **Zag/Ark** | **46/51 máquinas** | prop → contexto, **valor `prop("dir")`: ausente si nadie lo pidió** |
|
|
|
|
|
|
**Ninguna** de las cinco emite `dir="auto"`, `<bdi>` ni `unicode-bidi`.
|
|
|
|
|
|
Dos datos que costaron trabajo y conviene no volver a averiguar:
|
|
|
|
|
|
- **`unicode-bidi: isolate` NO arregla el `?`.** Aísla la tirada respecto a sus
|
|
|
hermanas, pero no cambia su dirección base, y el `?` está *dentro* de la tirada.
|
|
|
- **`dir="ltr"` sí, y de paso aísla.** Verificado en la hoja de estilos de agente del
|
|
|
WHATWG: `… bdi, output, [dir=ltr i], [dir=rtl i], [dir=auto i] { unicode-bidi:
|
|
|
isolate; }`. Un atributo, dos efectos.
|
|
|
- **`dir="auto"` es la herramienta equivocada** cuando SÍ se sabe la dirección: mira
|
|
|
sólo el primer carácter fuerte, la spec llama a la heurística *"very crude"* y el
|
|
|
W3C documenta que falla justo en esta clase de texto.
|
|
|
|
|
|
Excepción correcta que se queda: `chat-message.css:156` usa `unicode-bidi: plaintext`
|
|
|
porque el texto de un mensaje es de dirección **incognoscible al escribir**. Ése es
|
|
|
el discriminador de la doctrina: **auto-detectar sólo donde no se puede saber;
|
|
|
donde se sabe, declararlo en el sitio que lo sabe.**
|
|
|
|
|
|
---
|
|
|
|
|
|
## 5. Trampas de método — me costaron horas, léelas
|
|
|
|
|
|
- **Ensancha el TIPO primero.** Al pasar `dir: Direction` → `Direction | undefined`
|
|
|
en las opts, cada sitio de LÓGICA que asumía un valor concreto se volvió error de
|
|
|
compilación y el compilador enumeró el trabajo. **Pero no cubre la otra mitad**:
|
|
|
`dir: undefined` es un valor de atributo válido, así que los estampados compilan
|
|
|
pasara lo que pasara. Esa mitad se verifica **contando** (`grep -c` de crudo vs
|
|
|
resuelto por fichero), no confiando.
|
|
|
- **Los barridos con regex mintieron cuatro veces de cuatro**: `resolvedDir`
|
|
|
autorreferencial en 17 ficheros (el regex reescribió el cuerpo de la propia
|
|
|
declaración), duplicados en 3, campo insertado en 3 clases sin `dir`, y
|
|
|
`activeDir(valor)` en vez de `activeDir(() => valor)` en 10 wrappers.
|
|
|
- **Anclaje de clase que falla**: `^export class \w+Provider \{$` exige que la línea
|
|
|
TERMINE en `{`, así que se salta `class X implements Y {` y aterriza en la clase
|
|
|
siguiente. Usa `^export class \w+Provider\b[^\n]*\{$`.
|
|
|
- **Discriminante que sí generaliza** cuando un símbolo tiene dos roles: la forma
|
|
|
sintáctica, no el identificador. Aquí `dir: …` (clave de objeto) = estampado;
|
|
|
cualquier otro `.opts.dir.current` = lógica. Sobrevivió a las tres formas distintas
|
|
|
del catálogo.
|
|
|
- **NO lances `prettier --write` sobre un directorio entero.** Sobre las demos generó
|
|
|
**165 ficheros y +31.000 líneas** de ruido y **rompió dos atributos** normalizando
|
|
|
`data-perm-mode='type="X"'` a comillas dobles. Revertido y reaplicado sólo el
|
|
|
cambio: 49 ficheros, +357/−161. Formatea únicamente los ficheros que tocas, y mira
|
|
|
el tamaño de la diff después.
|
|
|
- **El panel de navegador embebido no sirve** para nada visual (no compone frames).
|
|
|
Usar el Chrome real (`mcp__claude-in-chrome__*`).
|
|
|
- **`smoke` es inestable.** Con mis cambios falló en `/demos/motion`, `/temas`,
|
|
|
`/uix/components/card`; con los cambios stasheados falló en **dos rutas
|
|
|
distintas** (`/demos/cristal`, `/demos/heroscrolling`). Conjuntos disjuntos ⇒ no
|
|
|
son regresiones. Compara siempre contra una pasada de referencia antes de culpar
|
|
|
a tu diff.
|
|
|
- ⚠️ **Rama compartida.** El usuario commiteó `42d58c394` mientras yo trabajaba.
|
|
|
Verifica `HEAD` antes de dar por sabido el estado.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 6. Sesión 2026-08-02 — §2.1 cerrada, y lo que arrastró
|
|
|
|
|
|
Sin commitear. `check` = **74 = línea base exacta** en las tres pasadas.
|
|
|
|
|
|
### 6.1 · El guard RTL-1
|
|
|
|
|
|
`src/uix/eidos/rtl-lint.ts` (lógica) + `rtl-lint.test.ts` (**14/14**) +
|
|
|
`scripts/rtl-check.ts` + `npm run rtl:check`. La fila RTL de
|
|
|
`docs/guides/component-guide.md` deja de decir «rule (LIVE)».
|
|
|
|
|
|
El test ancla el caso histórico: `slider.css` ANTES de `013ceac57` sale rojo, el
|
|
|
arreglo que se commiteó sale verde, y el eje de bloque (`inset-block-start` +
|
|
|
`translateY`) no se marca.
|
|
|
|
|
|
**Dos desviaciones del enunciado de §2.1, deliberadas:**
|
|
|
- **Amplía** a la propiedad independiente `translate:`, que se usa MÁS que
|
|
|
`transform: translate*` (87 apariciones frente a 36) y tiene el mismo defecto.
|
|
|
Ocho de los 24 hallazgos eran de esa forma.
|
|
|
- **Recorta** `padding-inline*`: no mueve la caja contra su ancla, así que no
|
|
|
puede pelear con un translate. Sólo añadía ruido.
|
|
|
|
|
|
Vía de escape `/* rtl-physical: <razón> */` en la línea del translate o la de
|
|
|
encima. Uso legítimo: el signo se invierte en otra regla bajo `:dir(rtl)` y el
|
|
|
guard no puede ver esa compensación.
|
|
|
|
|
|
### 6.2 · La cosecha: 24 → 1
|
|
|
|
|
|
El único que queda es `palabras-chrome.css:446`, **auditado y no tocado** por la
|
|
|
regla de no escribir en palabras. Los 23 restantes cayeron en tres familias:
|
|
|
|
|
|
| Familia | Cuántos | Arreglo |
|
|
|
| --- | --- | --- |
|
|
|
| **Centrado** | 10 | `margin-inline-start: calc(<size> / -2)` si el tamaño se conoce (idioma del slider); `inset-inline: 0` + `margin-inline: auto` si lo fija el contenido |
|
|
|
| **Direccional** | 6 | El signo se invierte con `:dir(rtl)`; el par queda marcado `rtl-physical` |
|
|
|
| **Físico** | 7 | `left`/`right` — geometría de brújula (`cropper`) y polar (`menu-dial` arco) |
|
|
|
|
|
|
⚠️ **NO añadas `inline-size: fit-content` a ciegas** en el centrado por márgenes
|
|
|
automáticos: al trigger del `carousel` le comió el ancho de 36px a 17px porque su
|
|
|
tamaño venía de fuera. Sólo hace falta cuando el elemento no tiene ancho
|
|
|
determinable, y hay que medirlo después.
|
|
|
|
|
|
**Verificado en Chrome**: switch (roto→arreglado, medido y visto), carousel,
|
|
|
avatar, cropper, menu-dial, chat-log.
|
|
|
|
|
|
**Los tres que faltaban, verificados después** con un probe de Playwright y
|
|
|
`Emulation.setEmulatedMedia` (`pointer: coarse`), porque el navegador embebido no
|
|
|
llega a esa media query:
|
|
|
|
|
|
- `archetypes` (checkbox y switch): `margin-inline-start: -22px` con slop de
|
|
|
`44px` — la mitad exacta — y `translate: 0 -50%` sin componente X. Barrido de
|
|
|
hit-test: alcanza 22/21 y 22/22 px, **simétrico en LTR y en RTL**.
|
|
|
- `proof-of-human`: `-23px` sobre `46px`, `translateY(-22)` sin X, hit-test 23/23.
|
|
|
- `sliding-indicator`: el CSS es correcto — con medida fresca cae exacto en RTL.
|
|
|
Pero destapó §6.6.
|
|
|
|
|
|
### 6.3 · `:dir()` es la doctrina; `[dir='rtl']` está PROHIBIDO
|
|
|
|
|
|
Los 12 selectores `[dir='rtl']` que había en eidos estaban **todos rotos**, en los
|
|
|
dos sentidos opuestos del mismo error. Medido en Chrome:
|
|
|
|
|
|
| Forma | Cuántos | Fallo | Medición |
|
|
|
| --- | --- | --- | --- |
|
|
|
| `[dir='rtl'] <desc>` | 5 | **Se aplica de más** — capta un ancestro RTL e ignora un `dir` más cercano que redeclare | `sidebar`: `direction: ltr` y aun así matchea |
|
|
|
| `[data-x][dir='rtl']` | 7 | **No se aplica nunca** — el atributo ya no se estampa por defecto (`013ceac57`) | `tree-view`: `direction: rtl` de verdad y NO matchea |
|
|
|
|
|
|
Migrados los 12 a `:dir()`, que acierta los tres casos (atributo propio, heredado,
|
|
|
y redeclarado por un ancestro intermedio). Verificado en `sidebar` y `tree-view`.
|
|
|
|
|
|
**No lo reintroduzcas.** Con el contrato «el atributo nunca tiene defecto»,
|
|
|
`[dir='rtl']` no puede expresar «la dirección efectiva de este elemento es RTL».
|
|
|
Baseline desde 2023 (Chrome 120, Safari 16.4, Firefox 49).
|
|
|
|
|
|
### 6.4 · El pin del `<main>` anulaba la otra mitad de `5469e05df`
|
|
|
|
|
|
`5469e05df` hizo dos cosas que **se cancelan**: puso las demos en `auto` (sin prop
|
|
|
`dir`, resolviendo por prefs → **no estampan atributo → heredan del DOM**) y a la
|
|
|
vez clavó `<main data-uix-canvas dir="ltr">` sobre ellas. Resultado: el toggle del
|
|
|
topbar y el árabe llegaban a `<html>` y **morían en el `<main>`**. Las 51 demos
|
|
|
estaban congeladas en LTR.
|
|
|
|
|
|
El comentario que lo justificaba afirmaba que «los componentes siguen las prefs,
|
|
|
así que este contenedor no les afecta». Es cierto para la LÓGICA (teclado,
|
|
|
cálculos JS con `resolvedDir`) y **falso para el CSS**, que depende del `direction`
|
|
|
computado, y éste viene del DOM.
|
|
|
|
|
|
Medido en el sidebar: `html dir="rtl"` con el panel todavía a la izquierda, y
|
|
|
quitar ESE atributo y nada más lo movía a la derecha.
|
|
|
|
|
|
Quitado el `dir` del `<main>` (el `lang="en"` se queda: es correcto y no toca la
|
|
|
dirección). **Coste aceptado por el usuario**: la prosa inglesa vuelve a mostrar la
|
|
|
puntuación desplazada en RTL. Es cosmético y sólo en un modo de inspección; demos
|
|
|
que no pueden demostrar, no. Si se quiere recuperar, **el pin va alrededor de la
|
|
|
PROSA**, nunca alrededor del canvas que también contiene los ejemplos vivos.
|
|
|
|
|
|
### 6.6 · El indicador deslizante no se re-medía al voltear
|
|
|
|
|
|
Verificar §6.2 destapó la otra mitad del `sliding-indicator`. `MeasuredIndicator`
|
|
|
(`src/uix/soma/layers/measured-indicator.svelte.ts`) re-mide cuando cambian `root`
|
|
|
o `active`, cuando alguno redimensiona, en `resize` de ventana y en
|
|
|
`visibilitychange`. **Voltear la dirección no redimensiona nada** —los ítems sólo
|
|
|
cambian de POSICIÓN— y `active` conserva su identidad, así que `--indicator-x`
|
|
|
se quedaba con la coordenada de la disposición anterior.
|
|
|
|
|
|
Arreglado con una opción `dir` (un getter) que el `$effect` lee **antes del return
|
|
|
temprano**, para que la dependencia se registre en todas las pasadas. El consumidor
|
|
|
(`radio-group`) le pasa `resolvedDir`. Getter y no lectura del DOM: la dirección
|
|
|
viene de la cadena prop/prefs, y el atributo es proyección, no fuente.
|
|
|
|
|
|
⚠️ **TRAMPA DE MÉTODO, me costó una pasada entera**: el primer probe volteaba con
|
|
|
`el.setAttribute('dir', 'rtl')` y daba «no arreglado» — pero eso **no cambia
|
|
|
`resolvedDir`**, que sale de la prop/prefs, así que la dependencia no podía
|
|
|
dispararse. Para probar cualquier cosa del eje hay que mover la dirección por el
|
|
|
CAMINO REAL (el toggle del topbar o el control de la demo), nunca tocando el DOM.
|
|
|
|
|
|
A/B con `git stash` sobre los dos ficheros, por la cadena real: sin el arreglo,
|
|
|
`translate` se queda en `177.156px` y el indicador cae 173px fuera del
|
|
|
seleccionado; con él, pasa a `4px` y `dx = 0`. `check` 74 = línea base, 73/73 en
|
|
|
`soma/layers` + `radio-group`.
|
|
|
|
|
|
**Único consumidor hoy**: `radio-group`. Si aparece otro, tiene que pasar `dir`.
|
|
|
|
|
|
### 6.7 · Los otros dos indicadores — uno bien, otro roto
|
|
|
|
|
|
Buscados los que comparten mecanismo con §6.6. **Medidos, no supuestos:**
|
|
|
|
|
|
- **`navigation-menu`: correcto, no se toca.** Su indicador sólo existe mientras
|
|
|
un menú está ABIERTO, así que abrirlo ES el disparador de la medida y nunca
|
|
|
queda rancia; voltear cierra el menú, con lo que el escenario ni se alcanza; y
|
|
|
ancla y medida son ambas físicas. Medido: `dx = 0` en LTR, en RTL y al volver.
|
|
|
- **`tabs`: roto por partida doble, y MIGRADO a `MeasuredIndicator`.** Medía en
|
|
|
el wrapper de eidos con su propio rAF y observadores. (a) No re-medía al
|
|
|
voltear —`dx = 518`— y (b) **ni con medida fresca acertaba en RTL** —`dx = 400`—
|
|
|
porque `[data-tabs-indicator]` anclaba con `inset-inline-start: 0` LÓGICO
|
|
|
mientras el JS le aplicaba un `translate3d(x)` FÍSICO. Ahora soma mide (con la
|
|
|
dependencia `dir`) y el recipe posiciona desde `--indicator-*` con `left: 0`.
|
|
|
Los cuatro escenarios a `dx = 0`, verificado en las tres variantes con
|
|
|
indicador y en RTL a ojo.
|
|
|
|
|
|
⚠️ **PUNTO CIEGO DE RTL-1, y es importante**: el defecto (b) llevaba ahí desde
|
|
|
siempre y el guard **no podía verlo**, porque el ancla estaba en el CSS y el
|
|
|
`transform` lo escribía el JS INLINE. RTL-1 cruza declaraciones dentro de una
|
|
|
regla CSS. Así que «24 → 1» quiere decir 24 en el CSS estático, **no** que el
|
|
|
catálogo esté limpio: lo que pinta desde JS (§2.2) sigue necesitando el ojo.
|
|
|
|
|
|
Efecto lateral bueno: la variante `line` vuelve a mandar en su grosor (2px). El
|
|
|
`height` inline del wrapper viejo lo pisaba, cosa que un comentario del propio
|
|
|
`tabs.css` daba por hecha que no pasaba.
|
|
|
|
|
|
Un guardián se activó y **es correcto que lo hiciera**:
|
|
|
`component-visual-attrs.test.ts` exigía `data-ready` en el wrapper de eidos. El
|
|
|
atributo cambió de dueño a soma, así que la entrada se retira — igual que el
|
|
|
indicador del `radio-group`, que tampoco lo lista.
|
|
|
|
|
|
### 6.8 · §2.2 verificada — los cinco están sanos, pero la LISTA no lo estaba
|
|
|
|
|
|
Pasados por navegador, con la dirección movida por el camino real (toggle del
|
|
|
topbar → prefs), nunca por `setAttribute`:
|
|
|
|
|
|
| | Resultado |
|
|
|
| --- | --- |
|
|
|
| **slider** | ✓ Click al 25% del ancho FÍSICO da 75 en RTL; arrastrar +120px a la derecha sube en LTR y baja en RTL; `ArrowRight` igual. Los seis sitios responden |
|
|
|
| **number-field** | ✓ El mismo arrastre físico sube en LTR y baja en RTL |
|
|
|
| **css-field** | ✓ Idéntico (`16px → 40px` en LTR, `40px → 16px` en RTL) |
|
|
|
| **dropdown-menu** | ✓ El panel raíz se alinea al inline-start (izquierda en LTR, derecha en RTL) y el submenú abre a la derecha en LTR, a la izquierda en RTL |
|
|
|
| **carousel** | Track ✓ (Next mueve −622px en LTR y +622px en RTL). **Swipe SIN MEDIR** |
|
|
|
|
|
|
⚠️ **El swipe del carousel se me resistió a cinco intentos** de simulación de
|
|
|
puntero: la capa de gesto tiene umbral de distancia y de velocidad, y con
|
|
|
`page.mouse` no conseguí dispararlo de forma reproducible ni en LTR. El signo
|
|
|
está bien POR LECTURA (`isRtlHorizontal ? -offset : offset`, con los tres casos
|
|
|
comentados). Son diez segundos con un dedo o un ratón de verdad: probar que
|
|
|
arrastrar hacia la izquierda avanza en LTR y hacia la derecha avanza en RTL.
|
|
|
|
|
|
### 6.9 · Barrido del punto ciego — quién más pinta desde JS
|
|
|
|
|
|
Un primer cruce «matemática horizontal» × «menciona la dirección» dio trece
|
|
|
sospechosos. **Revisado uno a uno, esa lista estaba inflada Y le faltaban
|
|
|
piezas.** El cruce por sí solo no vale: `.left` capta cosas que no son
|
|
|
direccionales, y un componente puede manejar la dirección sin nombrar `rtl`.
|
|
|
|
|
|
**Un motor cubre nueve de golpe.** `src/uix/soma/layers/floating` lo consumen
|
|
|
`combobox`, `context-menu`, `dropdown-menu`, `link-preview`, `menubar`,
|
|
|
`popover`, `select`, `sidebar` y `tooltip`. Verificado el motor a través de
|
|
|
`popover` (align=end: derecha en LTR, izquierda en RTL ✓) y `menubar`
|
|
|
(align=start: izquierda en LTR, derecha en RTL ✓), más `dropdown-menu` en §6.8.
|
|
|
**Los nueve quedan cubiertos.** ⚠️ `float-panel` NO usa este motor — tiene
|
|
|
colocación, arrastre, resize y teclado propios, y cero dirección.
|
|
|
|
|
|
**Falsos positivos, descartados por lectura del uso real:** `container` (es una
|
|
|
prop de márgenes), `text-focus` / `text-scramble` / `path-trace` (miden dónde
|
|
|
está un elemento o el puntero para un efecto: físico correcto), `drag-drop`
|
|
|
(sueltas donde ves), `cropper` (paneas una imagen que no se voltea, ya decidido
|
|
|
en §6.2), `gradient-builder` (arrastras la parada donde la ves).
|
|
|
|
|
|
**Falsos positivos de MI MÉTODO, que conviene no repetir:** medir «a qué borde
|
|
|
se pega el panel» falla cuando el panel tiene el ancho exacto del disparador —
|
|
|
el `select` daba `Δleft=0 Δright=0` y mi desempate lo marcaba en rojo sin que
|
|
|
pasara nada.
|
|
|
|
|
|
**Lo que el primer cruce NO vio** (escriben geometría inline desde JS sin usar
|
|
|
`clientX`): `drawer`, `color-picker`, `rotate-align`, `spinner`,
|
|
|
`text-circular`. De ellos:
|
|
|
|
|
|
- **`drawer` está bien y es el ejemplo a imitar**: traduce `start`/`end` a
|
|
|
`left`/`right` UNA vez en `resolveDirection()` y a partir de ahí toda su
|
|
|
matemática es física y coherente. Verificado que con un valor físico
|
|
|
(`right`) NO voltea, que es lo correcto. ⚠️ **Pero su demo sólo expone
|
|
|
`top`/`right`/`bottom`/`left`**, así que la mitad lógica —la única que
|
|
|
ejercita RTL— **no es observable**. Mismo agujero que §2.3.
|
|
|
- `color-picker`: el thumb del área 2D se queda en el mismo sitio al voltear.
|
|
|
Probablemente correcto por diseño (una rueda de color es física, como el
|
|
|
cropper), pero es una DECISIÓN, no un hecho verificado.
|
|
|
- `rotate-align`, `spinner`, `text-circular`: efectos rotatorios, sin eje.
|
|
|
|
|
|
### 6.10 · float-panel — era código redundante, y por eso estaba mal
|
|
|
|
|
|
`float-panel` no usa el motor compartido porque el suyo es otro problema:
|
|
|
`useFloating` mantiene el panel PEGADO a su ancla, y un float-panel se arrastra
|
|
|
y redimensiona libremente en cuanto se abre. Hasta ahí, correcto.
|
|
|
|
|
|
**Pero su semilla anclada sí era redundante.** `computeInitialPosition()`
|
|
|
re-derivaba `side` + `align` contra el rect del ancla a mano, y esa copia
|
|
|
resolvía `align: 'start'` a `r.left` SIEMPRE — una alineación LÓGICA clavada a
|
|
|
un borde FÍSICO, que nunca voltea. `$ethereal` ya exporta la matemática buena:
|
|
|
|
|
|
```ts
|
|
|
computeCoordsFromPlacement(rects, placement, rtl) // pura, síncrona, sin ciclo de vida
|
|
|
```
|
|
|
|
|
|
Es la misma que usa el motor compartido, y toma la dirección como argumento.
|
|
|
Migrado: se comparte la SEMILLA, no `useFloating`.
|
|
|
|
|
|
Verificado con `anchored-seed.test.ts` (4 casos), porque **la demo no ancla
|
|
|
ningún panel** y el defecto no era observable ahí:
|
|
|
- LTR: idéntico al algoritmo anterior en las 12 combinaciones side × align —
|
|
|
cero regresión.
|
|
|
- RTL con side vertical: `start` y `end` se espejan (lo que el viejo no podía).
|
|
|
- RTL con side horizontal: el align NO cambia — es el eje de bloque.
|
|
|
- `center` sigue centrado en ambas.
|
|
|
|
|
|
La demo, medida antes y después, no se mueve (`left` 329 en LTR, 81 en RTL).
|
|
|
|
|
|
**No tocado, y es una decisión pendiente**: `Home`/`End` en el eje X llevan el
|
|
|
panel a `bounds.left` / `bounds.right`. Para un panel que se arrastra libremente
|
|
|
es defendible que sean extremos físicos y no principio/final de lectura. No es
|
|
|
un defecto claro; hace falta criterio.
|
|
|
|
|
|
**`toast`: descartado, es correcto.** Todo su vocabulario es FÍSICO y coherente
|
|
|
consigo mismo — `position` es `top-left`…`bottom-right` y `swipeDirection` es
|
|
|
`left`/`right`/`up`/`down` (por defecto `'right'`). No hay un solo `start`/`end`
|
|
|
en su superficie, así que no promete seguir la dirección y no la incumple; misma
|
|
|
excepción legítima que el `side` físico del drawer. Radix hace igual. Aquí basta
|
|
|
la lectura porque lo que se comprueba es una AUSENCIA (no existe vocabulario
|
|
|
lógico), que es propiedad del código; no es el caso de `tabs`, donde había una
|
|
|
promesa lógica que el runtime incumplía.
|
|
|
|
|
|
### 6.11 · Los virtualizadores — el contenido DESAPARECÍA en RTL
|
|
|
|
|
|
Reportado por el usuario y reproducido: en `virtual-grid`, voltear la dirección
|
|
|
dejaba la rejilla en blanco. Medido en Chrome:
|
|
|
|
|
|
```
|
|
|
LTR : 108 celdas, 70 visibles, primera en x=381 (borde del viewport)
|
|
|
RTL : 108 celdas, 0 visibles, primera en x=-5101 (fuera de la pantalla)
|
|
|
```
|
|
|
|
|
|
No era `scrollLeft` —está en 0 en ambos—, era el anclaje de la celda:
|
|
|
|
|
|
```ts
|
|
|
position: absolute; top: 0; left: 0;
|
|
|
transform: translate3d(${columnStart}px, …)
|
|
|
```
|
|
|
|
|
|
El sizer interno es `inline-size: 6000px`, o sea **lógico**, así que en RTL
|
|
|
desborda hacia la izquierda y ocupa `[-5101, 899]`. Su borde físico izquierdo es
|
|
|
donde el contenido TERMINA, y `left: 0` mandaba todas las celdas allí.
|
|
|
|
|
|
Arreglado con ancla lógica (`inset-inline-start`) y el signo en el offset, para
|
|
|
que el recorrido siga en `translate3d` — un virtualizador recoloca miles de
|
|
|
celdas y conviene que siga en el compositor. Tras el arreglo: **70 visibles en
|
|
|
RTL**, primera en x=779 = borde derecho del viewport − ancho de celda.
|
|
|
|
|
|
**`virtual-list` tenía el mismo patrón** (`left: 0` + `translateX`) y por tanto
|
|
|
el mismo defecto en modo horizontal. Mismo arreglo; el modo vertical no se ve
|
|
|
afectado (`inset-inline-start: 0` con `width: 100%` es lo que `left: 0` era).
|
|
|
Verificado: 12 items visibles en RTL, primero en x=755.
|
|
|
|
|
|
**El otro síntoma reportado —«el cambio de idioma no le afecta»— NO es un
|
|
|
defecto.** Con el toggle en `auto` el árabe voltea la lista correctamente
|
|
|
(medido: `htmlDir` y `direction` del componente pasan a `rtl`). Si el toggle
|
|
|
está en `ltr`/`rtl` EXPLÍCITO, el idioma no manda: el intent gana sobre la
|
|
|
derivación, que es lo ratificado en `013ceac57`. Despista en la práctica, pero
|
|
|
es el contrato.
|
|
|
|
|
|
### 6.12 · Los tres que «tenían código de dirección» — dos estaban rotos
|
|
|
|
|
|
**`rating-group`: roto, y por DOS motivos encadenados.**
|
|
|
|
|
|
1. `calcFromPointer` medía `(clientX - rect.left) / rect.width`, una fracción
|
|
|
desde el borde físico izquierdo. En RTL los items van de derecha a izquierda,
|
|
|
así que la mitad que EMPIEZA una estrella es la derecha: pinchar la primera
|
|
|
mitad visual daba la estrella entera. Volteado, igual que el slider.
|
|
|
2. Y aun así seguía sin funcionar, porque **su wrapper se quedó fuera de la
|
|
|
normalización de `013ceac57`**: tenía `dir = 'ltr'` como default duro y
|
|
|
`readableActive(() => dir)` en vez de `activeDir(() => dir, soma)`. O sea
|
|
|
nunca consultaba prefs y `resolvedDir` valía `'ltr'` siempre.
|
|
|
|
|
|
⚠️ **`natural-time-picker` tenía el mismo agujero del wrapper** (`dir ?? 'ltr'`).
|
|
|
Son los DOS únicos que quedaron fuera; los otros 36 sí usan `activeDir`.
|
|
|
Medido tras arreglar ambos: en RTL la mitad derecha da 2.5 y la izquierda la
|
|
|
entera — espejo correcto.
|
|
|
|
|
|
**LECCIÓN DE MÉTODO**: al ver `resolvedDir = opts.dir.current ?? 'ltr'` en ~30
|
|
|
providers pensé que a todos les faltaba el eslabón de prefs. **Falso**: la cadena
|
|
|
vive en el WRAPPER (`activeDir(getter, soma)`), y el provider recibe la prop ya
|
|
|
resuelta. Antes de "arreglar" 30 componentes, mira el wrapper.
|
|
|
|
|
|
**`splitter`: el arrastre estaba roto, el teclado no.** `resizePanels` toma un
|
|
|
delta LÓGICO —positivo agranda el panel de ANTES del handle— y recibía el delta
|
|
|
físico del puntero sin voltear. Medido: en RTL arrastrar a la derecha agrandaba
|
|
|
el panel que debía encoger, y el arrastre **contradecía a las flechas**, que sí
|
|
|
pasan por `getDirectionalKeys`. Tras el arreglo: LTR arrastre +118 / `ArrowRight`
|
|
|
+6; RTL arrastre −118 / `ArrowRight` −6 — espejo exacto y ambos de acuerdo.
|
|
|
|
|
|
**`scroll-area`: SIN VERIFICAR, y con dos señales.** Su `resolvedDir` aparece
|
|
|
UNA sola vez —la declaración—, o sea es código muerto. Y usa `scrollLeft` en
|
|
|
cuatro sitios (`isAtLeft = scrollLeft <= 0`, el ratio del thumb, el salto por
|
|
|
click y el arrastre) sin tratar que en RTL los navegadores lo devuelven
|
|
|
**negativo**: `isAtLeft` sería siempre cierto y el ratio saldría negativo. No
|
|
|
está medido porque el scroll-area de la demo no tiene eje horizontal
|
|
|
(`maxScroll: 0`) — hace falta un ejemplo que sí lo tenga.
|
|
|
|
|
|
### 6.13 · El swipe del carousel SÍ estaba mal — el rail huía del dedo
|
|
|
|
|
|
Lo que §6.8 no consiguió disparar por simulación, el usuario lo vio a mano. El
|
|
|
defecto no estaba en la decisión de `finishDrag` (que sí voltea con
|
|
|
`isRtlHorizontal`) sino en el pintado durante el arrastre:
|
|
|
|
|
|
```ts
|
|
|
translate = base + dragOffset // base = índice, dragOffset = dedo
|
|
|
itemGroupTransform = translate * flip // el flip caía sobre LOS DOS
|
|
|
```
|
|
|
|
|
|
El recorrido por índice es LÓGICO y debe voltear; `dragOffset` es el
|
|
|
desplazamiento FÍSICO del puntero y no. Al voltear ambos, en RTL el rail se movía
|
|
|
en sentido contrario al dedo. Separados: sólo `baseTranslate` lleva el signo.
|
|
|
|
|
|
A/B con `git stash`, midiendo el transform A MITAD del arrastre (que no exige
|
|
|
superar el umbral de gesto, y por eso ahora sí es medible): sin el arreglo, RTL
|
|
|
da Δ−100 con el dedo a +100 —huye—; con él, Δ+100 —lo sigue—. LTR intacto en
|
|
|
ambos casos.
|
|
|
|
|
|
**MÉTODO**: para un gesto con umbral de distancia/velocidad, no intentes
|
|
|
completar el swipe; mide el estado INTERMEDIO con el puntero aún abajo.
|
|
|
|
|
|
### 6.14 · Descartados con medida — no tocar
|
|
|
|
|
|
- **`scroll-area` funciona.** Confirmado por el usuario y medido: con el toggle
|
|
|
en `auto`, ES→ltr y AR→rtl, y el elemento pasa a `direction: rtl`. Su
|
|
|
`resolvedDir` sigue siendo código muerto (una sola aparición) y el uso de
|
|
|
`scrollLeft` sigue sin verificarse en un eje horizontal real — pero el
|
|
|
componente responde a la dirección.
|
|
|
- **`chart` no necesita arreglo, y las referencias lo respaldan.** Medido:
|
|
|
`chartDir`, `legendDir` y el texto de los ejes pasan de `ltr` a `rtl`; el eje
|
|
|
numérico no se voltea. Es exactamente el consenso: Chart.js implementó RTL
|
|
|
SÓLO para leyendas y tooltips y mantiene abierto el issue de ejes/etiquetas
|
|
|
(chartjs/Chart.js#11937, PR #6460); el patrón documentado es eje numérico LTR
|
|
|
con textos RTL. amCharts es de los pocos con RTL integral.
|
|
|
|
|
|
### 6.15 · El síntoma «la demo no reacciona al idioma» — es el TOGGLE
|
|
|
|
|
|
Reportado tres veces (virtual-list, carousel, scroll-area) y medido en las tres:
|
|
|
**con el toggle en `auto`, las tres reaccionan al idioma**. Si el toggle está en
|
|
|
`ltr`/`rtl` EXPLÍCITO, el idioma no manda — el intent gana sobre la derivación,
|
|
|
que es lo ratificado en `013ceac57`.
|
|
|
|
|
|
No es un defecto, pero **despista de forma sistemática**: el control parece un
|
|
|
interruptor de dos posiciones y en realidad es un ciclo de tres donde sólo uno
|
|
|
cede el mando al idioma, y el estado sólo se lee en el `title`. Si se quiere
|
|
|
cerrar, es trabajo de la shell (hacer visible el estado), no del eje.
|
|
|
|
|
|
### 6.5 · Suelto, para quien siga
|
|
|
|
|
|
- **Hay componentes estampando `dir=""`** (cadena vacía) en vez de omitir el
|
|
|
atributo — visto en el wrapper del `sidebar` y en `tree-view`. No rompe nada
|
|
|
(valor inválido ⇒ hereda), pero contradice la letra del contrato y hace que
|
|
|
`[dir]` matchee. Sin investigar de dónde sale.
|
|
|
- `src/uix/eidos/lint.test.ts` falla por `audio-player` — **preexistente**,
|
|
|
verificado con stash. Es del hilo del sonido, no de este eje.
|
|
|
- Los 9 CSS tocados ya fallaban `prettier --check` ANTES de tocarlos. No los
|
|
|
formatees: son ficheros enteros de ruido (§5).
|
|
|
|
|
|
---
|
|
|
|
|
|
## 7. Las gráficas — barrido completo del catálogo (2026-08-03)
|
|
|
|
|
|
El usuario abrió este frente con «los chart calculan mal» y una captura. **Me
|
|
|
costó cuatro avisos suyos dar con lo que señalaba**, y la causa de las tres
|
|
|
primeras equivocaciones fue siempre la misma: verificar en LTR, con números, y
|
|
|
sobre un recorte que yo elegía. La regla que sale de aquí está en la fila
|
|
|
**RTL · SVG** del contrato y desarrollada en
|
|
|
`src/uix/eidos/components/chart/README.md` §Direction.
|
|
|
|
|
|
### 7.1 · Las dos decisiones, que NO son la misma
|
|
|
|
|
|
1. **¿Espeja la composición?** Sólo si el gráfico tiene EJE DE LECTURA. Lo
|
|
|
tienen `chart` (escala x), `calendar-heatmap`, la matriz de `heatmap`, el
|
|
|
canalón del `funnel` y `bar-list`. NO lo tienen los radiales (`gauge`,
|
|
|
`pie-chart`, `polar-area`, `radar-chart`, `smith-chart`): sus posiciones son
|
|
|
ángulos. Se espeja invirtiendo el RANGO de píxeles de la escala, o
|
|
|
reflejando la x (`mx()`). **Los valores de Y no se espejan nunca.**
|
|
|
2. **`text-anchor` es LÓGICO.** Si la composición espeja, se deja lógico —
|
|
|
voltearlo además lo cancela. Si NO espeja (canalón del eje Y, radar), se
|
|
|
fuerza el físico con `rtl.anchor()`.
|
|
|
|
|
|
Confundirlas es silencioso y se ve como las etiquetas caminando sobre el
|
|
|
gráfico.
|
|
|
|
|
|
### 7.2 · Estado del catálogo — TODAS revisadas
|
|
|
|
|
|
| | Estado |
|
|
|
| --- | --- |
|
|
|
| `chart` + presets (line/area/bar/bubble/scatter) | Arreglado: eje X espeja · tooltip hereda `formatY` · canalón adaptativo · etiquetas a 9.4px del tick · anchor físico |
|
|
|
| `funnel` | Arreglado: composición espejada (embudo derecha, etiquetas izquierda) |
|
|
|
| `calendar-heatmap` | Arreglado: semanas y canalón de días espejados |
|
|
|
| `heatmap` (matriz) | Arreglado: columnas y canalón de filas espejados |
|
|
|
| `radar-chart` | Arreglado: anchor físico, sin espejar (es radial) |
|
|
|
| `bar-list` | Correcto sin tocar — CSS lógico |
|
|
|
| `sparkline`, `bubble-chart` | Correctos sin tocar — heredan el frame de `chart` |
|
|
|
| `gauge`, `pie-chart`, `polar-area`, `smith-chart` | Correctos sin tocar — radiales |
|
|
|
|
|
|
Y los bloques de código de las demos (`pre/code/kbd/samp`) se declaran `ltr`:
|
|
|
el bidi los volvía ilegibles (`<script lang='ts'>` → `<'script lang='ts>`). Es
|
|
|
la versión ESTRECHA del pin que llevaba `<main data-uix-canvas>`.
|
|
|
|
|
|
### 7.3 · Trampas de medición — me engañaron tres veces
|
|
|
|
|
|
- **El bbox de un path espejado es idéntico al del original.** Mi «fingerprint»
|
|
|
por `getBoundingClientRect` declaró `sparkline` sin espejar cuando sí lo
|
|
|
hacía. Para un espejado hay que mirar la PRIMERA coordenada (`M0,…` vs
|
|
|
`M186,…`), no la caja.
|
|
|
- **Un desborde hacia DENTRO no sale en un test de «se sale del SVG».** El
|
|
|
barrido que escribí buscaba texto fuera del svg y dio 0 en todo mientras las
|
|
|
etiquetas invadían el área de trazado.
|
|
|
- **Medir contra el elemento equivocado.** El hueco del eje Y lo medí contra la
|
|
|
LÍNEA del eje (7px, aceptable) cuando lo que toca es medir contra el TICK, que
|
|
|
sale 4px hacia la etiqueta: quedaban 3.4px y se leía pegado.
|
|
|
- **Verificar en LTR un defecto de RTL.** Tres veces. El `text-anchor` lógico no
|
|
|
se manifiesta en LTR en absoluto.
|
|
|
- **La página completa, no el primer SVG.** La ruta `heatmap` tiene DOS modos
|
|
|
(`matrix` / `calendar`) tras un control; capturando el primer svg sólo veía uno.
|
|
|
|
|
|
### 7.4 · QUEDA en las gráficas
|
|
|
|
|
|
- `radar-chart`: «Economy» empieza en **x=-6**, o sea se sale unos píxeles del
|
|
|
SVG **también en LTR**. Preexistente, no tocado.
|
|
|
- No hay NINGÚN test de chart en el repo. Todo lo de esta sesión está verificado
|
|
|
a ojo y con medidas puntuales, no fijado por tests.
|
|
|
- Sin revisar en RTL: `metrics`, `bar-segment`, `chart-legend` como piezas
|
|
|
sueltas.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 8. La cola para mañana — por orden de valor
|
|
|
|
|
|
### 8.1 · `scroll-area` (lo único del eje con defecto sospechado y sin medir)
|
|
|
|
|
|
Dos señales, ninguna verificada:
|
|
|
- Su `resolvedDir` aparece **una sola vez** en todo el componente — la
|
|
|
declaración. Es código muerto.
|
|
|
- Usa `scrollLeft` en cuatro sitios (`isAtLeft = scrollLeft <= 0`, el ratio del
|
|
|
thumb, el salto por click, el arrastre) sin tratar que **en RTL los
|
|
|
navegadores lo devuelven NEGATIVO**: `isAtLeft` sería siempre cierto y el
|
|
|
ratio del thumb saldría negativo.
|
|
|
|
|
|
⚠️ No está medido porque **el scroll-area de la demo no tiene eje horizontal**
|
|
|
(`maxScroll: 0`). Primero hace falta un ejemplo con scroll horizontal; sin eso
|
|
|
no es observable. El usuario ya confirmó que el componente «funciona» en lo
|
|
|
demás, y medido: con el toggle en `auto`, ES→ltr y AR→rtl llegan bien.
|
|
|
|
|
|
### 8.2 · Agujeros de OBSERVABILIDAD (bloquean verificar, no rompen nada)
|
|
|
|
|
|
- **`drawer`**: su demo sólo expone `top/right/bottom/left`. La mitad LÓGICA
|
|
|
(`start`/`end`), que es la única que ejercita RTL, no se puede ver. El
|
|
|
componente está bien por lectura (`resolveDirection()` es el ejemplo a imitar)
|
|
|
y verificado sólo en su mitad física.
|
|
|
- **`float-panel`**: su demo no ancla ningún panel, así que la semilla anclada
|
|
|
—lo que se arregló en `caf3cfd88`— no es observable. Cubierto por
|
|
|
`anchored-seed.test.ts`.
|
|
|
- **§2.3**: cablear `secondaryValue` en la demo del slider.
|
|
|
|
|
|
### 8.3 · Verificaciones que quedaron sin hacer
|
|
|
|
|
|
- `archetypes` y `proof-of-human`: sus reglas viven en `@media (pointer: coarse)`
|
|
|
y se verificaron con `Emulation.setEmulatedMedia`; el resto de esa media query
|
|
|
no se ha mirado.
|
|
|
- **El swipe del `carousel` con un ratón de verdad.** El arreglo de `714a1ddb5`
|
|
|
está medido (el rail sigue al dedo, Δ+100 en ambas direcciones) pero conviene
|
|
|
el gesto real.
|
|
|
- `metrics`, `bar-segment`, `chart-legend` como piezas sueltas en RTL.
|
|
|
|
|
|
### 8.4 · Deuda ajena al eje, por gravedad
|
|
|
|
|
|
1. **91 ficheros de test falsifican `Soma.require()`** con
|
|
|
`vi.spyOn(Soma, 'require').mockReturnValue({ prefs: { getDir: () => dir } })`.
|
|
|
Incumple una Regla Crítica de CLAUDE.md: esa parte de la batería **no
|
|
|
ejercita el código real**. Es un proyecto entero y es lo más grave del
|
|
|
documento.
|
|
|
2. **No hay NINGÚN test de chart** en el repo. Todo lo de §7 está verificado a
|
|
|
ojo, no fijado.
|
|
|
3. `html lang` no sigue al idioma · componentes estampando `dir=""` vacío
|
|
|
(`sidebar`, `tree-view`).
|
|
|
4. **`smoke` y `perm:check` sin ejecutar** en toda la sesión.
|
|
|
|
|
|
### 8.5 · Lo que NO hay que rehacer
|
|
|
|
|
|
- Las 5 de §2.2 (slider, number-field, css-field, dropdown-menu, carousel) están
|
|
|
**medidas y sanas**; el único defecto era el pintado del swipe, ya arreglado.
|
|
|
- El motor `soma/layers/floating` está verificado y cubre sus 9 consumidores.
|
|
|
- `toast` es correcto: su vocabulario es físico y coherente consigo mismo.
|
|
|
- Las 11 gráficas del catálogo están revisadas (§7.2).
|