# 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 `` 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.