El campo obligatorio `dir: SomaRuntimeDir | null` de 4.1 conservaba el gate a
costa de ~116 lineas de ceremonia: 67 `dir: null` en fuente + 47 en tests
obligaban a ~75 componentes SIN direccion a pronunciarse sobre un eje que no
les concierne, y `parts` era un `readonly string[]` sin validar — un typo
estampaba nada, en silencio (fail-open). El usuario lo dijo antes que la
auditoria: "los dir no deben ser obligados de pasar".
Ahora lo declara el MORFO, que es donde viven los contratos:
direction: {} // 46 morfos — estampa en 'provider'
direction: { parts: ['trigger'] } // dropdown-menu (su raiz no renderiza)
direction: { parts: ['content'] } // drawer · float-panel (pintan en portal)
direction: { parts: [...,'input'] } // css-field · number-field (dos caras)
y el TIPO se computa de la declaracion, con el generico que ya existia inerte
en soma.runtime<M>:
morfo declara direction => dir: Active<Direction|undefined> REQUERIDO
no declara => dir?: never PROHIBIDO
Olvidar el cable no compila; cablear un eje a un componente sin direccion
tampoco. Y compileMorfo valida direction.parts contra el arbol declarado —
fail-CLOSED: el typo que antes estampaba nada ahora revienta al registrar.
El colapso: 49 getters `{ get: () => this.opts.dir.current }` -> `dir:
this.opts.dir`; los 67+47 `dir: null` MUEREN. El estampado gana su sitio en el
tipo de la superficie (`SomaRuntimePart.props` incluye `dir?: Direction`), asi
que los casts `(props as Record...).dir` de los tests sobran.
El gate cazo en el acto, y las dos presas eran reales:
- `command`: 4.1 le dejo `dir: null` en el runtime y el estampado manual
inline en el assert (la linea no estaba sola y el barrido no la vio) — un
componente CON direccion que habia quedado fuera del mecanismo.
- 5 runtimes SECUNDARIOS del mismo morfo (accordion item, nav-tree item,
emoji-picker cell, context-menu root-runtime, feed sentinel): mismo morfo,
misma exigencia — reciben la afirmacion del dueno (el estampado solo aterriza
en direction.parts, que esos runtimes no registran).
Guards nuevos:
- compile.test: normalizacion a ['provider'], partes explicitas, y el
fail-closed del typo (4 casos).
- direction-census.test: prop publica `dir?: Direction` <=> morfo.direction,
en ambos sentidos, con las excepciones FIRMADAS y sus razones (field-langs:
LangSpec; waveform: delega en el Slider; popover/tooltip/link-preview:
overlays puros cuya pintura estampa la capa flotante en el wrapper
portalizado — declararles direction seria el atributo inutil contra el que
4.1 avisaba). El propio censo cazo a esos tres: la excepcion quedo escrita
el mismo dia que el guard.
Verificado en Chrome con la fuente nueva (el morfo): dropdown-menu estampa en
el TRIGGER, css-field en raiz E input, drawer con pagina rtl por prefs deja el
content SIN atributo y computa rtl por herencia — el modelo del flip intacto.
check 77 = linea base · morfo+soma+active-uix 1401/1402 (el 1 es
soma-attr-audit, flaky bajo carga, pasa aislado) · rtl:check 1 (palabras) ·
docs:check 0/566.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>