|
|
# CONTINUE — la dirección deja de ser una convención
|
|
|
|
|
|
Handoff para una sesión nueva. **Leer entero antes de tocar nada**: el refactor
|
|
|
es pequeño en líneas y grande en alcance — toca un mecanismo del que cuelga todo
|
|
|
el catálogo.
|
|
|
|
|
|
Contexto previo: `docs/process/CONTINUE-direction.md` (el eje entero) y
|
|
|
`docs/canon/direction-contract.md` (el contrato, que es lo que este refactor
|
|
|
automatiza). **No hay que releerlos para empezar**; lo que hace falta está aquí.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 1. Por qué — los números
|
|
|
|
|
|
El contrato es correcto. El **mecanismo** para cumplirlo es teclear la misma
|
|
|
línea en cada componente, y por eso falla:
|
|
|
|
|
|
| | veces |
|
|
|
| -------------------------------------------------------------- | ------------ |
|
|
|
| Estampado del `dir` escrito a mano en un provider | **54** |
|
|
|
| Enlace al padre compuesto a mano en un envoltorio | **7** |
|
|
|
| `Content` portalizados que re-arrancan la cadena por su cuenta | **9** |
|
|
|
| Componentes que aceptaban `dir` y **no** lo estampaban | **20 de 55** |
|
|
|
|
|
|
Los 20 se arreglaron uno a uno (`21055bd3c`), leyendo receta y envoltorio de
|
|
|
cada uno. Encontrarlos costó una auditoría. **El siguiente componente que
|
|
|
alguien escriba volverá a tenerlo**, porque nada lo impide: la regla vive en la
|
|
|
cabeza del que lo escribe.
|
|
|
|
|
|
Una convención que hay que repetir 54 veces y que **20 de 55 incumplían no es
|
|
|
una convención — es una abstracción que falta**.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 2. Las dos comprobaciones, HECHAS
|
|
|
|
|
|
Las dos condiciones de viabilidad están verificadas. No hay que repetirlas.
|
|
|
|
|
|
### 2.1 · ¿Puede el runtime emitir un atributo que a veces debe estar AUSENTE?
|
|
|
|
|
|
**Sí, ya lo hace.** `src/uix/soma/runtime.svelte.ts:519-522`, en `renderProps()`:
|
|
|
|
|
|
```ts
|
|
|
for (const plan of compiledPart.dynamicAttrs) {
|
|
|
const value = evalAttrPlan(plan, bindings);
|
|
|
if (value !== undefined) props[plan.attr] = value;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
`undefined` ⇒ el atributo **no se escribe**. Es exactamente la semántica del
|
|
|
estampado crudo: nadie afirmó ⇒ no hay atributo ⇒ el elemento hereda.
|
|
|
`resolveProps()` (`:496-503`) hace lo mismo. **No hay que construir nada para
|
|
|
esto.**
|
|
|
|
|
|
### 2.2 · ¿Y sin declararlo en el morfo?
|
|
|
|
|
|
**También, y hay precedente literal.** `partPropsForRegistration()`
|
|
|
(`runtime.svelte.ts:538-551`) ya inyecta `id`, el marcador del componente,
|
|
|
`data-archetype` y `reg.attachment` **sin ninguna declaración en el morfo**, y
|
|
|
el comentario de `data-archetype` justifica por qué:
|
|
|
|
|
|
> `data-archetype` is part of static identity (cross-component classification,
|
|
|
> never mutates), so it ships through partProps — not through dom.apply.
|
|
|
|
|
|
Así que **el movimiento 1 NO exige tocar 55 ficheros de morfo.** Hay dos
|
|
|
vehículos posibles y la elección es de quien lo haga (§4.1).
|
|
|
|
|
|
### 2.3 · ¿Tiene la capa flotante acceso al dueño?
|
|
|
|
|
|
**Sí.** Los sub-proveedores llevan `this.provider` (verificado en `select` y
|
|
|
`popover`).
|
|
|
|
|
|
> ⚠️ **El reparto que decía este apartado era FALSO, y la fuga no estaba donde
|
|
|
> decía.** Censo real (§9.1): de los 11 `FloatingContent.create`, **10** pasan
|
|
|
> `dir: opts.dir` y **1** (`sidebar`) pasa `this.provider.opts.dir` — no 8 y 3.
|
|
|
> Y ese `opts.dir` **no era una fuga**: los 7 envoltorios `Content` que lo
|
|
|
> alimentan ya componían el enlace al padre a mano, así que la afirmación de la
|
|
|
> raíz SÍ cruzaba. Lo que no cruzaba era otra cosa (§9.1).
|
|
|
|
|
|
---
|
|
|
|
|
|
## 3. Qué está mal hoy, en una frase
|
|
|
|
|
|
**El estampado y la propagación son dos convenciones manuales**, y el eje entero
|
|
|
—cuatro auditorías, tres barridos y unos veinte defectos— es lo que cuesta
|
|
|
mantenerlas a mano.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 4. Los dos movimientos
|
|
|
|
|
|
### 4.1 · El estampado pertenece al runtime
|
|
|
|
|
|
**Objetivo**: que las 54 líneas escritas a mano se vuelvan cero, y que un
|
|
|
componente que acepte `dir` no pueda dejar de estamparlo.
|
|
|
|
|
|
**Vehículo A — inyección en `partPropsForRegistration`.** El provider registra
|
|
|
su parte pasando la dirección cruda; el runtime la emite junto a `id` y
|
|
|
`data-archetype`. Precedente exacto (§2.2). Mínimo código. Coste: el runtime
|
|
|
pasa a conocer un concepto que hoy no conoce.
|
|
|
|
|
|
**Vehículo B — un `dynamicAttr` universal en el compilador de morfo.** El
|
|
|
compilador añade a la parte `provider` un `AttrPlan` para `dir` con fuente
|
|
|
`fromProp: 'dir'`. Encaja con «morfo declara, soma ejecuta», y `renderProps()`
|
|
|
ya lo omitiría solo cuando resuelve `undefined` (§2.1). Coste: toca el
|
|
|
compilador y su caché; hay que mirar `contracts.cssSelectors`, porque el
|
|
|
conjunto cerrado de selectores que eidos puede usar se calcula ahí.
|
|
|
|
|
|
⚠️ **Decidir A o B es la primera tarea, y no está decidida.** Mi inclinación es
|
|
|
**A** por tamaño y por precedente, pero B es más fiel a la doctrina. Léete los
|
|
|
dos sitios antes de elegir.
|
|
|
|
|
|
⚠️ **Lo que NO debe pasar**: que el atributo aparezca en componentes que hoy
|
|
|
NO deben llevarlo. Son cuatro y están verificados: `popover`, `tooltip`,
|
|
|
`link-preview` y `context-menu` — su raíz no renderiza elemento o no pinta nada
|
|
|
direccional, y su pintura vive en el panel portalizado. Si el runtime lo emite
|
|
|
universalmente, esos cuatro ganan un atributo inútil. **Es la parte delicada del
|
|
|
movimiento 1**: la emisión tiene que colgar de que el componente ACEPTE `dir`,
|
|
|
no de que exista una parte `provider`.
|
|
|
|
|
|
⚠️ Y **la excepción del §2 del contrato**: `avatar` e `image` NO aceptan `dir` a
|
|
|
propósito (su `:dir()` sólo traduce a físico una prop lógica que ya eligió el
|
|
|
consumidor). Si acaban con la prop por efecto colateral, el refactor se ha
|
|
|
llevado por delante una decisión firmada por el usuario.
|
|
|
|
|
|
### 4.2 · La propagación pertenece a la capa flotante
|
|
|
|
|
|
**Objetivo**: que la afirmación de la raíz cruce el portal sin que nadie escriba
|
|
|
el enlace.
|
|
|
|
|
|
Hoy cada `Content` portalizado vuelve a arrancar `activeDir` desde cero, y por
|
|
|
eso la afirmación no cruza. La forma: **`FloatingContent` toma la dirección del
|
|
|
proveedor DUEÑO, y el `dir` propio del Content la pisa si existe.** Una
|
|
|
composición dentro de la capa en vez de once fuera.
|
|
|
|
|
|
Los 7 enlaces compuestos a mano (`dropdown-menu` ×2, `menubar`, `select`,
|
|
|
`combobox`, `context-menu` ×2) y el de `color-picker` se borran.
|
|
|
|
|
|
Y **cierra sin trabajo extra el pendiente que quedó reportado**: ningún picker
|
|
|
de eidos reenvía `dir` a su `PopoverContent`, así que el cromo del `picker-shell`
|
|
|
dentro del portal (`picker-shell.css:78`, el split del pie) está sin cubrir en
|
|
|
los siete. Con 4.2 deja de ser una tarea; sin 4.2 son seis parches idénticos.
|
|
|
|
|
|
---
|
|
|
|
|
|
> ✅ **4.2 EJECUTADA — ver §9.** Lo de abajo es el enunciado original, conservado
|
|
|
> porque el diagnóstico cambió al verificarlo.
|
|
|
|
|
|
## 5. Orden, y por qué
|
|
|
|
|
|
**4.2 primero.** Es más pequeña, está más acotada (una capa, once consumidores)
|
|
|
y su verificación es directa: abrir un `Select` y comprobar que trigger y panel
|
|
|
coinciden. Además borra código, así que 4.1 llega a un árbol más limpio.
|
|
|
|
|
|
**4.1 después**, y sólo cuando 4.2 esté verde y commiteada.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 6. Verificación — la línea base exacta
|
|
|
|
|
|
Antes de empezar, tomar estas medidas y **compararlas al final**. Cualquier
|
|
|
desviación hay que explicarla, no absorberla:
|
|
|
|
|
|
| | valor esperado |
|
|
|
| ------------------------------------ | -------------------------------------------------------------------------- |
|
|
|
| `npx svelte-check --threshold error` | **77** (la base; sube si otra sesión toca `media-player`) |
|
|
|
| `npm run rtl:check` | **1 error** — `palabras-chrome.css:446`, preexistente y excluido |
|
|
|
| `npm run docs:check` | 0 errores sobre 564 docs |
|
|
|
| Tests de los componentes tocados | verdes; `eidos/lint.test.ts` falla por `audio-player` sin morfo, **ajeno** |
|
|
|
|
|
|
⚠️ **En navegador, y no es opcional**: el censo del estampado NO se puede hacer
|
|
|
por grep. Dos componentes (`listbox`, `navigation-menu`) estampan a través de un
|
|
|
alias `assertedDir` y un grep del patrón literal los da como huecos. La
|
|
|
comprobación buena es leer el DOM:
|
|
|
|
|
|
```js
|
|
|
// con la demo abierta, por cada componente
|
|
|
document.querySelector('[data-select]').getAttribute('dir');
|
|
|
```
|
|
|
|
|
|
⚠️ **El panel del navegador tiene que estar VISIBLE.** Oculto no compone frames:
|
|
|
no hay capturas y **las transiciones CSS se congelan**, así que
|
|
|
`getComputedStyle(...).transform` devuelve la matriz identidad y parece un
|
|
|
defecto que no lo es. Me pasó dos veces.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 7. Trampas ya pagadas — no volver a pisarlas
|
|
|
|
|
|
- **El estampado es el CRUDO** (`opts.dir.current`), nunca `resolvedDir`. Un
|
|
|
valor resuelto escribe `dir="ltr"` cuando nadie afirmó nada, forzando el
|
|
|
subárbol a LTR dentro de una página RTL. Pasó en `sidebar` y lo escribí yo.
|
|
|
- **`dir` en un `<svg>` no hace nada** — la hoja UA de HTML no alcanza a los
|
|
|
elementos SVG. Va en el envoltorio HTML. Medido en Chrome.
|
|
|
- **`OptsFromProps` quita el `undefined`** del prop público y destruye la
|
|
|
distinción entre «nadie afirmó» y `'ltr'`. Quien lo use declara `dir` aparte.
|
|
|
- **Clasificar esto por grep se equivoca.** Yo di `avatar` por defectuoso
|
|
|
leyendo el patrón en vez del componente, y el usuario lo cazó. Abre la receta,
|
|
|
abre el provider, mira dónde renderiza cada parte.
|
|
|
- **Una migración mecánica hereda los defectos que traduce.** El barrido de
|
|
|
§6.3 cambió doce selectores sin mirar los cuerpos y arrastró dos dobles
|
|
|
volteos hasta que los cazó el guard RTL-2.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 8. Estado del árbol al escribir esto
|
|
|
|
|
|
Rama `alpha-0.1-dir-prefs`, con remoto `gita` (no hay `origin`). Último commit
|
|
|
del eje: `21055bd3c`. Sin tocar y **no commitear**: `.claude/settings.local.json`,
|
|
|
`src/arts/adom/__scratch-verify.ts`, `web/routes/alpha/`. `media-player` lo
|
|
|
edita otra sesión — excluirlo de todo staging.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 9. Sesión 2026-08-04 — §4.2 ejecutada
|
|
|
|
|
|
Sin commitear. Rama `alpha-0.1-dir-prefs`, **HEAD se movió a `4755d97d1`
|
|
|
mientras trabajaba** (otra sesión, `media-player` + `audio-player`): esos
|
|
|
ficheros están fuera de este cambio y fuera de cualquier staging.
|
|
|
|
|
|
### 9.1 · El censo del §2.3 estaba mal, y la fuga era otra
|
|
|
|
|
|
Rehecho leyendo los ficheros, no el patrón:
|
|
|
|
|
|
| | |
|
|
|
| ------------------------------------------------------------- | -------------------------------------------- |
|
|
|
| `FloatingContent.create` | **11** sitios en 9 ficheros |
|
|
|
| …pasando `dir: opts.dir` | **10** |
|
|
|
| …pasando `this.provider.opts.dir` | **1** (`sidebar`) |
|
|
|
| Envoltorios `Content` que componían el enlace al padre A MANO | **7** |
|
|
|
| Envoltorios `Content` SIN enlace | **3** — `popover`, `tooltip`, `link-preview` |
|
|
|
|
|
|
**Y esos 3 no eran un defecto**: sus raíces no aceptaban `dir` en absoluto, así
|
|
|
que no había nada de la raíz que heredar. O sea **entre los 11 consumidores del
|
|
|
motor flotante no había ninguna fuga**.
|
|
|
|
|
|
**La fuga estaba un piso más arriba: en quien COMPONE un popover.** Nueve
|
|
|
componentes crean un `PopoverProvider` internamente
|
|
|
(`color-picker`, `date-picker`, `date-range-picker`, `emoji-picker`,
|
|
|
`gradient-picker`, `natural-time-picker`, `picker`, `time-picker`,
|
|
|
`time-range-picker`) y **siete de ellos aceptan `dir`** — pero `PopoverProvider`
|
|
|
no tenía dónde recibirlo, así que la afirmación moría en el borde del portal.
|
|
|
|
|
|
Medido en Chrome ANTES, con `<DatePicker dir="rtl">` y la página en LTR:
|
|
|
|
|
|
```
|
|
|
[data-floating-wrapper] dir="ltr" ← el panel entero forzado a LTR
|
|
|
[data-picker-shell] direction ltr
|
|
|
[data-picker-footer] direction ltr Clear x=15 · Cancel x=102 · Close x=209
|
|
|
[data-calendar] direction RTL ← lo ÚNICO que espejaba
|
|
|
```
|
|
|
|
|
|
El calendario mirroreaba porque es un componente soma propio que recibe el `dir`
|
|
|
por el árbol de proveedores; el cromo que lo rodea —el `picker-shell`, cuyo pie
|
|
|
reparte con `margin-inline-end: auto` (`picker-shell.css:78`)— no. **Panel
|
|
|
partido por la mitad**, que es exactamente el fallo del §2 del contrato.
|
|
|
|
|
|
### 9.2 · La forma implementada
|
|
|
|
|
|
```
|
|
|
el dir propio de la superficie → el dir afirmado por el proveedor DUEÑO → omitir
|
|
|
```
|
|
|
|
|
|
- `FloatingProviderOpts.dir` — el dueño baja su afirmación (ya pasada por
|
|
|
`activeDir`, o sea `prop → prefs`, así que la cola de prefs queda intacta).
|
|
|
- `FloatingContent.assertedDir` — `opts.dir.current ?? provider.opts.dir.current`.
|
|
|
Los dos estampados del wrapper (rama nativa y rama JS) leen de ahí.
|
|
|
- `createFloatingShellRoot` lo reenvía; es la puerta de 8 de los 10
|
|
|
`FloatingProvider`. Los otros 2 (submenús de `dropdown-menu` y `context-menu`)
|
|
|
pasan el del menú padre.
|
|
|
|
|
|
⚠️ **El punto que decide si esto funciona**: `FloatingContentOpts.dir` pasa a ser
|
|
|
**la prop cruda del Content**, no el resultado de `activeDir`. Si el envoltorio
|
|
|
resolviera ahí, el valor sería casi siempre concreto (prefs) y el dueño **nunca**
|
|
|
ganaría el fallback. Los 10 envoltorios pasan ahora `readableActive(() => dir)`.
|
|
|
|
|
|
Consecuencias:
|
|
|
|
|
|
- **Los 7 enlaces compuestos a mano se borran**, y con ellos el `soma`, el
|
|
|
`activeDir` y el `parentRoot`/`parentMenu` de cada envoltorio.
|
|
|
- `popover`, `tooltip` y `link-preview` **aceptan `dir` en su raíz** (prop
|
|
|
pública nueva + opt). No estampan nada —no renderizan elemento—; el valor
|
|
|
existe para cruzar el portal, y es lo que permite que un picker empuje su
|
|
|
dirección al panel. Sin esa prop habría que escribir
|
|
|
`activeDir(() => undefined, soma)` en la raíz, que es peor y no da nada a cambio.
|
|
|
- Los 9 composers pasan su dir al `PopoverProvider`. `gradient-picker` y `picker`
|
|
|
no aceptan `dir` (§9.17 del eje), así que corren la cadena con el primer
|
|
|
eslabón ausente: `activeDir(() => undefined, soma)`.
|
|
|
|
|
|
**Queda UN `activeDir` con enlace al padre**, y a propósito:
|
|
|
`dropdown-menu-sub-content.svelte` lo necesita para `effectiveSide` (el lado por
|
|
|
defecto del submenú es un `left`/`right` concreto, o sea MATEMÁTICA, que sí
|
|
|
quiere valor resuelto). El ATRIBUTO ya no sale de ahí.
|
|
|
|
|
|
### 9.3 · Verificación
|
|
|
|
|
|
Medido en Chrome real, con la página en LTR y el `dir` movido por el control de
|
|
|
la demo (el camino real, nunca `setAttribute`):
|
|
|
|
|
|
| | antes | después |
|
|
|
| ------------------------------------------ | -------------- | ------------------ |
|
|
|
| `date-picker` · wrapper | `ltr` | **`rtl`** |
|
|
|
| `date-picker` · shell / footer | `ltr` | **`rtl`** |
|
|
|
| `date-picker` · Clear/Cancel/Close x | 15 / 102 / 209 | **200 / 92 / 15** |
|
|
|
| `date-range-picker` · Clear/Cancel/Close x | — | **448 / 217 / 17** |
|
|
|
|
|
|
Y a ojo: el panel entero espeja —cabecera, rejilla de días y pie—, no sólo el
|
|
|
calendario. **Sin regresión** en los que ya funcionaban, comprobados uno a uno:
|
|
|
`dropdown-menu` (raíz y submenú, con `data-side=left` en RTL), `context-menu`,
|
|
|
`menubar`, `select` (por prefs, las dos direcciones), y `tooltip` en `auto`, que
|
|
|
sigue estampando `ltr` — la cola de prefs no se perdió.
|
|
|
|
|
|
| guard | resultado |
|
|
|
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
|
| `npx svelte-check --threshold error` | **78 = línea base** (A/B con stash: 78 sin mis cambios) |
|
|
|
| Tests de los 20 ámbitos tocados | **158/158** |
|
|
|
| `npm run rtl:check` | **1** — `palabras-chrome.css:446`, preexistente |
|
|
|
| `npm run docs:check` | 0 sobre 565 |
|
|
|
| Pasada completa de vitest | 9 fallos, **ninguno mío** (A/B con el árbol limpio: los 6 deterministas de `contracts`/`eidos-lint` salen igual; `soma-attr-audit`, `orca` y `cookie-adapter` pasan aislados — flaky bajo carga) |
|
|
|
|
|
|
⚠️ La línea base de `check` es **78**, no 77 como decía §6 — con el
|
|
|
`__scratch-verify.ts` sin commitear en el árbol.
|
|
|
|
|
|
### 9.4 · Trampas de esta sesión
|
|
|
|
|
|
- **Lancé `prettier --write` sobre los 43 ficheros tocados y fue un error.**
|
|
|
Reformateó líneas PREEXISTENTES (imports colapsados, prosa de dos READMEs
|
|
|
reflowada: `popover/README.md` pasó de +7/−4 a +49/−46). Deshecho a mano hasta
|
|
|
devolver la diff a su forma quirúrgica. La regla del §5 del eje sigue viva:
|
|
|
**formatea sólo lo que ensuciaste, y mira el tamaño de la diff después.** Lo
|
|
|
único que se conserva del formateo son dos líneas MÍAS que superaban las 100
|
|
|
columnas.
|
|
|
- **Un click JS justo después de navegar no engancha**: hasta que Svelte hidrata,
|
|
|
`.click()` no dispara el handler. Y leer el atributo en la MISMA llamada que el
|
|
|
click devuelve el valor viejo — hay que separar en dos llamadas.
|
|
|
- **El control de dirección de algunas demos vive bajo la pestaña «eidos
|
|
|
System»**, no en la vista Live. Sin cambiar de pestaña no hay chips que pulsar.
|
|
|
|
|
|
### 9.5 · Lo que queda apuntado, sin tocar
|
|
|
|
|
|
1. **9 campos `resolvedDir` MUERTOS en los proveedores `Content`**
|
|
|
(`select` 495, `combobox` 577, `dropdown-menu` 314 y 1230, `context-menu` 252
|
|
|
y 995, `menubar` 563, `tooltip` 409, `link-preview` 268). Verificado que
|
|
|
**nadie los lee** — toda la matemática direccional consume
|
|
|
`this.provider.resolvedDir`, o sea el de la RAÍZ. Ya eran código muerto antes;
|
|
|
ahora además su docblock («Direction for this part's OWN math») describe algo
|
|
|
que `opts.dir` ya no es. **Borrarlos requiere tu palabra.**
|
|
|
2. **Agujero de observabilidad en 5 demos de picker**: `time-picker`,
|
|
|
`time-range-picker`, `color-picker`, `emoji-picker` y `gradient-picker` tienen
|
|
|
el control `dir` del arnés pero **no se lo pasan al componente**, así que la
|
|
|
mitad que ejercita el portal no se puede ver. Sólo `date-picker` y
|
|
|
`date-range-picker` lo pasan — y por eso son los dos que pude medir. Mismo
|
|
|
agujero que §8.2 del eje, misma clase de arreglo.
|
|
|
3. **`context-menu-sub-content` clava `side = 'right'`** (físico) mientras
|
|
|
`dropdown-menu-sub-content` lo deriva de la dirección. Incoherencia entre dos
|
|
|
componentes hermanos, preexistente y ajena a este cambio.
|
|
|
4. **§4.1 sigue entera y sin empezar** — el estampado del `dir` en el runtime, y
|
|
|
la elección Vehículo A vs B sigue sin tomar.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 10. Auditoría de la sesión — lo que salió al re-verificarlo todo
|
|
|
|
|
|
### 10.1 · Código muerto: BORRADO
|
|
|
|
|
|
Los 9 `resolvedDir` de §9.5.1 están **eliminados**. Antes de borrar, verificado
|
|
|
que ninguno se lee: las 5 lecturas vivas de `resolvedDir` en esos ficheros son
|
|
|
todas `this.provider.resolvedDir` —o sea el de la RAÍZ— desde
|
|
|
`SelectTriggerProvider`, `MenuContentProvider`, `ContextMenuContentProvider`,
|
|
|
`MenubarMenuProvider` y `MenubarTriggerProvider`. Cero lecturas desde eidos,
|
|
|
blocks, packs, `web/routes` o tests. Quedan **5** declaraciones, las cinco de
|
|
|
raíces vivas.
|
|
|
|
|
|
### 10.2 · Las demos: las 5 cableadas
|
|
|
|
|
|
`time-picker`, `time-range-picker` y `color-picker` no tenían control de
|
|
|
dirección **en absoluto**; `emoji-picker` y `natural-time-picker` lo tenían en el
|
|
|
arnés pero **no se lo pasaban al componente**. Ahora las siete demos de picker
|
|
|
que aceptan `dir` lo pasan a la instancia, así que el camino de la PROP —el
|
|
|
único que ejercita el portal— es observable en todas.
|
|
|
|
|
|
⚠️ **Corrección a §9.5.2**: dije «5 demos con el control pero sin pasarlo». Falso
|
|
|
en dos sentidos: tres no tenían control, y `gradient-picker`/`picker` no aceptan
|
|
|
`dir` (§9.17 del eje), así que no entran.
|
|
|
|
|
|
### 10.3 · Censo de ISLAS — un defecto nuevo, PREEXISTENTE y de otra familia
|
|
|
|
|
|
Con las demos cableadas se puede al fin escanear cada panel en RTL buscando
|
|
|
elementos que estampen una dirección DISTINTA a la del envoltorio flotante:
|
|
|
|
|
|
| panel | islas |
|
|
|
| ------------------------------------------------------------------------- | -------------------------------------------------------- |
|
|
|
| `date-picker` · `date-range-picker` · `time-picker` · `time-range-picker` | **0** |
|
|
|
| `natural-time-picker` | **1** — `[data-slider]` |
|
|
|
| `emoji-picker` | **3** — `[data-command]` + `[data-toggle-group]` ×2 |
|
|
|
| `color-picker` | **1** — `[data-color-field-format-select]` (un `Select`) |
|
|
|
|
|
|
**La forma es la misma que arregló §4.2, pero para composición EN SITIO en vez
|
|
|
de por portal**: un componente eidos monta otro componente del canon
|
|
|
(`natural-time-picker-panel.svelte` → `Slider.Provider`,
|
|
|
`emoji-picker-content.svelte` → `Command` + `ToggleGroup`,
|
|
|
`color-field-format-select.svelte` → `Select`) **sin reenviarle el `dir`**. El
|
|
|
hijo corre su propia cadena, cae en prefs, estampa `ltr` — y ese atributo CORTA
|
|
|
la herencia del panel, que sí es `rtl`.
|
|
|
|
|
|
Es **preexistente**: mientras el panel entero era LTR la isla no se veía. Lo que
|
|
|
hizo §4.2 fue volverla visible.
|
|
|
|
|
|
⚠️ **No lo he arreglado, y es deliberado**: no hay UN solo precedente en eidos de
|
|
|
reenviar `dir` a un componente compuesto (`grep "dir={" src/uix/eidos` da un
|
|
|
único resultado, y es `data-dir` de otra cosa). Arreglarlo es inventar el patrón
|
|
|
—doctrina nueva, no un parche— y toca 3 ficheros que este movimiento no toca.
|
|
|
**Es un movimiento propio.**
|
|
|
|
|
|
La misma clase de defecto vivía en la DEMO del `color-picker`, que monta `<Tabs>`
|
|
|
y un `<ColorField>` a mano dentro del panel: ahí sí lo arreglé, porque es demo y
|
|
|
porque si no el control de dirección que acabo de añadir enseñaría un panel medio
|
|
|
volteado y parecería defecto del framework.
|
|
|
|
|
|
### 10.4 · Verificación tras la auditoría
|
|
|
|
|
|
| | |
|
|
|
| ------------------------------------ | ------------------------------------------------------- |
|
|
|
| `npx svelte-check --threshold error` | **78 = línea base** (A/B con stash: 78 sin mis cambios) |
|
|
|
| Tests de los 20 ámbitos tocados | **158/158** |
|
|
|
| `npm run rtl:check` | **1**, el de `palabras` |
|
|
|
| `npm run docs:check` | 0 sobre 565 |
|
|
|
| Islas en los 7 paneles | 4 paneles a 0; las 5 restantes documentadas arriba |
|
|
|
|
|
|
⚠️ **Trampa medida dos veces**: `svelte-check` dio **79** en una pasada y 78 en
|
|
|
la siguiente sin tocar nada. Era una CARRERA con la otra sesión guardando
|
|
|
ficheros de `media-player`. En una rama compartida, un número que no reproduce
|
|
|
no es un hallazgo — repite la medida antes de perseguirlo.
|
|
|
|
|
|
⚠️ Las 5 demos tocadas **ya fallaban `prettier --check` en HEAD**, así que no se
|
|
|
formatearon (§9.4).
|
|
|
|
|
|
---
|
|
|
|
|
|
## 11. ENDGAME ejecutado — 2026-08-05 (P1–P5, decisiones D1–D4 firmadas)
|
|
|
|
|
|
El plan completo vive en la sesión que lo ejecutó; lo durable está en el canon
|
|
|
(`direction-contract.md` §1/§2/§6) y en `docs/decisions.md`. Resumen operativo:
|
|
|
|
|
|
| fase | commit | qué |
|
|
|
| ---- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
|
| P1 | `9158d0796` | **DirectionContext dentro de `activeDir`** — la cadena gana el eslabón del ancestro; los 3 reenvíos de 4.3, el enlace manual del submenú y los 3 canarios de media-player se BORRAN. El repro de la otra sesión pasa sin ellos. |
|
|
|
| P2 | `973d2c886` | **Proyección automática + semilla** — `createActiveUix` proyecta por defecto (`projectPrefs:false` opt-out; attach opt-in); `readPrefsEnvironmentFromDom` siembra `<html dir>` puesto a mano (`intent > env > derive > default`). Los 3 cableados manuales borrados en el MISMO commit. |
|
|
|
| P3 | `01cc13d41` | **El flip** — `activeDir(dir)` devuelve la AFIRMACIÓN (prop ?? ctx), sin prefs y sin parámetro soma; la matemática vive en `resolveDir(dir, soma)`. En `auto` la página entera lleva UN `dir` (el `<html>`). SSR: cero `dir` en el payload. |
|
|
|
| P4 | `fc84305c2` | **El morfo declara** — `direction: { parts }` en 51 morfos; `soma.runtime<M>` computa el requisito del tipo (declarado ⇒ requerido; no ⇒ prohibido); `compileMorfo` valida parts fail-closed; censo prop↔morfo como guard (`direction-census.test.ts`) con las excepciones firmadas (field-langs, waveform, popover/tooltip/link-preview). La ceremonia de 4.1 (67+47 `dir: null`, 49 getters) MUERE. |
|
|
|
| P5 | (este commit) | `activeEidosDir` gana el contexto (compartido con soma) y se parte igual (`resolveEidosDir`); charts al día; corpus + decisiones + memoria. |
|
|
|
|
|
|
**El estado final del eje**: nadie escribe `dir=` a mano; ausente hereda de
|
|
|
verdad; olvidar el cable no compila; el ambiente llega al DOM una vez. Los tres
|
|
|
`dir=` de media-player eran los canarios y CANTARON (panel rtl sin ellos).
|
|
|
|
|
|
### Verificación final medida
|
|
|
|
|
|
- `auto`: **1** `[dir]` en toda la página (el `<html>` proyectado) — antes ~cada raíz.
|
|
|
- prefs rtl: select y su panel portalizado SIN atributo, computan rtl por herencia.
|
|
|
- prop rtl: trigger del dropdown estampa, el portal cruza, submenú `data-side=left`, 0 islas.
|
|
|
- matemática: slider sin atributo con página rtl — click 25% físico → 75, `ArrowRight` baja.
|
|
|
- SSR (curl): cero `dir=` en select y accordion.
|
|
|
- `check` 77 = línea base en todas las fases · `rtl:check` 1 (palabras) · `docs:check` 0/566 · suites 1401/1402 (el 1: `soma-attr-audit`, flaky bajo carga, pasa aislado).
|
|
|
|
|
|
### La cola que QUEDA (pospuesta por decisión D4 o ajena)
|
|
|
|
|
|
- ~~**Censo de API**~~ **CERRADO 2026-08-05 (tarde)** — los tres que quedaban:
|
|
|
`picker` y `gradient-picker` aceptan `dir?: Direction` (prop → `activeDir` →
|
|
|
morfo `direction: {}` → runtime; el genérico arrastra la opt a
|
|
|
natural-time-picker y gradient-picker por tipo), y `chat-log` acepta y
|
|
|
reenvía a los `Feed`/`VirtualList` que compone — **sus 2 errores de línea
|
|
|
base MUEREN: la base pasa de 77 a 75**. El censo-guard los cuenta sin
|
|
|
excepción nueva. Verificado en Chrome: gradient-picker root+panel `rtl` por
|
|
|
prop con 0 islas; chat-log AUSENTE en auto y `rtl` por prop.
|
|
|
- **Opts canónicas transversales** (bindProps v2) — eje propio; el censo
|
|
|
completo está en el reporte del agente de diseño de esta sesión.
|
|
|
- Deuda ajena intacta: 91 tests que falsean `Soma.require()` · cero tests de
|
|
|
charts · `smoke`/`perm:check` sin correr.
|
|
|
|
|
|
### Trampas nuevas de esta pasada, para no repetir
|
|
|
|
|
|
- `git commit` SIN pathspec en rama compartida se llevó por delante el índice
|
|
|
de la otra sesión una vez (b41669c43). Desde entonces: SIEMPRE `git commit --
|
|
|
<rutas>`.
|
|
|
- El detector de huérfanos por identificador se tropezó con la RUTA del import
|
|
|
(`core/soma.svelte` contiene "soma") — excluir líneas de import antes de
|
|
|
buscar usos.
|
|
|
- `readableActive`/vista de prefs construida DENTRO de una función pura llamada
|
|
|
por `$derived` = una alocación por pasada — cachear por instancia (WeakMap en
|
|
|
`resolveEidosDir`).
|
|
|
- El gate de P4 cazó a `command` (estampado inline en el assert que el barrido
|
|
|
4.1 no vio) y a 5 runtimes secundarios del mismo morfo — un tipo condicional
|
|
|
bien puesto encuentra lo que los barridos no.
|