# PLAN — un nombre de evento, una convención: `{familia}-{verbo}[-{matiz}]` > **✅ CERRADO 2026-08-12 en `f44d1486d`.** La §6 Aceptación se cumplió entera y > medida: **256 eventos declarados / 256 con prefijo de familia / 0 ambiguos**, > guard en `validateMorfo` **visto fallar** con un nombre pelado inyectado a > propósito, barrido de catálogo sobre los 172 morfos (`components` + `internal` > + fixtures), las cuatro escenas de F4 medidas en navegador, `eidos-lint-all` > sin inválidos nuevos y `docs:check` en 0. > > **Lo que este eje DESTAPÓ no es este eje.** El barrido encendió una luz sobre > catorce defectos que ya estaban ahí, y viven en > [`AUDIT-docs-code-ledger.md`](./AUDIT-docs-code-ledger.md). Ese ledger es un > **artefacto de traspaso, no una lista de trabajo**: se escribió para que una > sesión futura los tome con su propio plan y su propia puerta. Ejecutarlo a > goteo desde aquí fue el error de método del 2026-08-12 — la jornada se cerró a > las 03:52 y siguió trabajando hasta las 20:43 sobre una iniciativa que nunca se > nombró, hasta acabar borrando la librería de sonido (revertido, `0d10b983e`). > > **Handoff**: [`CONTINUE-event-name-normalization.md`](./CONTINUE-event-name-normalization.md). > > **Kickoff (histórico)**: _«Lee `docs/process/PLAN-event-name-normalization.md` y ejecútalo > por fases, empezando por F0.»_ > **Fecha**: 2026-08-10 · Decidido por el autor en la sesión del eje de > superficie perceptual. **Eje independiente**: no bloquea ni lo bloquea > `CONTINUE-perceptual-surface.md`, pero comparte ficheros — no ejecutar los > dos a la vez sobre los mismos componentes. > **Rama**: `alpha-0.1-dir-prefs` es COMPARTIDA (otra sesión trabaja en > `blocks`): `git reset -q` + `git add` sólo lo propio, siempre. --- ## 1 · Por qué El framework llama a la misma cosa de dos maneras. Medido: | Convención | Componentes | | --- | --- | | `open` / `close` pelados | context-menu, dialog, drawer, dropdown-menu, float-panel, popover, menu-dial, onion-menu | | `emerge-open` / `emerge-close` | combobox, navigation-menu, select | Son el mismo evento con dos dialectos. En un framework de este tamaño eso no es una inconsistencia estética: obliga a recordar qué componente habla qué dialecto antes de escribir una regla de pack o una receta, y hace que un `grep` por `emerge-open` mienta por omisión. **La decisión (autor, 2026-08-10): prefijo de familia SIEMPRE.** El nombre pasa a declarar la familia que el motor va a resolver, y deja de ser un dialecto por componente. --- ## 2 · El censo — el trabajo es más pequeño de lo que parece `scratchpad/event-name-census.mjs` (recréalo si hace falta; lee los morfos textualmente, sin importarlos): ``` eventos declarados : 252 (91 morfos) ya con prefijo : 216 ← 86 % ya cumple SIN prefijo : 36 ``` **Cero nombres ambiguos**: ningún nombre significa dos familias distintas en dos morfos. Eso hace que el mapeo sea **1:1 y determinista**, que es lo que permite automatizarlo con seguridad. Compruébalo otra vez antes de empezar: si aparece un ambiguo, el codemod deja de ser seguro y hay que resolverlo a mano primero. ### Los 36, con su renombrado | Familia | Actual → nuevo | Morfos | | --- | --- | --- | | emerge | `open` → `emerge-open` | context-menu, dialog, drawer, dropdown-menu, float-panel, popover | | emerge | `close` → `emerge-close` | los mismos 6 | | emerge | `expand` → `emerge-expand` | accordion, collapsible | | emerge | `collapse` → `emerge-collapse` | accordion, collapsible | | emerge | `present` → `emerge-present` | toast, tooltip | | emerge | `dismiss` → `emerge-dismiss` | toast, tooltip | | emerge | `dismiss-escape` → `emerge-dismiss-escape` | tooltip | | emerge | `sub-open` → `emerge-open-sub` | context-menu, dropdown-menu | | emerge | `sub-close` → `emerge-close-sub` | context-menu, dropdown-menu | | handle | `drag-start` → `handle-drag-start` | drawer, float-panel | | handle | `drag-end` → `handle-drag-end` | drawer, float-panel | | handle | `drag-progress` → `handle-drag-progress` | drawer | | handle | `resize` → `handle-resize` | drawer | | handle | `resize-start` → `handle-resize-start` | float-panel | | handle | `resize-end` → `handle-resize-end` | float-panel | | contact | `trigger-picker` → `contact-trigger-picker` | file-upload | | commit | `select` → `commit-select` | tabs | | signal | `announce` → `signal-announce` | toast | **El matiz va al final** (`emerge-open-sub`), no al principio. Precedente vivo: `commit-toggle-check` / `commit-toggle-uncheck` (checkbox), `commit-select-range` / `commit-select-start` (range-calendar), `commit-set-add` (tags-input). Así el prefijo sigue siendo familia+verbo y el `[data-event^='emerge-open']` de una receta sigue capturando la variante. ⚠️ **`select` → `commit-select` en tabs**: comprobar antes que tabs no tiene ya un `commit-select` (hoy no lo tiene). Es el único renombrado que podría colisionar. --- ## 3 · Los consumidores, y cuáles los caza el compilador Esto es lo que hace el eje viable HOY y no hace seis meses: **F1 tipó los nombres de evento** (`SomaRuntime` sin default, `EventNameOf`). | Superficie | ¿La caza el compilador? | Cómo | | --- | --- | --- | | `morfo/components/*.ts` — la declaración | — | es el origen del cambio | | `soma/**` — `runtime.trigger('name')` | **SÍ** | `trigger` está tipado con `EventNameOf` | | `soma/**` — `events: { name: handler }` | **SÍ** | mapped type sobre `EventNameOf` | | `sema/components/*.ts` — packs | **SÍ** | `semaSelector(morfo, part, { eventName })` está tipado | | `SomaRuntimePart.trigger` anclado | **SÍ** | `EventNameTargeting` | | **`eidos/lib/motion/presets/css.ts`** | **NO** | strings sueltos — ver abajo | | **CSS a mano** (`[data-event=...]`) | **NO** | `scripts/eidos-lint.ts` es la red | | Tests, demos, READMEs | NO | grep | **El único punto ciego real** son los presets de movimiento: `src/uix/eidos/lib/motion/presets/css.ts` declara `event: 'present'`, `event: ['dismiss', 'close']`, `event: 'announce'`, `event: 'expand'`, `event: 'collapse'`… y `signatureSelectorList()` (en `lib/render-css.ts`) los convierte en `[data-event^='...']`. Son **~10 declaraciones** que generan `eidos/generated/base.css`. Detalle que juega a favor: el operador es `^=` (prefijo). Tras el renombrado, `[data-event^='emerge-open']` sigue capturando `emerge-open-sub`. Pero un `[data-event^='open']` sin actualizar deja de casar con NADA — falla en silencio, sin error. **Por eso F4 (medir en navegador) no es opcional en este eje**: un preset olvidado no rompe ninguna prueba, sólo deja de animar. Fuera de ahí: 1 sola regla a mano en `eidos/components/collapsible/collapsible.css` (`[data-event^='expand']`). --- ## 4 · Fases ### F0 · Reconfirmar el censo y congelar el mapa Re-ejecutar el censo (los números de arriba son de 2026-08-10). Escribir el mapa 36→36 en un fichero de datos que el codemod y la verificación compartan, para que no haya dos listas que puedan divergir. Verificar el cero-ambiguos. ### F1 · Renombrar en los morfos Sólo `morfo/components/*.ts`. Al terminar, `npm run check` debe **explotar** con los call sites rotos: **esos errores SON el worklist**, no un problema. Anotar cuántos salen — es la medida de cobertura del compilador. ### F2 · Seguir al compilador hasta cero Soma, sema, `SomaRuntimePart.trigger`. No usar grep aquí: el compilador es exhaustivo y el grep no. Terminar cuando `check` vuelva a su línea base. ⚠️ **Medir la línea base con `git stash`, no de memoria.** En esta sesión el «74» resultó ser 74 posiciones únicas y 87 líneas del output `machine`: comparar por MÉTODO, nunca por cifra. ### F3 · El punto ciego: presets de movimiento + CSS a mano `eidos/lib/motion/presets/css.ts` (~10) + la regla de collapsible. Regenerar `base.css` (`npm run generate:eidos-css`). Pasar `scripts/eidos-lint-all.ts`. ### F4 · Navegador — NO OPCIONAL Un preset olvidado no rompe ninguna prueba. Medir con `MutationObserver` ligado al NODO (no a un selector) + `getAnimations()`, al menos: - **dialog / drawer / popover** — `emerge-open` / `emerge-close` animan. - **accordion / collapsible** — `emerge-expand` / `emerge-collapse` animan. - **toast** — `signal-announce` (5 presets lo usan: es el más expuesto). - **tabs** — `commit-select`. Trampas ya medidas en esta rama, no relearnearlas: - Al exportar algo nuevo de un barrel, el grafo SSR de Vite va por detrás y la página da 500 con `X is not defined` mientras el cliente ya pinta bien. Se arregla navegando otra vez. **No reiniciar el dev server.** - Probar el gesto correcto: en esta sesión probé `Escape` esperando un collapse cuando la tecla era `Backspace`, y un `pointerleave` sintético que la coordinación SafePolygon no dispara. El código estaba bien; la prueba, mal. ### F5 · Docs, demos y guards READMEs de los ~15 componentes tocados, `docs/CANON.md` si nombra alguno, `docs/architecture/sema.md`. Las 68 demos que emiten. `npm run docs:check`. ### F6 · Cerrar la puerta Que esto no pueda volver a pasar: un guard que falle si un `morfo.events[].name` no empieza por su `semantic.family`. Es una prueba de ~10 líneas sobre el censo que ya existe, y convierte la convención en contrato. **Sin esta fase el eje se re-abre solo** — es la lección de los tres audits que encontraron el mismo defecto sin cerrarlo. --- ## 5 · Riesgos 1. **Los presets de movimiento fallan en silencio** (§3). Mitigación: F4. 2. **La rama es compartida.** `git reset -q`, `git add` explícito, verificar con `git diff --cached --name-only`. 3. **`sub-open`/`sub-close` son de hoy mismo** (2026-08-10, sesión del eje perceptual): renombrarlos a `emerge-open-sub` es coherente y barato, pero revisar que su prueba en navegador siga en pie. 4. **No mezclar con el eje perceptual**: F3 de aquel plan aún migra emisiones en los mismos providers. Ejecutar uno, verificar, y luego el otro. ## 6 · Aceptación - Censo: **252 declarados / 252 con prefijo de familia / 0 sin prefijo**. - `check` en la línea base medida con stash · suites server + navegador iguales. - `eidos-lint-all` sin selectores inválidos nuevos · `docs:check` 0. - Las cuatro escenas de F4 medidas y animando. - El guard de F6 en verde, y **visto fallar antes** con un nombre pelado introducido a propósito.