docs(direction): handoff del refactor — la direccion deja de ser una convencion

El usuario preguntó si lo entregado era lo mas logico y profesional. No lo es:
el contrato es correcto y el MECANISMO para cumplirlo es teclear la misma linea
en cada componente. 54 estampados a mano, 7 enlaces al padre a mano, 9 Content
que re-arrancan la cadena por su cuenta, y 20 de 55 componentes que lo
incumplian hasta `21055bd3c`. Una convencion que hay que repetir 54 veces y que
20 de 55 incumplian no es una convencion — es una abstraccion que falta.

`docs/process/CONTINUE-direction-runtime.md`, para abrir en sesion nueva. Lleva
LAS DOS COMPROBACIONES DE VIABILIDAD YA HECHAS, que es lo que separa un handoff
util de una idea:

- El runtime YA omite un atributo cuyo valor resuelve a `undefined`
  (`runtime.svelte.ts:519-522`). Es exactamente la semantica del estampado
  crudo. No hay que construir nada para eso.
- El runtime YA inyecta atributos sin declaracion en morfo — `id`, el marcador,
  `data-archetype`, `reg.attachment` (`:538-551`), con el comentario que lo
  justifica. Asi que el movimiento NO exige tocar 55 ficheros de morfo.
- La capa flotante tiene acceso al dueño: los sub-proveedores llevan
  `this.provider`. Y el reparto de los 11 consumidores es el diagnostico —
  8 pasan el `dir` DEL PROPIO Content (la fuga), 3 el de la raiz (lo correcto).

Dos movimientos, con el orden razonado (la propagacion primero: mas acotada,
verificable de un vistazo, y BORRA codigo, asi que el segundo llega a un arbol
mas limpio). Dos vehiculos posibles para el primero, sin decidir a proposito.

Lleva ademas la linea base exacta de verificacion y las trampas ya pagadas: el
crudo nunca resuelto, `dir` en un `<svg>` no hace nada, `OptsFromProps` quita el
`undefined`, clasificar por grep se equivoca (me paso con `avatar`), y una
migracion mecanica hereda los defectos que traduce.

⚠️ Anota lo que el refactor NO debe romper: los 4 que correctamente NO estampan
(popover, tooltip, link-preview, context-menu) y la excepcion firmada por el
usuario de `avatar`/`image`, que no aceptan `dir` a proposito.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-dir-prefs
dev 2 months ago
parent c798f7465a
commit 7b0556a955

@ -0,0 +1,208 @@
# 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`). Y el reparto actual de los 11 consumidores lo dice todo:
- **8** pasan `dir: opts.dir` — el `dir` **del propio Content**, resuelto por su
cadena, que no tiene paso por el padre. **Ésa es la fuga.**
- **3** pasan `this.provider.opts.dir` — el de la raíz. Lo correcto.
`menubar-provider.svelte.ts:290` es el modelo bueno; `select-provider.svelte.ts:538`
el de la fuga.
---
## 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.
---
## 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.

@ -1560,6 +1560,18 @@ diciendo que el guard no ve esta forma, y dejarán de ser verdad):
`palabras-chrome.css:446`, preexistente y excluido de escritura. Cualquier otro
número hay que explicarlo. Más `npx vitest run src/uix/eidos/rtl-lint.test.ts`.
### 10.1-bis · El refactor — handoff APARTE
El estampado y la propagación siguen siendo **convenciones manuales**: 54 líneas
escritas a mano, 7 enlaces compuestos a mano, y 20 de 55 componentes que las
incumplían hasta `21055bd3c`. Eso no se arregla con otro barrido.
📄 **`docs/process/CONTINUE-direction-runtime.md`** — handoff propio, para abrir
en sesión nueva. Lleva las dos comprobaciones de viabilidad YA HECHAS (el
runtime ya omite un atributo `undefined`; ya inyecta atributos sin declaración
en morfo, con precedente literal), los dos movimientos, el orden y la línea base
de verificación.
### 10.2 · El resto de la cola, por valor
- **Los charts deberían aceptar `dir`.** Es la divergencia DECLARADA en §7 del

Loading…
Cancel
Save

Powered by TurnKey Linux.