El defecto que tres auditorías encontraron y ninguna cerró no era que N eventos
redirigieran sin declararlo: era que la API de emisión aceptaba un HTMLElement
suelto, compensando que el registro de partes perdía la identidad de instancia.
Censar y arreglar a mano reproduce el modo de fallo. Esto lo hace inexpresable.
F1 — SomaRuntime<M> genérico, SIN default. Nombres de evento, kebabs de parte y
claves de events/actions tipados contra la declaración (EventNameOf /
PartKebabOf / ActionNameOf, los extractores que semaSelector ya usaba del otro
lado). 104 anotaciones migradas + 3 wrappers que devolvían tipado pero tomaban
sources ANCHO. SomaRuntime<Morfo> queda sólo en la tabla de contratos.
F2 — registro por instancia: Map<part, PartRegistration[]>, pertenencia
mantenida por el stream del attachment (poda al desmontar, re-entrada al
remontar), resolución liveReg = la instancia VIVA más nueva. Mata de paso la
clase "la más nueva muerta ensombrece a una viva".
F3 — tres formas legales de destino: el target declarado; el anclaje por
instancia (SomaRuntimePart.trigger, tipado por EventNameTargeting, +
runtime.partInstance por identidad de referencia); y targetFallback, la cadena
por estado de montaje declarada en el morfo y resuelta por resolveEmitTarget —
LA resolución única para validación, emit y foco a11y, que ya no pueden
discrepar. ~30 componentes migrados; censo de targetOverride 148 -> 89.
F4 — A-36 tenía un superviviente. Medido en dropdown-menu: bajo `pre` el `open`
no colisionaba con el contact-activate del Button compuesto, se PERDÍA entero
porque su content no había montado. Aplicada la receta de float-panel a
dropdown-menu Y context-menu. Ahora: contact-activate en trigger, open en
content — dos superficies, ambas expresando.
Submenús mudos, tres componentes. No era cableado: el morfo no declaraba el
evento. onion-menu recibe emerge-expand/emerge-collapse (la forma de TreeView:
un nivel revela sus hijos en el sitio); dropdown-menu y context-menu reciben
sub-open/sub-close sobre sub-content, distintos del open raíz a propósito
porque el sub es su propia superficie flotante. Nacen con la receta A-36 para
no repetirla en código nuevo.
chronos — declaraba 26 partes y registraba UNA; 17 role= a mano y el ARIA
declarado no llegaba nunca al DOM. Se anula la mitad sobre-extendida de la
decisión de 29ffe902a: la reutilización sigue viajando por el motor puro, pero
la ESTRUCTURA se compone como en Table y Calendar. C1 (las 6 partes que emiten,
chronos con cero targetOverride) + C2 (8 singletons) + C3 8/9.
Registrar una parte DESTAPA declaraciones falsas, tres veces en un pase: el chip
declaraba defaultElement button y ponía type= sobre un div (anida el asa de
resize); grid-row declaraba archetype item y pintó la semana entera como fila de
lista; y un aria por propRef desaparece en SILENCIO si el provider no publica la
fuente. En los tres el arreglo fue la DECLARACIÓN, no el DOM.
Verificado: check 74 = línea base medida con stash, DELTA CERO sostenido en cada
lote · 458 pasan (chronos+morfo+sema) · navegador limpio en las tres vistas de
chronos, con el contrato ARIA vivo por primera vez (aria-labelledby resolviendo
a «junio de 2026», aria-readonly/disabled que no se habían emitido nunca).
Handoff en docs/process/CONTINUE-perceptual-surface.md. Aparte, y decidido por
el autor: docs/process/PLAN-event-name-normalization.md — prefijo de familia
siempre; 36 de 252 sin prefijo, cero ambiguos.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>