fix(sema,morfo,soma): una superficie no admite dos ocurrencias, y el nombre ya no miente
`data-event-*` es UNA RANURA por elemento. Tres auditorias independientes
encontraron el mismo defecto y ninguna lo cerro: fable S1 (2026-07-01, con el
README diciendo «findings executed»), sema S-17 (2026-08-05, midio el estampado
del knob muriendo a los 24ms de un hold de 240) y blocks A-36/A-65 (reproducido
en navegador, dos estampados a 1,1ms). Lo que faltaba no era diagnostico: nadie
podia distinguir una redireccion legitima de una deriva, porque la opcion que
redirigia se llamaba `fallbackTarget` y hacia lo contrario de lo que decia.
EL NOMBRE PRIMERO. `fallbackTarget` -> `targetOverride` (202 apariciones, 64
ficheros, dirigido con `git ls-files`). Siempre GANO sobre el ref registrado; el
nombre honesto es la precondicion para auditarlo. Las cronicas conservan el viejo
por diseño.
LA PROPIEDAD. `unstampEventAttrs` recibia el signal y lo DESCARTABA, asi que la
ocurrencia que terminaba antes borraba a la que tenia la ranura. Ahora comprueba
`data-event-id`. Con eso, `replace` deja de ser el comportamiento roto y pasa a
ser el suelo correcto.
A-36, SIN `regime`. Los cuatro overlays cedian el estampado del `open` al
trigger por un workaround de la era `sequence: 'pre'`; con `post` el content ya
esta montado. Retirado el override (float-panel migrado `pre`->`post`, ultimo
fuera de la doctrina que sema.md escribe). Medido en /blocks/site-header/preview
a 375px: contact-activate en el TRIGGER +18,7ms y open en el CONTENT +20,6ms,
con press-squeeze y slide-from-right-full corriendo. Antes: los dos en el
trigger, press-squeeze jamas.
`regime` DEFINIDO — y son dos valores, no cuatro. `replace` (default) y `queue`,
este solo para las 4 parejas irreducibles: los tres toggles (el provider ES el
boton) y el knob. `collapse` retirado (su caso murio con la propiedad: medido, 0
huecos en 5 emisiones a 72ms) y `lock` retirado (significaria «un cierre que no
se anuncia»), con sus 4 declaraciones muertas.
El guard de `queue` PASO EN VERDE SIENDO INCORRECTO: con timers falsos no hay
animaciones, asi que awaitExpression volvia al instante. El navegador midio el
commit encolado llegando 1,6s tarde. `queue` espera el HOLD, no la expresion.
Medir la envolvente no es medir la salida.
`renderAttrs` — los attrs que no son partes. Vaciar ACTIVE_DEV_TRACK (palabras y
chronos ya no estan excluidos) destapo 23 `data-palabras-*` que ningun morfo
declaraba y que palabras.css SI estiliza: aterrizan en el arbol que el usuario
escribio, asi que no pueden ser partes. Campo nuevo con la linea afilada —si el
consumidor puede componerlo, es una PARTE— y 3 consumidores (palabras, waveform,
aura). Declarado en types.ts Y en el esquema sium: la leccion de MorfoElement.
Ademas: 5 selectores muertos en el predicado del focus-scope de palabras
(barrido de todo el repo: aparecian solo ahi) · RTL-1 real en palabras-chrome
(inset logico + translate fisico = doble volteo; rtl:check 1->0) · el union
inline al `Direction` canonico, que destapo que el censo de direccion grepea el
NOMBRE del tipo · deuda de chronos (scope, barrel, README).
Y de la cola: S-36 (barrel 68->71 + guard), S-31 (vibrate(NaN)), S5 (la
preferencia de reduced-motion del usuario ya gana a los channels del morfo), SO2
(el warn que el JSDoc prometia), SO4 (validar el target antes del prewrite).
Todos los guards nuevos vistos fallar antes de arreglar nada.
Base: check 74 (= base) · sema+morfo+contracts 454 verdes / 6 ajenos
preexistentes · soma navegador 1258/1259 (el rojo es un timeout ajeno,
reproducido con los cambios en stash) · docs:check 0/618 · rtl:check 0/177.
Handoff: docs/process/CONTINUE-perceptual-surface.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
# CONTINUE — la superficie perceptual es UNA RANURA
docs(process): el handoff cierra la jornada — una sola cadena mecanica queda viva
El §0 pasa a decir donde esta el eje del nombrado al cerrar, que es lo que se
lee manana antes que nada: los cuatro commits pusheados, la unica cadena que
sigue pendiente y es mecanica (`alert-dialog`, excluida durante el barrido
porque la otra sesion editaba el fichero — su D1 ya esta commiteado, asi que
hoy esta libre), y por que las otras diez NO son deuda sino clases medidas
(estado, interpolacion, composicion, supresion sobre prop sin default).
Queda escrita la decision del autor de NO ensanchar `mapRef`, con su razon:
desbloquearia solo una de las tres clases y reintroduce la averia que esta
jornada mato — el guard v2 no recorre los valores del mapa y
`normalizeTranslationRef` tampoco los tocaria, asi que una clave relativa
saldria literal. Y queda escrita la forma correcta por si alguna vez se hace:
valores del mapa como FUENTES, no como cadenas traducidas.
Anotada la deuda menor medida hoy para que nadie la re-descubra: los diez
harnesses cuyo mock ignora su clave, los catorce `langs.ts` sin consumidores
(que NO se borran sin instruccion), los doce avisos de huerfanos. Y la linea
base con la que medirse manana, incluido el matiz de que `check` da 70 con el
WIP ajeno presente y 57 sin el.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
> **Kickoff**: _«Lee `docs/process/CONTINUE-perceptual-surface.md` y sigue por §0.»_
> **Fecha**: 2026-08-10, actualizado 2026-08-11 (cierre) · Rama
> `alpha-0.1-dir-prefs`, pusheada en `61457baac`.
> ⚠️ **COMPARTIDA**: otra sesión trabaja en `blocks` **y** en un eje de
> dismissal. `git reset -q` + stagear por lista explícita, y clasificar **por
> contenido del diff, nunca por nombre** — hoy un fichero de proceso estaba
> mixto (133 líneas, 3 mías).
fix(sema,morfo,soma): una superficie no admite dos ocurrencias, y el nombre ya no miente
`data-event-*` es UNA RANURA por elemento. Tres auditorias independientes
encontraron el mismo defecto y ninguna lo cerro: fable S1 (2026-07-01, con el
README diciendo «findings executed»), sema S-17 (2026-08-05, midio el estampado
del knob muriendo a los 24ms de un hold de 240) y blocks A-36/A-65 (reproducido
en navegador, dos estampados a 1,1ms). Lo que faltaba no era diagnostico: nadie
podia distinguir una redireccion legitima de una deriva, porque la opcion que
redirigia se llamaba `fallbackTarget` y hacia lo contrario de lo que decia.
EL NOMBRE PRIMERO. `fallbackTarget` -> `targetOverride` (202 apariciones, 64
ficheros, dirigido con `git ls-files`). Siempre GANO sobre el ref registrado; el
nombre honesto es la precondicion para auditarlo. Las cronicas conservan el viejo
por diseño.
LA PROPIEDAD. `unstampEventAttrs` recibia el signal y lo DESCARTABA, asi que la
ocurrencia que terminaba antes borraba a la que tenia la ranura. Ahora comprueba
`data-event-id`. Con eso, `replace` deja de ser el comportamiento roto y pasa a
ser el suelo correcto.
A-36, SIN `regime`. Los cuatro overlays cedian el estampado del `open` al
trigger por un workaround de la era `sequence: 'pre'`; con `post` el content ya
esta montado. Retirado el override (float-panel migrado `pre`->`post`, ultimo
fuera de la doctrina que sema.md escribe). Medido en /blocks/site-header/preview
a 375px: contact-activate en el TRIGGER +18,7ms y open en el CONTENT +20,6ms,
con press-squeeze y slide-from-right-full corriendo. Antes: los dos en el
trigger, press-squeeze jamas.
`regime` DEFINIDO — y son dos valores, no cuatro. `replace` (default) y `queue`,
este solo para las 4 parejas irreducibles: los tres toggles (el provider ES el
boton) y el knob. `collapse` retirado (su caso murio con la propiedad: medido, 0
huecos en 5 emisiones a 72ms) y `lock` retirado (significaria «un cierre que no
se anuncia»), con sus 4 declaraciones muertas.
El guard de `queue` PASO EN VERDE SIENDO INCORRECTO: con timers falsos no hay
animaciones, asi que awaitExpression volvia al instante. El navegador midio el
commit encolado llegando 1,6s tarde. `queue` espera el HOLD, no la expresion.
Medir la envolvente no es medir la salida.
`renderAttrs` — los attrs que no son partes. Vaciar ACTIVE_DEV_TRACK (palabras y
chronos ya no estan excluidos) destapo 23 `data-palabras-*` que ningun morfo
declaraba y que palabras.css SI estiliza: aterrizan en el arbol que el usuario
escribio, asi que no pueden ser partes. Campo nuevo con la linea afilada —si el
consumidor puede componerlo, es una PARTE— y 3 consumidores (palabras, waveform,
aura). Declarado en types.ts Y en el esquema sium: la leccion de MorfoElement.
Ademas: 5 selectores muertos en el predicado del focus-scope de palabras
(barrido de todo el repo: aparecian solo ahi) · RTL-1 real en palabras-chrome
(inset logico + translate fisico = doble volteo; rtl:check 1->0) · el union
inline al `Direction` canonico, que destapo que el censo de direccion grepea el
NOMBRE del tipo · deuda de chronos (scope, barrel, README).
Y de la cola: S-36 (barrel 68->71 + guard), S-31 (vibrate(NaN)), S5 (la
preferencia de reduced-motion del usuario ya gana a los channels del morfo), SO2
(el warn que el JSDoc prometia), SO4 (validar el target antes del prewrite).
Todos los guards nuevos vistos fallar antes de arreglar nada.
Base: check 74 (= base) · sema+morfo+contracts 454 verdes / 6 ajenos
preexistentes · soma navegador 1258/1259 (el rojo es un timeout ajeno,
reproducido con los cambios en stash) · docs:check 0/618 · rtl:check 0/177.
Handoff: docs/process/CONTINUE-perceptual-surface.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
> **Doctrina viva**: [`architecture/sema.md` §The surface is ONE SLOT](../architecture/sema.md)
docs(process): el handoff cierra la jornada — una sola cadena mecanica queda viva
El §0 pasa a decir donde esta el eje del nombrado al cerrar, que es lo que se
lee manana antes que nada: los cuatro commits pusheados, la unica cadena que
sigue pendiente y es mecanica (`alert-dialog`, excluida durante el barrido
porque la otra sesion editaba el fichero — su D1 ya esta commiteado, asi que
hoy esta libre), y por que las otras diez NO son deuda sino clases medidas
(estado, interpolacion, composicion, supresion sobre prop sin default).
Queda escrita la decision del autor de NO ensanchar `mapRef`, con su razon:
desbloquearia solo una de las tres clases y reintroduce la averia que esta
jornada mato — el guard v2 no recorre los valores del mapa y
`normalizeTranslationRef` tampoco los tocaria, asi que una clave relativa
saldria literal. Y queda escrita la forma correcta por si alguna vez se hace:
valores del mapa como FUENTES, no como cadenas traducidas.
Anotada la deuda menor medida hoy para que nadie la re-descubra: los diez
harnesses cuyo mock ignora su clave, los catorce `langs.ts` sin consumidores
(que NO se borran sin instruccion), los doce avisos de huerfanos. Y la linea
base con la que medirse manana, incluido el matiz de que `check` da 70 con el
WIP ajeno presente y 57 sin el.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
> · [`architecture/morfo.md` §Step 4](../architecture/morfo.md) (las DOS clases
> de precedencia de attrs: contract vs naming) · [`§renderAttrs`](../architecture/morfo.md).
fix(sema,morfo,soma): una superficie no admite dos ocurrencias, y el nombre ya no miente
`data-event-*` es UNA RANURA por elemento. Tres auditorias independientes
encontraron el mismo defecto y ninguna lo cerro: fable S1 (2026-07-01, con el
README diciendo «findings executed»), sema S-17 (2026-08-05, midio el estampado
del knob muriendo a los 24ms de un hold de 240) y blocks A-36/A-65 (reproducido
en navegador, dos estampados a 1,1ms). Lo que faltaba no era diagnostico: nadie
podia distinguir una redireccion legitima de una deriva, porque la opcion que
redirigia se llamaba `fallbackTarget` y hacia lo contrario de lo que decia.
EL NOMBRE PRIMERO. `fallbackTarget` -> `targetOverride` (202 apariciones, 64
ficheros, dirigido con `git ls-files`). Siempre GANO sobre el ref registrado; el
nombre honesto es la precondicion para auditarlo. Las cronicas conservan el viejo
por diseño.
LA PROPIEDAD. `unstampEventAttrs` recibia el signal y lo DESCARTABA, asi que la
ocurrencia que terminaba antes borraba a la que tenia la ranura. Ahora comprueba
`data-event-id`. Con eso, `replace` deja de ser el comportamiento roto y pasa a
ser el suelo correcto.
A-36, SIN `regime`. Los cuatro overlays cedian el estampado del `open` al
trigger por un workaround de la era `sequence: 'pre'`; con `post` el content ya
esta montado. Retirado el override (float-panel migrado `pre`->`post`, ultimo
fuera de la doctrina que sema.md escribe). Medido en /blocks/site-header/preview
a 375px: contact-activate en el TRIGGER +18,7ms y open en el CONTENT +20,6ms,
con press-squeeze y slide-from-right-full corriendo. Antes: los dos en el
trigger, press-squeeze jamas.
`regime` DEFINIDO — y son dos valores, no cuatro. `replace` (default) y `queue`,
este solo para las 4 parejas irreducibles: los tres toggles (el provider ES el
boton) y el knob. `collapse` retirado (su caso murio con la propiedad: medido, 0
huecos en 5 emisiones a 72ms) y `lock` retirado (significaria «un cierre que no
se anuncia»), con sus 4 declaraciones muertas.
El guard de `queue` PASO EN VERDE SIENDO INCORRECTO: con timers falsos no hay
animaciones, asi que awaitExpression volvia al instante. El navegador midio el
commit encolado llegando 1,6s tarde. `queue` espera el HOLD, no la expresion.
Medir la envolvente no es medir la salida.
`renderAttrs` — los attrs que no son partes. Vaciar ACTIVE_DEV_TRACK (palabras y
chronos ya no estan excluidos) destapo 23 `data-palabras-*` que ningun morfo
declaraba y que palabras.css SI estiliza: aterrizan en el arbol que el usuario
escribio, asi que no pueden ser partes. Campo nuevo con la linea afilada —si el
consumidor puede componerlo, es una PARTE— y 3 consumidores (palabras, waveform,
aura). Declarado en types.ts Y en el esquema sium: la leccion de MorfoElement.
Ademas: 5 selectores muertos en el predicado del focus-scope de palabras
(barrido de todo el repo: aparecian solo ahi) · RTL-1 real en palabras-chrome
(inset logico + translate fisico = doble volteo; rtl:check 1->0) · el union
inline al `Direction` canonico, que destapo que el censo de direccion grepea el
NOMBRE del tipo · deuda de chronos (scope, barrel, README).
Y de la cola: S-36 (barrel 68->71 + guard), S-31 (vibrate(NaN)), S5 (la
preferencia de reduced-motion del usuario ya gana a los channels del morfo), SO2
(el warn que el JSDoc prometia), SO4 (validar el target antes del prewrite).
Todos los guards nuevos vistos fallar antes de arreglar nada.
Base: check 74 (= base) · sema+morfo+contracts 454 verdes / 6 ajenos
preexistentes · soma navegador 1258/1259 (el rojo es un timeout ajeno,
reproducido con los cambios en stash) · docs:check 0/618 · rtl:check 0/177.
Handoff: docs/process/CONTINUE-perceptual-surface.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
> Este documento sólo dice **qué se cerró, qué queda y cómo medirlo**.
docs(process): el handoff cierra la jornada — una sola cadena mecanica queda viva
El §0 pasa a decir donde esta el eje del nombrado al cerrar, que es lo que se
lee manana antes que nada: los cuatro commits pusheados, la unica cadena que
sigue pendiente y es mecanica (`alert-dialog`, excluida durante el barrido
porque la otra sesion editaba el fichero — su D1 ya esta commiteado, asi que
hoy esta libre), y por que las otras diez NO son deuda sino clases medidas
(estado, interpolacion, composicion, supresion sobre prop sin default).
Queda escrita la decision del autor de NO ensanchar `mapRef`, con su razon:
desbloquearia solo una de las tres clases y reintroduce la averia que esta
jornada mato — el guard v2 no recorre los valores del mapa y
`normalizeTranslationRef` tampoco los tocaria, asi que una clave relativa
saldria literal. Y queda escrita la forma correcta por si alguna vez se hace:
valores del mapa como FUENTES, no como cadenas traducidas.
Anotada la deuda menor medida hoy para que nadie la re-descubra: los diez
harnesses cuyo mock ignora su clave, los catorce `langs.ts` sin consumidores
(que NO se borran sin instruccion), los doce avisos de huerfanos. Y la linea
base con la que medirse manana, incluido el matiz de que `check` da 70 con el
WIP ajeno presente y 57 sin el.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
## 0 · Dónde estás (2026-08-11, cierre de jornada)
docs(morfo,chronos): la doctrina alcanza a lo que se construyo hoy
`morfo.md` gana la trampa que se llevo cinco etiquetas: **`texts` es una
DECLARACION, nunca una tabla de consulta**. `normalizeTranslationRef` toma la
clave VERBATIM y no traduce un alias a una ruta, asi que `prevMonth` frente al
`prev-month` del catalogo resuelve a una ruta que no existe y **envia el
fallback ingles sin error ni aviso**. Igual con `commonRef('action.undo')`
cuando `common.action` no existe. Y el aviso que faltaba: el guard que deberia
cazarlo, `npm run translations:check`, **estaba cascando** antes de comprobar
nada — arreglarlo y censar los 166 morfos antes de volver a fiarse.
README de soma/chronos: `SearchButton` faltaba en la tabla de partes, y ahora
documenta los dos permisos de manipulacion directa (`canMove` / `canResize` —
separados a proposito aunque hoy coincidan, porque el handler de teclado
preguntaba el equivocado) y el estado `searchOpen`.
README de eidos/chronos: seccion **Composicion** nueva (la ranura `children`,
que las partes publicas las decide `kind` en el morfo, que son ergonomicas y no
ganchos vacios, y que los overlays los pone la RAIZ). La coletilla «sin API
compound publica» queda marcada como ANULADA en su gap. Busqueda y undo/redo
pasan de hueco a hecho; la recurrencia anota que la manipulacion directa se
bloquea de forma visible y que falta PINTARLO.
`CONTINUE.md` de chronos se marca como NO vigente y apunta al handoff del eje.
El handoff gana una seccion §0 «donde estas» con los seis commits y el orden de
lo siguiente, y §4 recoge las dos decisiones de autor medidas y listas.
docs:check 0/621 · rtl:check 0/177. Los .md tocados que prettier marca ya
estaban asi en HEAD salvo el README de eidos, cuyo contenido SI pasa (el aviso
es solo CRLF del working tree, que git normaliza).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
docs(process): el handoff cierra la jornada — una sola cadena mecanica queda viva
El §0 pasa a decir donde esta el eje del nombrado al cerrar, que es lo que se
lee manana antes que nada: los cuatro commits pusheados, la unica cadena que
sigue pendiente y es mecanica (`alert-dialog`, excluida durante el barrido
porque la otra sesion editaba el fichero — su D1 ya esta commiteado, asi que
hoy esta libre), y por que las otras diez NO son deuda sino clases medidas
(estado, interpolacion, composicion, supresion sobre prop sin default).
Queda escrita la decision del autor de NO ensanchar `mapRef`, con su razon:
desbloquearia solo una de las tres clases y reintroduce la averia que esta
jornada mato — el guard v2 no recorre los valores del mapa y
`normalizeTranslationRef` tampoco los tocaria, asi que una clave relativa
saldria literal. Y queda escrita la forma correcta por si alguna vez se hace:
valores del mapa como FUENTES, no como cadenas traducidas.
Anotada la deuda menor medida hoy para que nadie la re-descubra: los diez
harnesses cuyo mock ignora su clave, los catorce `langs.ts` sin consumidores
(que NO se borran sin instruccion), los doce avisos de huerfanos. Y la linea
base con la que medirse manana, incluido el matiz de que `check` da 70 con el
WIP ajeno presente y 57 sin el.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**PUSHEADO a `gita/alpha-0.1-dir-prefs` en `61457baac` ** — la rama está limpia
contra el remoto (0 por delante). Cuatro commits de la jornada del nombrado:
docs(morfo,chronos): la doctrina alcanza a lo que se construyo hoy
`morfo.md` gana la trampa que se llevo cinco etiquetas: **`texts` es una
DECLARACION, nunca una tabla de consulta**. `normalizeTranslationRef` toma la
clave VERBATIM y no traduce un alias a una ruta, asi que `prevMonth` frente al
`prev-month` del catalogo resuelve a una ruta que no existe y **envia el
fallback ingles sin error ni aviso**. Igual con `commonRef('action.undo')`
cuando `common.action` no existe. Y el aviso que faltaba: el guard que deberia
cazarlo, `npm run translations:check`, **estaba cascando** antes de comprobar
nada — arreglarlo y censar los 166 morfos antes de volver a fiarse.
README de soma/chronos: `SearchButton` faltaba en la tabla de partes, y ahora
documenta los dos permisos de manipulacion directa (`canMove` / `canResize` —
separados a proposito aunque hoy coincidan, porque el handler de teclado
preguntaba el equivocado) y el estado `searchOpen`.
README de eidos/chronos: seccion **Composicion** nueva (la ranura `children`,
que las partes publicas las decide `kind` en el morfo, que son ergonomicas y no
ganchos vacios, y que los overlays los pone la RAIZ). La coletilla «sin API
compound publica» queda marcada como ANULADA en su gap. Busqueda y undo/redo
pasan de hueco a hecho; la recurrencia anota que la manipulacion directa se
bloquea de forma visible y que falta PINTARLO.
`CONTINUE.md` de chronos se marca como NO vigente y apunta al handoff del eje.
El handoff gana una seccion §0 «donde estas» con los seis commits y el orden de
lo siguiente, y §4 recoge las dos decisiones de autor medidas y listas.
docs:check 0/621 · rtl:check 0/177. Los .md tocados que prettier marca ya
estaban asi en HEAD salvo el README de eidos, cuyo contenido SI pasa (el aviso
es solo CRLF del working tree, que git normaliza).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| Commit | Qué |
| --- | --- |
docs(process): el handoff cierra la jornada — una sola cadena mecanica queda viva
El §0 pasa a decir donde esta el eje del nombrado al cerrar, que es lo que se
lee manana antes que nada: los cuatro commits pusheados, la unica cadena que
sigue pendiente y es mecanica (`alert-dialog`, excluida durante el barrido
porque la otra sesion editaba el fichero — su D1 ya esta commiteado, asi que
hoy esta libre), y por que las otras diez NO son deuda sino clases medidas
(estado, interpolacion, composicion, supresion sobre prop sin default).
Queda escrita la decision del autor de NO ensanchar `mapRef`, con su razon:
desbloquearia solo una de las tres clases y reintroduce la averia que esta
jornada mato — el guard v2 no recorre los valores del mapa y
`normalizeTranslationRef` tampoco los tocaria, asi que una clave relativa
saldria literal. Y queda escrita la forma correcta por si alguna vez se hace:
valores del mapa como FUENTES, no como cadenas traducidas.
Anotada la deuda menor medida hoy para que nadie la re-descubra: los diez
harnesses cuyo mock ignora su clave, los catorce `langs.ts` sin consumidores
(que NO se borran sin instruccion), los doce avisos de huerfanos. Y la linea
base con la que medirse manana, incluido el matiz de que `check` da 70 con el
WIP ajeno presente y 57 sin el.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| `37c732c3a` | el guard de traducciones deja de cascar **y de ser ciego** a los call sites |
| `25e59e18f` | A-85: el attr de nombrado es un DEFAULT del contrato, no una imposición |
| `b201f190d` | la consecuencia: espejos, button, las 21 cadenas, cross-element, gemelos |
| `61457baac` | el handoff dice lo que pasó (dos diagnósticos heredados eran falsos) |
⚠️ **Rama compartida, y hoy se notó** : otra sesión trabaja en `blocks` **y** en
un eje de dismissal (los `types.ts` de color-picker / time-picker /
time-range-picker). Clasificar **por contenido del diff** , nunca por nombre:
`AUDIT-blocks-ledger.md` estaba MIXTO (133 líneas, 3 mías) y quedó entero
fuera. **Mis filas de A-85 en ese ledger siguen SIN COMMITEAR** — cuando esa
sesión lo commitee, irán en su commit; no hay que rehacerlas.
### El eje del nombrado: qué queda, y por qué
refactor(morfo,soma): alert-dialog cierra el eje del nombrado — el default es del CONTRATO, cero cadenas mecanicas
La ultima cadena clase 1 del catalogo, excluida del barrido de los 21 porque
otra sesion editaba el fichero. Receta identica (morfo.md Step 4): el morfo
declara #?components.alert-dialog.action|Confirm y .cancel|Cancel; fuera la
cadena resolvedAriaLabel x2, la fuente del runtime, el prop de opts y el
destructure de los dos wrappers — el aria-label del consumidor fluye por
restProps y gana por politica de merge. ALERT_DIALOG_LANGS queda huerfano
(su langs.ts se suma a la lista declarada, 14 → 15; no se borra).
El harness del test se alinea con produccion de paso: translate = ts (un solo
traductor para las dos puertas — la trampa del harness de editable, donde la
identidad dejaba pasar el ref crudo por la cadena resuelta).
Medido: navegador en es → aria-label="Confirmar" / "Cancelar" saliendo del
contrato, type="button" intacto · translations:check 159 refs compilados
(157+2), 0 errores · alert-dialog 2/2 · check 69 = base, 0 propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
~~Sólo **UNA** cadena pendiente es mecánica: ** `alert-dialog` **~~ **HECHA
2026-08-12** — la receta de los 21 aplicada tal cual (`morfo.md` Step 4):
el morfo declara `#?components.alert-dialog.action|Confirm` y `.cancel|Cancel` ,
fuera la cadena, la fuente, el prop de opts y el destructure de los dos
wrappers; `ALERT_DIALOG_LANGS` queda huérfano (su `langs.ts` se suma a la lista
de abajo). Medido en navegador (`es`): `aria-label="Confirmar"` /
`"Cancelar"` saliendo del contrato. Refs compilados 157 → **159** ; el harness
del test alineado con producción (`translate` = `ts` , la trampa de editable).
**El eje del nombrado queda a CERO cadenas mecánicas.**
docs(process): el handoff cierra la jornada — una sola cadena mecanica queda viva
El §0 pasa a decir donde esta el eje del nombrado al cerrar, que es lo que se
lee manana antes que nada: los cuatro commits pusheados, la unica cadena que
sigue pendiente y es mecanica (`alert-dialog`, excluida durante el barrido
porque la otra sesion editaba el fichero — su D1 ya esta commiteado, asi que
hoy esta libre), y por que las otras diez NO son deuda sino clases medidas
(estado, interpolacion, composicion, supresion sobre prop sin default).
Queda escrita la decision del autor de NO ensanchar `mapRef`, con su razon:
desbloquearia solo una de las tres clases y reintroduce la averia que esta
jornada mato — el guard v2 no recorre los valores del mapa y
`normalizeTranslationRef` tampoco los tocaria, asi que una clave relativa
saldria literal. Y queda escrita la forma correcta por si alguna vez se hace:
valores del mapa como FUENTES, no como cadenas traducidas.
Anotada la deuda menor medida hoy para que nadie la re-descubra: los diez
harnesses cuyo mock ignora su clave, los catorce `langs.ts` sin consumidores
(que NO se borran sin instruccion), los doce avisos de huerfanos. Y la linea
base con la que medirse manana, incluido el matiz de que `check` da 70 con el
WIP ajeno presente y 57 sin el.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Las otras 10 cadenas **no son deuda** — están medidas y clasificadas:
`clipboard` · `password-field` (toggle) · `table` (row-detail) · `tree-grid`
(expand) están BLOQUEADAS por estado; `tag-group` (remove) por interpolación;
`waveform` es correcta (su nombre lo lleva un `<Slider>` compuesto);
`progress` · `radio-group` · `rating-group` · `slider` conservan sólo la
supresión por labelledby sobre un prop sin default.
> **DECISIÓN TOMADA (2026-08-11): NO se ensancha `mapRef`.** Traducir su salida
> desbloquearía sólo la clase ESTADO —ni las interpoladas ni las
> cross-element— y reintroduce la avería que esta jornada mató: el guard v2
> **no recorre los valores del mapa**, y `normalizeTranslationRef` tampoco los
> tocaría, así que una clave relativa llegaría cruda a `ts()` y saldría literal.
> Si algún día se hace, la forma correcta NO es traducir la salida sino que los
> valores del mapa sean **fuentes** (composicional, sin magia de prefijo, y el
> guard los ve por la vía que ya tiene).
docs(morfo,chronos): la doctrina alcanza a lo que se construyo hoy
`morfo.md` gana la trampa que se llevo cinco etiquetas: **`texts` es una
DECLARACION, nunca una tabla de consulta**. `normalizeTranslationRef` toma la
clave VERBATIM y no traduce un alias a una ruta, asi que `prevMonth` frente al
`prev-month` del catalogo resuelve a una ruta que no existe y **envia el
fallback ingles sin error ni aviso**. Igual con `commonRef('action.undo')`
cuando `common.action` no existe. Y el aviso que faltaba: el guard que deberia
cazarlo, `npm run translations:check`, **estaba cascando** antes de comprobar
nada — arreglarlo y censar los 166 morfos antes de volver a fiarse.
README de soma/chronos: `SearchButton` faltaba en la tabla de partes, y ahora
documenta los dos permisos de manipulacion directa (`canMove` / `canResize` —
separados a proposito aunque hoy coincidan, porque el handler de teclado
preguntaba el equivocado) y el estado `searchOpen`.
README de eidos/chronos: seccion **Composicion** nueva (la ranura `children`,
que las partes publicas las decide `kind` en el morfo, que son ergonomicas y no
ganchos vacios, y que los overlays los pone la RAIZ). La coletilla «sin API
compound publica» queda marcada como ANULADA en su gap. Busqueda y undo/redo
pasan de hueco a hecho; la recurrencia anota que la manipulacion directa se
bloquea de forma visible y que falta PINTARLO.
`CONTINUE.md` de chronos se marca como NO vigente y apunta al handoff del eje.
El handoff gana una seccion §0 «donde estas» con los seis commits y el orden de
lo siguiente, y §4 recoge las dos decisiones de autor medidas y listas.
docs:check 0/621 · rtl:check 0/177. Los .md tocados que prettier marca ya
estaban asi en HEAD salvo el README de eidos, cuyo contenido SI pasa (el aviso
es solo CRLF del working tree, que git normaliza).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Lo siguiente, en orden:**
refactor(morfo,soma): alert-dialog cierra el eje del nombrado — el default es del CONTRATO, cero cadenas mecanicas
La ultima cadena clase 1 del catalogo, excluida del barrido de los 21 porque
otra sesion editaba el fichero. Receta identica (morfo.md Step 4): el morfo
declara #?components.alert-dialog.action|Confirm y .cancel|Cancel; fuera la
cadena resolvedAriaLabel x2, la fuente del runtime, el prop de opts y el
destructure de los dos wrappers — el aria-label del consumidor fluye por
restProps y gana por politica de merge. ALERT_DIALOG_LANGS queda huerfano
(su langs.ts se suma a la lista declarada, 14 → 15; no se borra).
El harness del test se alinea con produccion de paso: translate = ts (un solo
traductor para las dos puertas — la trampa del harness de editable, donde la
identidad dejaba pasar el ref crudo por la cadena resuelta).
Medido: navegador en es → aria-label="Confirmar" / "Cancelar" saliendo del
contrato, type="button" intacto · translations:check 159 refs compilados
(157+2), 0 errores · alert-dialog 2/2 · check 69 = base, 0 propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
1. ~~**`alert-dialog`** — la única cadena mecánica que queda (30 min).~~ **HECHA 2026-08-12.**
docs(process): la 6a fila de chronos no era un defecto — decision revocada por el SPEC
El item llevaba un mes en la cola como «la 6a fila fantasma» y estaba FIRMADO
para arreglarse. Al ir a ejecutarlo aparecio el SPEC del propio componente, que
dice lo contrario en TRES sitios coordinados: rejilla de 6 filas constante
«estabiliza la altura» (SPEC.md:270), inventario de reuso pidiendo «rejilla de
mes 6x7» (:492), y `data-outside-month` como estado declarado de `day-cell` CON
su propio token de tema (:311 y :461). Las celdas de fuera de mes son diseno,
no relleno.
La asimetria con la familia —chronos `true`, los otros cuatro `false`— se leyo
como deriva y es al reves: chronos es el unico que escribio su decision, y
coincide con el default de sus dos referentes de libreria, verificados en la
fuente y no citados de memoria: FullCalendar `fixedWeekCount` viene `true` («the
calendar will always be 6 weeks tall») y Toast UI `month.isAlways6Weeks` viene
`true`. Google/Apple/Outlook si varian las filas, pero porque son aplicaciones
que llenan el viewport; chronos no tiene contrato de altura, asi que la receta
propuesta tenia que inventarse la altura con `calc(6 * cell-min-block)` — la
altura de 6 filas. Era la misma altura con 5 filas mas gordas, y menos
informacion.
Medido antes de opinar: hoy son 115 px por fila y 724 px de rejilla en todos los
meses; con `fixedWeeks:false`, jul→dic 2026 da 609/724/609/609/724/609, o sea el
salto de 115 px que la decision queria evitar. Y crecer las filas no ensenaria
ni un evento mas: `maxLanes` es `$state(3)` fijo.
Corregidos ademas dos errores de la ficha original: el default vive en DOS
sitios (chronos.svelte:20 y engine/state.svelte.ts:64), y chronos.css:278 no es
la altura de las filas del mes sino la pista de carriles de chips dentro de una
fila. La conducta de Google queda anotada como FUNCIONALIDAD con firma propia
(contrato de altura + maxLanes derivado), no como flip de flag.
docs:check 0/623.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
2. ~~**Dos decisiones del autor** (§4)~~ **FIRMADAS 2026-08-12** : el chip
bloqueado (vía b + su letra pequeña) y `mode` /`scope` (retirar). La tercera,
`fixedWeeks` , quedó **REVOCADA el 2026-08-13** al aparecer el SPEC del
componente — las disposiciones completas viven en §4.
docs(process): el handoff cierra la jornada — una sola cadena mecanica queda viva
El §0 pasa a decir donde esta el eje del nombrado al cerrar, que es lo que se
lee manana antes que nada: los cuatro commits pusheados, la unica cadena que
sigue pendiente y es mecanica (`alert-dialog`, excluida durante el barrido
porque la otra sesion editaba el fichero — su D1 ya esta commiteado, asi que
hoy esta libre), y por que las otras diez NO son deuda sino clases medidas
(estado, interpolacion, composicion, supresion sobre prop sin default).
Queda escrita la decision del autor de NO ensanchar `mapRef`, con su razon:
desbloquearia solo una de las tres clases y reintroduce la averia que esta
jornada mato — el guard v2 no recorre los valores del mapa y
`normalizeTranslationRef` tampoco los tocaria, asi que una clave relativa
saldria literal. Y queda escrita la forma correcta por si alguna vez se hace:
valores del mapa como FUENTES, no como cadenas traducidas.
Anotada la deuda menor medida hoy para que nadie la re-descubra: los diez
harnesses cuyo mock ignora su clave, los catorce `langs.ts` sin consumidores
(que NO se borran sin instruccion), los doce avisos de huerfanos. Y la linea
base con la que medirse manana, incluido el matiz de que `check` da 70 con el
WIP ajeno presente y 57 sin el.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
3. **C5 rebanada 3** , y empieza por **subir a soma el comportamiento del chip**
docs(morfo,chronos): la doctrina alcanza a lo que se construyo hoy
`morfo.md` gana la trampa que se llevo cinco etiquetas: **`texts` es una
DECLARACION, nunca una tabla de consulta**. `normalizeTranslationRef` toma la
clave VERBATIM y no traduce un alias a una ruta, asi que `prevMonth` frente al
`prev-month` del catalogo resuelve a una ruta que no existe y **envia el
fallback ingles sin error ni aviso**. Igual con `commonRef('action.undo')`
cuando `common.action` no existe. Y el aviso que faltaba: el guard que deberia
cazarlo, `npm run translations:check`, **estaba cascando** antes de comprobar
nada — arreglarlo y censar los 166 morfos antes de volver a fiarse.
README de soma/chronos: `SearchButton` faltaba en la tabla de partes, y ahora
documenta los dos permisos de manipulacion directa (`canMove` / `canResize` —
separados a proposito aunque hoy coincidan, porque el handler de teclado
preguntaba el equivocado) y el estado `searchOpen`.
README de eidos/chronos: seccion **Composicion** nueva (la ranura `children`,
que las partes publicas las decide `kind` en el morfo, que son ergonomicas y no
ganchos vacios, y que los overlays los pone la RAIZ). La coletilla «sin API
compound publica» queda marcada como ANULADA en su gap. Busqueda y undo/redo
pasan de hueco a hecho; la recurrencia anota que la manipulacion directa se
bloquea de forma visible y que falta PINTARLO.
`CONTINUE.md` de chronos se marca como NO vigente y apunta al handoff del eje.
El handoff gana una seccion §0 «donde estas» con los seis commits y el orden de
lo siguiente, y §4 recoge las dos decisiones de autor medidas y listas.
docs:check 0/621 · rtl:check 0/177. Los .md tocados que prettier marca ya
estaban asi en HEAD salvo el README de eidos, cuyo contenido SI pasa (el aviso
es solo CRLF del working tree, que git normaliza).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
— la tabla de §C5-r3 explica por qué no es mecánica.
docs(process): el handoff cierra la jornada — una sola cadena mecanica queda viva
El §0 pasa a decir donde esta el eje del nombrado al cerrar, que es lo que se
lee manana antes que nada: los cuatro commits pusheados, la unica cadena que
sigue pendiente y es mecanica (`alert-dialog`, excluida durante el barrido
porque la otra sesion editaba el fichero — su D1 ya esta commiteado, asi que
hoy esta libre), y por que las otras diez NO son deuda sino clases medidas
(estado, interpolacion, composicion, supresion sobre prop sin default).
Queda escrita la decision del autor de NO ensanchar `mapRef`, con su razon:
desbloquearia solo una de las tres clases y reintroduce la averia que esta
jornada mato — el guard v2 no recorre los valores del mapa y
`normalizeTranslationRef` tampoco los tocaria, asi que una clave relativa
saldria literal. Y queda escrita la forma correcta por si alguna vez se hace:
valores del mapa como FUENTES, no como cadenas traducidas.
Anotada la deuda menor medida hoy para que nadie la re-descubra: los diez
harnesses cuyo mock ignora su clave, los catorce `langs.ts` sin consumidores
(que NO se borran sin instruccion), los doce avisos de huerfanos. Y la linea
base con la que medirse manana, incluido el matiz de que `check` da 70 con el
WIP ajeno presente y 57 sin el.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
4. ~~**Arreglar `npm run translations:check`** (casca) y censar los 166 morfos.~~
docs(process): el handoff dice lo que paso, incluido donde el diagnostico viejo mentia
Dos diagnosticos heredados eran falsos y mandaban al sitio equivocado, asi que
se corrigen en su sitio en vez de anadir una nota al final: el crash del guard
de traducciones no era una url multilinea de un catalogo (era el stripper de
comentarios rompiendo un https:// dentro de una cadena) y el guard, ademas,
nunca escaneo los call sites que se le atribuian.
Dos secciones daban ordenes OPUESTAS a §3.0 y llevaban un dia haciendolo: el
hallazgo «chronos estampa fuera del morfo» pedia una decision que C1 ya habia
tomado el mismo dia, y «la cola de la campana» listaba como pendientes nueve
componentes que las olas 2-3 habian cerrado. Marcadas resuelta y congelada, con
los cerrados tachados y remision a la cola viva.
Y se escribe lo aprendido en el eje del nombrado: las cinco clases de cadena,
las cuatro razones por las que un default NO puede subir al contrato (estado,
interpolacion, cross-element, supresion por `<label for>`), la trampa del
spread condicional, la del harness cuyo `translate` diverge de `langs.ts`, y la
regla que resume la clase — antes de mover un default, mira si el elemento es
tuyo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**HECHO 2026-08-11** — y el diagnóstico era doblemente falso: el crash no era
una «url multilínea» sino el stripper de comentarios comiéndose el `//` de un
`https://` DENTRO de una cadena; y el guard, incluso sano, **jamás escaneó los
call sites** (`v.translationRef('key')` no contiene literal `#?` hasta que el
compilador lo normaliza) — los cinco defectos de chronos habrían pasado en
verde. Reescrito sobre el contrato COMPILADO (importa morfos + `compileMorfo` ,
valida cada ref normalizada; catálogos importados, no regex-evaluados; fuentes
`.ts` por AST de literales). Sweep: **172 errores → 0** (6 morfos sin catálogo ·
media-player 6 claves desfasadas · ~130 del chrome de palabras, con dos
colisiones hoja↔rama `status` /`placeholder` resueltas a `status.label` /
`placeholder.default` y descripciones slash en clave plana `-d` ). Detalle:
caja ⚠️ de `morfo.md` §common-mistakes.
docs(process): F5 cerrada — los tres sitios de las tres auditorias, medidos DESPUES de la campana
F5 no era «volver a medir cada ola»: es la comprobacion de regresion del eje
entero sobre los tres sitios que fable S1, sema S-17 y blocks A-36/A-65
encontraron por separado — y ahora con la API de emision ya borrada.
site-header 375px contact-activate TRIGGER +8,4ms · emerge-open CONTENT +10,3ms,
press-squeeze corriendo SOBRE el trigger, close en el content
knob handle-drop y commit-set separados por 245,4 ms
dropdown+Button +109 / +112,7 ms, close en content, commit-select en el item
PULSADO («Log out», 3.º de 3)
El numero que cierra S-17 es 245,4: el commit encolado entra justo tras el hold
de 240 del drop — ni aplastado a los 24 ms que S-17 midio, ni 1,6 s tarde como
midio el falso verde de `queue`. Y los tres sitios son la misma geometria: un
elemento con DOS morfos (trigger+button, control para drop+commit).
Anotados los dos artefactos del entorno para que no vuelvan como hallazgos: con
el panel sin componer, rAF esta suspendido (el handle-drag continuo del knob no
estampa nunca) y la linea de tiempo de las animaciones no avanza (el
desestampado cae SIEMPRE en el tope de 1500 ms de la espera de expresion, de ahi
los ~1,7 s de limpieza en las tres trazas). getAnimations() dice que corre sobre
que; para duracion de pintura hace falta Chrome real.
docs:check 0/623.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
5. El resto del eje: ~~**F3**~~ **CERRADA 2026-08-13** (censo a 0 y API borrada)
· ~~**F5** navegador~~ **CERRADA 2026-08-13** (los tres sitios medidos tras la
campaña; los números y los dos artefactos del entorno, en §3.0 F5) · §3.2
restitución de persistentes · §3.3 eje B · §3.4 cola de sema · §3.5 fable ·
§3.6 ledger de blocks (`A-47`, `A-69` ).
docs(process): el handoff cierra la jornada — una sola cadena mecanica queda viva
El §0 pasa a decir donde esta el eje del nombrado al cerrar, que es lo que se
lee manana antes que nada: los cuatro commits pusheados, la unica cadena que
sigue pendiente y es mecanica (`alert-dialog`, excluida durante el barrido
porque la otra sesion editaba el fichero — su D1 ya esta commiteado, asi que
hoy esta libre), y por que las otras diez NO son deuda sino clases medidas
(estado, interpolacion, composicion, supresion sobre prop sin default).
Queda escrita la decision del autor de NO ensanchar `mapRef`, con su razon:
desbloquearia solo una de las tres clases y reintroduce la averia que esta
jornada mato — el guard v2 no recorre los valores del mapa y
`normalizeTranslationRef` tampoco los tocaria, asi que una clave relativa
saldria literal. Y queda escrita la forma correcta por si alguna vez se hace:
valores del mapa como FUENTES, no como cadenas traducidas.
Anotada la deuda menor medida hoy para que nadie la re-descubra: los diez
harnesses cuyo mock ignora su clave, los catorce `langs.ts` sin consumidores
(que NO se borran sin instruccion), los doce avisos de huerfanos. Y la linea
base con la que medirse manana, incluido el matiz de que `check` da 70 con el
WIP ajeno presente y 57 sin el.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Deuda menor, medida y sin urgencia** (no la re-descubras):
fix(soma): el dropzone de file-upload deja de robar clicks ajenos — un gesto, una ocurrencia
Lo habia dejado ANOTADO en la ola 6 por no cascadear, y estaba mal
clasificado: es la clase que este eje persigue (una ocurrencia por gesto),
encontrada por mi propia medicion y en un fichero que ya estaba editando.
Y al medirlo de verdad, el mecanismo resulto MAS ANCHO que el diagnostico de
la nota. No es solo el Trigger burbujeando: el <input type=file> oculto vive
tambien DENTRO del Dropzone, asi que el click() SINTETICO que dispara
openPicker vuelve a entrar por el mismo manejador. Medido en navegador: UN
click en el dropzone llegaba DOS veces, la segunda con
target = INPUT[hidden-input] — solo el guard de reentrada que la plataforma
pone en click() lo paraba ahi. O sea que TODO click al dropzone duplicaba, no
unicamente el del boton.
Arreglo: guard por PROPIEDAD en el onclick del dropzone — `closest` sobre los
marcadores de `trigger` y `hidden-input`, construidos con partMarkerAttr y
nunca a mano, para que un renombrado de parte rompa aqui en vez de ensanchar
el alcance en silencio. Es el idioma que table ya practica.
Test visto FALLAR con la anidacion real del DOM (trigger + hidden input
dentro del dropzone) y los tres casos: la superficie propia abre, el input
oculto no re-abre, el boton no re-abre. Medido despues en navegador: click en
el dropzone → UN contact-trigger-picker en DROPZONE; click en el boton → UNO
en TRIGGER.
file-upload 5/5 · check 69 = base, 0 propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- ~~⚠️ **file-upload emite `contact-trigger-picker` DOS veces por un gesto** ~~
**ARREGLADO 2026-08-13** , y el mecanismo era MÁS ANCHO que el diagnóstico
inicial: no es sólo el Trigger burbujeando — el `<input type=file>` oculto
también vive DENTRO del Dropzone, así que el `click()` SINTÉTICO que dispara
`openPicker` vuelve a entrar por el mismo manejador. Medido: **un** click en
el dropzone llegaba **dos** veces, la segunda con
`target = INPUT[hidden-input]` ; sólo el guard de reentrada que la plataforma
pone en `click()` lo paraba en dos. Es decir, TODO click al dropzone
duplicaba, no sólo el del botón. Arreglo: guard **por propiedad** en el
`onclick` del dropzone (`closest` sobre los marcadores de `trigger` y
`hidden-input` , construidos con `partMarkerAttr` , nunca a mano). Test visto
fallar con la anidación real; medido después: click en el dropzone → UN
`contact-trigger-picker` en DROPZONE, click en el botón → UNO en TRIGGER.
⚠️ Lección de clasificación: lo anoté como «adyacente, no cascadear» y era
la clase que este eje persigue —una ocurrencia por gesto—, encontrada por mi
propia medida en un fichero que ya estaba editando. Eso se arregla, no se
anota.
refactor(morfo,soma): ola 6 de F3 — las colecciones, y el censo baja a 11 (solo palabras)
Ocho componentes, cuatro clases, cada sitio con la pregunta A-36 primero:
NO-OP DE SINGLETON, retirados: virtual-list ×2 y virtual-grid (viewportRef lo
escribe la propia registracion del viewport) y css-field signal-warn-invalid
(el elemento ERA runtime.partRef('input'), el destino declarado — se lee para
clearTarget, no para redirigir).
ANCLAJE POR IDENTIDAD en las repetidas: menubar trigger · navigation-menu
trigger ×2 + link · tags-input item · file-upload item. En color-field y
time-field no es adorno: DOS partes registran el kebab `input` (grupo visible
+ input oculto), y fieldNode es el ref de la visible, asi que partInstance la
nombra sin depender de quien monto ultimo.
TERMINALES devueltos al provider declarado: tags-input commit-set-add ×3 y
commit-reset; file-upload commit-set-add, signal-warn-reject y commit-reset.
El dropzone que recibe el drop y el boton que ordena el borrado son la
superficie del GESTO, no el sujeto de la ocurrencia — la fila de la doctrina
del sello. Con ellos caen los parametros `target` muertos y sus llamantes.
allowedTargets NUEVO, y es el eje correcto: file-upload contact-trigger-picker
declara [dropzone] junto a su target `trigger`. Es familia contact — el gesto
se sella donde esta la MANO — y las dos superficies son eleccion del llamante
en operacion normal, no estado de montaje.
Medido en navegador: nav-menu abre y cierra sobre el trigger PULSADO
(«Solutions», el 2º de 2, que es la prueba de la identidad) y commit-select
sobre el link «Pricing»; file-upload sella DROPZONE al pulsar la zona y
TRIGGER al pulsar el boton.
⚠️ Hallazgo anotado, NO tocado (preexistente, verificado byte a byte contra
HEAD): pulsar el Trigger de file-upload emite contact-trigger-picker DOS veces
— vive dentro del Dropzone, cuyo onclick no filtra el click burbujeado. Ficha
en el handoff; el arreglo es un guard de propiedad y no se cascadea aqui.
Verificado: suites 46/46 · censo 29 → 11 (solo palabras) · check 69 = base, 0
propios · docs:check 0/623 · prettier limpio en lo que estaba limpio en HEAD.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
docs(process): el handoff cierra la jornada — una sola cadena mecanica queda viva
El §0 pasa a decir donde esta el eje del nombrado al cerrar, que es lo que se
lee manana antes que nada: los cuatro commits pusheados, la unica cadena que
sigue pendiente y es mecanica (`alert-dialog`, excluida durante el barrido
porque la otra sesion editaba el fichero — su D1 ya esta commiteado, asi que
hoy esta libre), y por que las otras diez NO son deuda sino clases medidas
(estado, interpolacion, composicion, supresion sobre prop sin default).
Queda escrita la decision del autor de NO ensanchar `mapRef`, con su razon:
desbloquearia solo una de las tres clases y reintroduce la averia que esta
jornada mato — el guard v2 no recorre los valores del mapa y
`normalizeTranslationRef` tampoco los tocaria, asi que una clave relativa
saldria literal. Y queda escrita la forma correcta por si alguna vez se hace:
valores del mapa como FUENTES, no como cadenas traducidas.
Anotada la deuda menor medida hoy para que nadie la re-descubra: los diez
harnesses cuyo mock ignora su clave, los catorce `langs.ts` sin consumidores
(que NO se borran sin instruccion), los doce avisos de huerfanos. Y la linea
base con la que medirse manana, incluido el matiz de que `check` da 70 con el
WIP ajeno presente y 57 sin el.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **10 de 17 harnesses tienen un `ts` que IGNORA su clave** — una clave errónea
*pero existente* pasaría su aserción del bag. Lo tapa parcialmente
`translations:check` (caza las inexistentes) y hoy se verificó a mano que no
hay deriva. Cerrarlo es `expect(ts).toHaveBeenCalledWith(ref)` , decidido una
vez para todos.
refactor(morfo,soma): alert-dialog cierra el eje del nombrado — el default es del CONTRATO, cero cadenas mecanicas
La ultima cadena clase 1 del catalogo, excluida del barrido de los 21 porque
otra sesion editaba el fichero. Receta identica (morfo.md Step 4): el morfo
declara #?components.alert-dialog.action|Confirm y .cancel|Cancel; fuera la
cadena resolvedAriaLabel x2, la fuente del runtime, el prop de opts y el
destructure de los dos wrappers — el aria-label del consumidor fluye por
restProps y gana por politica de merge. ALERT_DIALOG_LANGS queda huerfano
(su langs.ts se suma a la lista declarada, 14 → 15; no se borra).
El harness del test se alinea con produccion de paso: translate = ts (un solo
traductor para las dos puertas — la trampa del harness de editable, donde la
identidad dejaba pasar el ref crudo por la cadena resuelta).
Medido: navegador en es → aria-label="Confirmar" / "Cancelar" saliendo del
contrato, type="button" intacto · translations:check 159 refs compilados
(157+2), 0 errores · alert-dialog 2/2 · check 69 = base, 0 propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **15 `langs.ts` sin consumidores** (meter, popover, drawer, splitter, listbox,
docs(process): el handoff cierra la jornada — una sola cadena mecanica queda viva
El §0 pasa a decir donde esta el eje del nombrado al cerrar, que es lo que se
lee manana antes que nada: los cuatro commits pusheados, la unica cadena que
sigue pendiente y es mecanica (`alert-dialog`, excluida durante el barrido
porque la otra sesion editaba el fichero — su D1 ya esta commiteado, asi que
hoy esta libre), y por que las otras diez NO son deuda sino clases medidas
(estado, interpolacion, composicion, supresion sobre prop sin default).
Queda escrita la decision del autor de NO ensanchar `mapRef`, con su razon:
desbloquearia solo una de las tres clases y reintroduce la averia que esta
jornada mato — el guard v2 no recorre los valores del mapa y
`normalizeTranslationRef` tampoco los tocaria, asi que una clave relativa
saldria literal. Y queda escrita la forma correcta por si alguna vez se hace:
valores del mapa como FUENTES, no como cadenas traducidas.
Anotada la deuda menor medida hoy para que nadie la re-descubra: los diez
harnesses cuyo mock ignora su clave, los catorce `langs.ts` sin consumidores
(que NO se borran sin instruccion), los doce avisos de huerfanos. Y la linea
base con la que medirse manana, incluido el matiz de que `check` da 70 con el
WIP ajeno presente y 57 sin el.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
breadcrumb, anchor-nav, cropper, feed, grid-list, pin-input, progress,
refactor(morfo,soma): alert-dialog cierra el eje del nombrado — el default es del CONTRATO, cero cadenas mecanicas
La ultima cadena clase 1 del catalogo, excluida del barrido de los 21 porque
otra sesion editaba el fichero. Receta identica (morfo.md Step 4): el morfo
declara #?components.alert-dialog.action|Confirm y .cancel|Cancel; fuera la
cadena resolvedAriaLabel x2, la fuente del runtime, el prop de opts y el
destructure de los dos wrappers — el aria-label del consumidor fluye por
restProps y gana por politica de merge. ALERT_DIALOG_LANGS queda huerfano
(su langs.ts se suma a la lista declarada, 14 → 15; no se borra).
El harness del test se alinea con produccion de paso: translate = ts (un solo
traductor para las dos puertas — la trampa del harness de editable, donde la
identidad dejaba pasar el ref crudo por la cadena resuelta).
Medido: navegador en es → aria-label="Confirmar" / "Cancelar" saliendo del
contrato, type="button" intacto · translations:check 159 refs compilados
(157+2), 0 errores · alert-dialog 2/2 · check 69 = base, 0 propios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
editable, carousel; +alert-dialog 2026-08-12, huérfano de su migración
clase 1). Basura declarada. **NO borrar sin instrucción explícita.**
docs(process): el handoff cierra la jornada — una sola cadena mecanica queda viva
El §0 pasa a decir donde esta el eje del nombrado al cerrar, que es lo que se
lee manana antes que nada: los cuatro commits pusheados, la unica cadena que
sigue pendiente y es mecanica (`alert-dialog`, excluida durante el barrido
porque la otra sesion editaba el fichero — su D1 ya esta commiteado, asi que
hoy esta libre), y por que las otras diez NO son deuda sino clases medidas
(estado, interpolacion, composicion, supresion sobre prop sin default).
Queda escrita la decision del autor de NO ensanchar `mapRef`, con su razon:
desbloquearia solo una de las tres clases y reintroduce la averia que esta
jornada mato — el guard v2 no recorre los valores del mapa y
`normalizeTranslationRef` tampoco los tocaria, asi que una clave relativa
saldria literal. Y queda escrita la forma correcta por si alguna vez se hace:
valores del mapa como FUENTES, no como cadenas traducidas.
Anotada la deuda menor medida hoy para que nadie la re-descubra: los diez
harnesses cuyo mock ignora su clave, los catorce `langs.ts` sin consumidores
(que NO se borran sin instruccion), los doce avisos de huerfanos. Y la linea
base con la que medirse manana, incluido el matiz de que `check` da 70 con el
WIP ajeno presente y 57 sin el.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **12 avisos de huérfanos** en `translations:check` (los `tab-*` de
gradient-builder/picker y 8 de palabras): candidatos a borrar, decisión tuya.
- `range-calendar/langs.ts` conserva `PREV_MONTH` /`NEXT_MONTH` muertas
**PREEXISTENTES** — no las tocó este pase.
docs(morfo,chronos): la doctrina alcanza a lo que se construyo hoy
`morfo.md` gana la trampa que se llevo cinco etiquetas: **`texts` es una
DECLARACION, nunca una tabla de consulta**. `normalizeTranslationRef` toma la
clave VERBATIM y no traduce un alias a una ruta, asi que `prevMonth` frente al
`prev-month` del catalogo resuelve a una ruta que no existe y **envia el
fallback ingles sin error ni aviso**. Igual con `commonRef('action.undo')`
cuando `common.action` no existe. Y el aviso que faltaba: el guard que deberia
cazarlo, `npm run translations:check`, **estaba cascando** antes de comprobar
nada — arreglarlo y censar los 166 morfos antes de volver a fiarse.
README de soma/chronos: `SearchButton` faltaba en la tabla de partes, y ahora
documenta los dos permisos de manipulacion directa (`canMove` / `canResize` —
separados a proposito aunque hoy coincidan, porque el handler de teclado
preguntaba el equivocado) y el estado `searchOpen`.
README de eidos/chronos: seccion **Composicion** nueva (la ranura `children`,
que las partes publicas las decide `kind` en el morfo, que son ergonomicas y no
ganchos vacios, y que los overlays los pone la RAIZ). La coletilla «sin API
compound publica» queda marcada como ANULADA en su gap. Busqueda y undo/redo
pasan de hueco a hecho; la recurrencia anota que la manipulacion directa se
bloquea de forma visible y que falta PINTARLO.
`CONTINUE.md` de chronos se marca como NO vigente y apunta al handoff del eje.
El handoff gana una seccion §0 «donde estas» con los seis commits y el orden de
lo siguiente, y §4 recoge las dos decisiones de autor medidas y listas.
docs:check 0/621 · rtl:check 0/177. Los .md tocados que prettier marca ya
estaban asi en HEAD salvo el README de eidos, cuyo contenido SI pasa (el aviso
es solo CRLF del working tree, que git normaliza).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Documentación ya actualizada** (no hace falta re-hacerla): `morfo.md`
docs(process): el handoff cierra la jornada — una sola cadena mecanica queda viva
El §0 pasa a decir donde esta el eje del nombrado al cerrar, que es lo que se
lee manana antes que nada: los cuatro commits pusheados, la unica cadena que
sigue pendiente y es mecanica (`alert-dialog`, excluida durante el barrido
porque la otra sesion editaba el fichero — su D1 ya esta commiteado, asi que
hoy esta libre), y por que las otras diez NO son deuda sino clases medidas
(estado, interpolacion, composicion, supresion sobre prop sin default).
Queda escrita la decision del autor de NO ensanchar `mapRef`, con su razon:
desbloquearia solo una de las tres clases y reintroduce la averia que esta
jornada mato — el guard v2 no recorre los valores del mapa y
`normalizeTranslationRef` tampoco los tocaria, asi que una clave relativa
saldria literal. Y queda escrita la forma correcta por si alguna vez se hace:
valores del mapa como FUENTES, no como cadenas traducidas.
Anotada la deuda menor medida hoy para que nadie la re-descubra: los diez
harnesses cuyo mock ignora su clave, los catorce `langs.ts` sin consumidores
(que NO se borran sin instruccion), los doce avisos de huerfanos. Y la linea
base con la que medirse manana, incluido el matiz de que `check` da 70 con el
WIP ajeno presente y 57 sin el.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
§Step 4 (las dos clases de precedencia, el anti-patrón del `propRef` + cadena,
la tabla de las cuatro exclusiones, el spread condicional y la regla «mira si el
elemento es tuyo») · `morfo.md` §common-mistakes (la trampa de `texts` + el
guard v2) · `soma-architecture.md` §mergeProps (la fila de naming) · README de
soma/chronos · README de eidos/chronos · `CONTINUE.md` de chronos (NO vigente,
apunta aquí).
**Línea base al cerrar** (mídete contra esto, no contra memoria): `check`
**70 = base** con el WIP ajeno presente, **57** sin él — mis commits aportan
CERO · soma **1269 ✓ / 1** (el timeout ajeno preexistente de
`soma-attr-audit` ) · morfo+sema **496 ✓** · `translations:check` **0 errores /
12 avisos** · `docs:check` 0/621 · `rtl:check` 0/177 · refs compilados **157** .
docs(morfo,chronos): la doctrina alcanza a lo que se construyo hoy
`morfo.md` gana la trampa que se llevo cinco etiquetas: **`texts` es una
DECLARACION, nunca una tabla de consulta**. `normalizeTranslationRef` toma la
clave VERBATIM y no traduce un alias a una ruta, asi que `prevMonth` frente al
`prev-month` del catalogo resuelve a una ruta que no existe y **envia el
fallback ingles sin error ni aviso**. Igual con `commonRef('action.undo')`
cuando `common.action` no existe. Y el aviso que faltaba: el guard que deberia
cazarlo, `npm run translations:check`, **estaba cascando** antes de comprobar
nada — arreglarlo y censar los 166 morfos antes de volver a fiarse.
README de soma/chronos: `SearchButton` faltaba en la tabla de partes, y ahora
documenta los dos permisos de manipulacion directa (`canMove` / `canResize` —
separados a proposito aunque hoy coincidan, porque el handler de teclado
preguntaba el equivocado) y el estado `searchOpen`.
README de eidos/chronos: seccion **Composicion** nueva (la ranura `children`,
que las partes publicas las decide `kind` en el morfo, que son ergonomicas y no
ganchos vacios, y que los overlays los pone la RAIZ). La coletilla «sin API
compound publica» queda marcada como ANULADA en su gap. Busqueda y undo/redo
pasan de hueco a hecho; la recurrencia anota que la manipulacion directa se
bloquea de forma visible y que falta PINTARLO.
`CONTINUE.md` de chronos se marca como NO vigente y apunta al handoff del eje.
El handoff gana una seccion §0 «donde estas» con los seis commits y el orden de
lo siguiente, y §4 recoge las dos decisiones de autor medidas y listas.
docs:check 0/621 · rtl:check 0/177. Los .md tocados que prettier marca ya
estaban asi en HEAD salvo el README de eidos, cuyo contenido SI pasa (el aviso
es solo CRLF del working tree, que git normaliza).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
fix(sema,morfo,soma): una superficie no admite dos ocurrencias, y el nombre ya no miente
`data-event-*` es UNA RANURA por elemento. Tres auditorias independientes
encontraron el mismo defecto y ninguna lo cerro: fable S1 (2026-07-01, con el
README diciendo «findings executed»), sema S-17 (2026-08-05, midio el estampado
del knob muriendo a los 24ms de un hold de 240) y blocks A-36/A-65 (reproducido
en navegador, dos estampados a 1,1ms). Lo que faltaba no era diagnostico: nadie
podia distinguir una redireccion legitima de una deriva, porque la opcion que
redirigia se llamaba `fallbackTarget` y hacia lo contrario de lo que decia.
EL NOMBRE PRIMERO. `fallbackTarget` -> `targetOverride` (202 apariciones, 64
ficheros, dirigido con `git ls-files`). Siempre GANO sobre el ref registrado; el
nombre honesto es la precondicion para auditarlo. Las cronicas conservan el viejo
por diseño.
LA PROPIEDAD. `unstampEventAttrs` recibia el signal y lo DESCARTABA, asi que la
ocurrencia que terminaba antes borraba a la que tenia la ranura. Ahora comprueba
`data-event-id`. Con eso, `replace` deja de ser el comportamiento roto y pasa a
ser el suelo correcto.
A-36, SIN `regime`. Los cuatro overlays cedian el estampado del `open` al
trigger por un workaround de la era `sequence: 'pre'`; con `post` el content ya
esta montado. Retirado el override (float-panel migrado `pre`->`post`, ultimo
fuera de la doctrina que sema.md escribe). Medido en /blocks/site-header/preview
a 375px: contact-activate en el TRIGGER +18,7ms y open en el CONTENT +20,6ms,
con press-squeeze y slide-from-right-full corriendo. Antes: los dos en el
trigger, press-squeeze jamas.
`regime` DEFINIDO — y son dos valores, no cuatro. `replace` (default) y `queue`,
este solo para las 4 parejas irreducibles: los tres toggles (el provider ES el
boton) y el knob. `collapse` retirado (su caso murio con la propiedad: medido, 0
huecos en 5 emisiones a 72ms) y `lock` retirado (significaria «un cierre que no
se anuncia»), con sus 4 declaraciones muertas.
El guard de `queue` PASO EN VERDE SIENDO INCORRECTO: con timers falsos no hay
animaciones, asi que awaitExpression volvia al instante. El navegador midio el
commit encolado llegando 1,6s tarde. `queue` espera el HOLD, no la expresion.
Medir la envolvente no es medir la salida.
`renderAttrs` — los attrs que no son partes. Vaciar ACTIVE_DEV_TRACK (palabras y
chronos ya no estan excluidos) destapo 23 `data-palabras-*` que ningun morfo
declaraba y que palabras.css SI estiliza: aterrizan en el arbol que el usuario
escribio, asi que no pueden ser partes. Campo nuevo con la linea afilada —si el
consumidor puede componerlo, es una PARTE— y 3 consumidores (palabras, waveform,
aura). Declarado en types.ts Y en el esquema sium: la leccion de MorfoElement.
Ademas: 5 selectores muertos en el predicado del focus-scope de palabras
(barrido de todo el repo: aparecian solo ahi) · RTL-1 real en palabras-chrome
(inset logico + translate fisico = doble volteo; rtl:check 1->0) · el union
inline al `Direction` canonico, que destapo que el censo de direccion grepea el
NOMBRE del tipo · deuda de chronos (scope, barrel, README).
Y de la cola: S-36 (barrel 68->71 + guard), S-31 (vibrate(NaN)), S5 (la
preferencia de reduced-motion del usuario ya gana a los channels del morfo), SO2
(el warn que el JSDoc prometia), SO4 (validar el target antes del prewrite).
Todos los guards nuevos vistos fallar antes de arreglar nada.
Base: check 74 (= base) · sema+morfo+contracts 454 verdes / 6 ajenos
preexistentes · soma navegador 1258/1259 (el rojo es un timeout ajeno,
reproducido con los cambios en stash) · docs:check 0/618 · rtl:check 0/177.
Handoff: docs/process/CONTINUE-perceptual-surface.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
---
## 1 · El hallazgo, y por qué costó tanto verlo
`data-event-*` es **una ranura por elemento** : dos ocurrencias sobre un nodo no
pueden expresarse las dos. El defecto lo encontraron **tres auditorías
independientes y ninguna lo cerró**:
| Auditoría | Fecha | Qué dijo |
| --- | --- | --- |
| fable **S1** | 2026-07-01 | «Race de señales solapadas sobre el mismo target», con el arreglo mínimo escrito. El README de `old-deprecated` afirmaba «findings executed». |
| sema **S-17** | 2026-08-05 | Re-descubierto solo. Midió el knob: el estampado de `commit-set` moría a los 24 ms de un hold de 240. |
| blocks **A-36 / A-65** | 2026-08-01/09 | Reproducido en NAVEGADOR: dos estampados a 1,1 ms, `press-squeeze` jamás. |
Tres veces el mismo defecto, tres veces sin arreglar. Lo que faltaba no era
diagnóstico: era que **nadie podía distinguir una redirección legítima de una
deriva**, porque la opción que redirigía se llamaba `fallbackTarget` y decía lo
contrario de lo que hacía.
---
## 2 · Lo cerrado (todos los guards vistos fallar antes de arreglar)
| # | Qué | Dónde |
| --- | --- | --- |
| 2.1 | ** `fallbackTarget` → `targetOverride` ** (202 apariciones / 64 ficheros). No cosmético: es la PRECONDICIÓN para auditar la redirección | `soma/runtime.svelte.ts` + todo el árbol |
| 2.2 | **El desestampado comprueba propiedad** (`data-event-id`). Rojo: `expected null to be 'sig-B'` | `sema/stamp.ts` · `sema/projection/dom.ts` |
| 2.3 | ** `assertTargetOverride` ** — el runtime lee el marcador de parte del elemento y avisa si no es `target` ni `allowedTargets` . Runtime porque el override es una expresión | `soma/runtime.svelte.ts` |
| 2.4 | **A-36 CERRADO, y sin `regime`** | los 4 overlays |
| 2.5 | ** `regime` definido y cableado**, reducido a `replace \| queue` | `sema/types.ts` · `engine.ts` · `signal.ts` |
| 2.6 | ** `renderAttrs` ** — los attrs que no son partes | `morfo/types.ts` + `schema.ts` |
| 2.7 | `ACTIVE_DEV_TRACK` vaciado + deuda de chronos (scope · barrel · README) | `contracts.test.ts` |
| 2.8 | **S-36** barrel 68→71 + guard · **S-31** `vibrate(NaN)` · **S5** · **SO2** · **SO4** | varios |
### 2.4 A-36 — la lección: no era arbitrar, era dejar de redirigir
Los cuatro overlays cedían el estampado del `open` al trigger. La razón escrita
en el código («el content no ha montado») era cierta **cuando `open` era
`pre` **. Dialog / Drawer / Popover migraron a `post` hace tiempo y nadie retiró
el override; `float-panel` seguía en `pre` (último fuera de la doctrina que
`sema.md` escribe). Con `<Drawer.Trigger>` compuesto sobre `<Button>` —que es UN
solo elemento— el `open` caía encima del `contact-activate` .
Medido en `/blocks/site-header/preview` a 375 px:
```
ANTES contact-activate TRIGGER +329ms · emerge/open TRIGGER +331ms · press-squeeze: NUNCA
AHORA contact-activate TRIGGER +18,7ms · open CONTENT +20,6ms
getAnimations() → press-squeeze running (120ms) + slide-from-right-full
```
### 2.5 `regime` — dos valores, no cuatro
- **`replace`** (default) no había que construirlo: **2.2 lo terminó** . Antes era
el comportamiento *roto* .
- **`queue`** es el único nuevo. Aplicado a las 4 parejas irreducibles: los tres
toggles (el provider ES el botón) y el knob (`handle-drop` + `commit-set` ).
- **`collapse` RETIRADO** — su caso (el parpadeo continuo) ya lo mató 2.2:
medido, **0 huecos** en 5 emisiones a 72 ms con hold de 240. Y reutilizar el
`data-event-id` habría minado la propiedad que lo curó.
- **`lock` RETIRADO** — bajo la semántica limpia significaría «un cierre que no
se anuncia si hay algo proyectando». Con él, sus 4 declaraciones muertas.
> ⚠️ **EL GUARD DE `queue` PASÓ EN VERDE SIENDO INCORRECTO.** Con timers falsos
> no hay animaciones, así que `awaitExpression` volvía al instante. El navegador
> midió que el commit encolado llegaba **1,6 s tarde**, porque esperaba la
> expresión entera (tope 1500 ms) en vez del hold. `queue` espera el **hold**;
> el desplazado está a salvo porque su limpieza ya es consciente del dueño. El
> test corregido inyecta un `getAnimations()` que nunca termina.
> **Medir la envolvente no es medir la salida** — otra vez.
### 2.6 `renderAttrs` + los fixes de palabras
Al vaciar `ACTIVE_DEV_TRACK` salieron 23 `data-palabras-*` que ningún morfo
declaraba y que `palabras.css` **sí estiliza** . No son partes ni pueden serlo:
aterrizan en el árbol que el usuario escribió. Campo nuevo, con la línea afilada
—*si el consumidor puede componerlo o direccionarlo, es una PARTE*— y ≥3
consumidores (palabras 23 · waveform 2 · aura 2).
Y tres más: **5 selectores muertos** en el predicado del focus-scope (barrido de
todo el repo: aparecían sólo ahí) · **RTL-1 real** en `palabras-chrome.css`
(`inset-inline-start` lógico + `translate` físico = el doble volteo; centrado con
margen derivado de un token, `rtl:check` 1→0) · el `dir?: 'ltr'|'rtl'` inline al
`Direction` canónico — lo que destapó que **el censo de dirección grepea el
NOMBRE del tipo**, así que un union inline lo escondía (exención firmada, misma
razón que `field-langs` ).
---
## 3 · LA COLA — por dónde seguir, en orden
feat(morfo,soma): la emisión deja de aceptar un elemento anónimo
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>
2 months ago
### 3.0 ⚠️ GIRO DE ENFOQUE (2026-08-10, aprobado por el autor)
La campaña de §3.1 quedó SUPERADA como mecanismo: censo + campaña + aviso de
runtime es la misma defensa que ya falló tres veces (depende de que alguien
mire), y el propio censo de hoy demostró el modo de fallo al declarar
supervivientes de más y de menos. El plan vigente hace inexpresable el estado
ilegal — el defecto de clase no es que N eventos redirijan sin declarar, es que
la API de emisión acepta un `HTMLElement` anónimo, compensando que el registro
de partes pierde la identidad de instancia (indexa por nombre, gana el último
montado):
- **F1 ✅ (2026-08-10)** — `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
de `semaSelector` ). 104 anotaciones migradas + 3 wrappers estrechados
(`createMenuDialRuntime` / `createOnionMenuRuntime` / `createMetricsRuntime` —
devolvían tipado pero tomaban sources ANCHO: agujero). `SomaRuntime<Morfo>`
sólo en la tabla de contratos. Doc: `soma-architecture.md` §"typed by ITS
morfo". Delta de check contra base medida con stash: **CERO** (74/74 únicos,
87/87 brutos); server 454✓/6 ajenos; navegador 1258✓/1 ajeno.
Los tipos ya desmontaron DOS filas del censo: los `'clear-document'` /`'unlink'`
de palabras eran nombres de COMANDO en un ternario que mapea a eventos reales
— falso positivo del censo, resuelto por inferencia.
- **F2 ✅ (2026-08-10)** — registro por instancia: `Map<part, PartRegistration[]>` ,
la pertenencia la mantiene el stream del attachment (poda al desmontar, se
re-añade al final al re-montar — sin crecimiento en listas virtualizadas),
resolución `liveReg` = la instancia VIVA más nueva con caída a la más nueva
a secas (pre-attach conserva la conducta anterior). Arregla de paso la clase
«la más nueva muerta ensombrece a una viva». Delta de check CERO; suites
idénticas (454✓/6 · 1258✓/1).
feat(soma,morfo)!: targetOverride BORRADO — el estado ilegal del eje es inexpresable
El endgame que §3.0 firmo al abrir F3: con el censo a 0, la opcion de elemento
anonimo sale de TriggerOptions, y con ella assertTargetOverride (existia solo
para vigilarla; queda assertAnchor, el guard de la puerta que la sustituye).
Las dos resoluciones `opts.targetOverride ?? resolveEmitTarget(...)` (emit y
foco a11y) quedan en la resolucion unica, y los Omit<..., 'targetOverride'>
de los triggers anclados se vuelven vacuos y desaparecen.
El tipo lleva LAPIDA doctrinal: la opcion nacio fallbackTarget, se renombro el
2026-08-10 como precondicion del censo, y compensaba un registro sin identidad
de instancia — es lo que dejo a tres auditorias (fable S1, sema S-17, blocks
A-36/A-65) encontrar la misma deriva sin poder cerrarla. No volver a añadir
una opcion de elemento.
Y la tesis se probo sola al ejecutarla: el compilador, ya como censo, cazo 9
usos en TESTS que el grep del scratch nunca escaneo. De ellos, 3 eran
redundantes (la parte registraba el mismo elemento), 2 del test de metrics
migran al registro + trigger plano, y 4 fijaban la conducta borrada — el
describe «targetOverride contract» entero y el test del override preferente,
retirados con lapida; el guard A-36 de los overlays (morfo sin redireccion +
post) se conserva, des-anidado. El scratch del censo, retirado: el censo es
`npm run check`.
Docs vivos adjudicados: morfo.md §allowedTargets describe la puerta anclada
(EventNameTargeting + assertAnchor) y §repeated-part nombra partInstance donde
decia targetOverride; los docblocks de las 3 factorias de vistas de eidos
dicen la forma nueva; errors.ts deja de ofrecer la opcion muerta como remedio.
Las menciones historicas (A-36, «the retired...») se conservan como historia.
Verificado: soma navegador 1278✓/1 (el timeout ajeno preexistente de
soma-attr-audit, documentado en la base) · runtime+metrics 53/53 · check 69 =
base, 0 propios · docs:check 0/623 · prettier limpio en lo que estaba limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **F3 — ✅ CERRADA (2026-08-13, censo 0 y API borrada)**. Siete olas en un
día: gestos handle (64→51) · grids de fecha (→44) · css/number-field (→38) ·
vistas de eidos que REGISTRAN (→29) · select/combobox con el A/B de
present-rise (→11 nav-menu incl.) · colecciones (→11) · palabras + los 2
declarados de pagination/toolbar (→0). ENDGAME EJECUTADO: `targetOverride`
borrado de `TriggerOptions` (con lápida doctrinal: no volver a añadir una
opción de elemento), `assertTargetOverride` fuera (queda `assertAnchor`
para la puerta anclada), el scratch retirado — y el compilador, ya como
censo, cazó 9 usos en tests que el grep del scratch nunca escaneó.
El estado ilegal es INEXPRESABLE: ningún elemento anónimo viaja por la API
de emisión. Historial del framework (2026-08-10):
feat(morfo,soma): la emisión deja de aceptar un elemento anónimo
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>
2 months ago
- `PartRef<T = string>` genérico (el builder `v.partRef` preserva el literal;
default `string` = cero ondas, doctrina «ensancha el tipo primero»).
- `targetFallback` declarado en el morfo (tipo + schema con 3 invariantes
testeados: parte desconocida / re-declarar el target / duplicado) y
pre-aplanado en `ActionPlan` ; `resolveEmitTarget` es LA resolución única
para validación, emit y foco a11y (no pueden discrepar).
- `SomaRuntimePart<M, K>` sin default + `trigger` anclado tipado por
`EventNameTargeting<M, K>` (target ∪ allowedTargets; el fallback EXCLUIDO a
propósito — anclar al degradado forzaría el aterrizaje pobre con el
primario montado) + `runtime.partInstance(part, el)` (identidad por
referencia contra el registro; elemento ajeno → null) + guard dev
`assertAnchor` para llamantes JS. Codemod: 101 ficheros + 6 uniones a mano
(clases parametrizadas por parte: paginación de calendarios, sidebar).
- Tests nuevos: cadena de fallback resuelta por el runtime · anclaje estampa
SU instancia (no la más nueva) · `partInstance` identidad. 71✓.
- **Ola 1 ✅ — los 4 overlays**: su `allowedTargets: [trigger]` del close era
el EJE EQUIVOCADO (es estado de montaje, no elección del llamante) →
`targetFallback: [trigger]` en dialog/drawer/popover/float-panel, y los 4
providers dejan de resolver `content ?? partRef ?? trigger` a mano. El
prewrite de `data-last-action` queda SOLO en content (verificado: todos
los selectores de eidos que lo leen son `[data-*-content]` ; escribirlo en
el trigger era incidental). `stampPartsOf` del pack-census cuenta ahora
`targetFallback` como estampable. 218✓ (overlays + census + morfo).
- **Olas 2-3 EN CURSO** (2026-08-10, censo 145 → **107** ). Migrados y en
verde (`check` DELTA CERO · navegador 1258✓+2 nuevos/1 ajeno):
* **Anclado al handle propio** — dropdown-menu item · context-menu item ·
command item (ratón) · table row · timeline item (emerge-reveal).
* **`partInstance` por identidad** — listbox · toggle-group (las TRES
emisiones del gesto anclan a UNA superficie, lo que el régimen `queue`
del par contact+commit asume) · tag-group select/remove · tree-view
(commit-select discrimina item/branch por identidad) · tree-grid ·
command (camino Enter) · radio-group · rating-group (item si el gesto
nombró estrella, provider si teclado — `partInstance` discrimina) ·
grid-list · feed · tabs · carousel.
* **Gesto DENTRO de la superficie** — table sort-trigger→column-header y
detail-trigger→row: `closest(partMarkerAttr(…))` + `partInstance` ; el
marcador sale de la función canónica, nunca string a mano.
`partMarkerAttr` exportado del barrel de morfo para esto.
⚠️ El elemento de partida es `opts.ref.current` de la clase, NO
`e.currentTarget` — los tests fabrican eventos sin currentTarget.
* **No-op retirados** — proof-of-human (3) · tag-group clear · checkbox.
* **CROSS retirados (lección A-36: sobra la redirección)** — editable
(shift-enter-mode/commit-save/commit-cancel estampan el PROVIDER
declarado; el subparte era deriva sin declarar).
* **aura → `targetFallback` ** — sus 4 `allowedTargets:[provider]` de la
mañana eran también eje equivocado; provider sin condicionales.
- **Más migrados en el mismo pase** (censo **107 → 99** , check DELTA CERO,
21✓): search-field y stepper (CROSS retirados — el provider declarado ES
la superficie; params de gesto eliminados de `clear` /`submit`/`setStep`/
`goTo*Step` ) · form (no-op retirados de submit/warn/reset) · clipboard
(`copy(triggerEl?)` ancla por identidad al Trigger pulsado — varios
Triggers por provider quedan bien — y el README lo documenta).
refactor(morfo,soma): ola 6 de F3 — las colecciones, y el censo baja a 11 (solo palabras)
Ocho componentes, cuatro clases, cada sitio con la pregunta A-36 primero:
NO-OP DE SINGLETON, retirados: virtual-list ×2 y virtual-grid (viewportRef lo
escribe la propia registracion del viewport) y css-field signal-warn-invalid
(el elemento ERA runtime.partRef('input'), el destino declarado — se lee para
clearTarget, no para redirigir).
ANCLAJE POR IDENTIDAD en las repetidas: menubar trigger · navigation-menu
trigger ×2 + link · tags-input item · file-upload item. En color-field y
time-field no es adorno: DOS partes registran el kebab `input` (grupo visible
+ input oculto), y fieldNode es el ref de la visible, asi que partInstance la
nombra sin depender de quien monto ultimo.
TERMINALES devueltos al provider declarado: tags-input commit-set-add ×3 y
commit-reset; file-upload commit-set-add, signal-warn-reject y commit-reset.
El dropzone que recibe el drop y el boton que ordena el borrado son la
superficie del GESTO, no el sujeto de la ocurrencia — la fila de la doctrina
del sello. Con ellos caen los parametros `target` muertos y sus llamantes.
allowedTargets NUEVO, y es el eje correcto: file-upload contact-trigger-picker
declara [dropzone] junto a su target `trigger`. Es familia contact — el gesto
se sella donde esta la MANO — y las dos superficies son eleccion del llamante
en operacion normal, no estado de montaje.
Medido en navegador: nav-menu abre y cierra sobre el trigger PULSADO
(«Solutions», el 2º de 2, que es la prueba de la identidad) y commit-select
sobre el link «Pricing»; file-upload sella DROPZONE al pulsar la zona y
TRIGGER al pulsar el boton.
⚠️ Hallazgo anotado, NO tocado (preexistente, verificado byte a byte contra
HEAD): pulsar el Trigger de file-upload emite contact-trigger-picker DOS veces
— vive dentro del Dropzone, cuyo onclick no filtra el click burbujeado. Ficha
en el handoff; el arreglo es un guard de propiedad y no se cascadea aqui.
Verificado: suites 46/46 · censo 29 → 11 (solo palabras) · check 69 = base, 0
propios · docs:check 0/623 · prettier limpio en lo que estaba limpio en HEAD.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **QUEDA de las olas 2-4** (99 sitios): ~~tags-input · file-upload~~
(**HECHOS 2026-08-13 con el resto de colecciones, censo 29 → 11**) ·
~~month/year-grid · calendar (day) · range-calendar~~ (**HECHOS 2026-08-13,
censo 51 → 44**: clase B migrada al anclaje por identidad — `day` /`cell`
registran, así que `partInstance` + trigger anclado; range-calendar gana un
helper `triggerOnDay` para sus 5 sitios) ·
feat(morfo,soma): la emisión deja de aceptar un elemento anónimo
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>
2 months ago
navigation-menu · menubar · virtual-list/grid · pagination (declarado;
anclar desde los 5 controles) · ~~css-field · number-field~~ (**HECHOS
2026-08-13, censo 44 → 38**: el `targetOverride` público de `commit()`
RETIRADO — sólo lo pasaban sus propios scrubbers, redirigiendo el TERMINAL
declarado `provider` al scrubber contra la fila de la doctrina del sello, y
de paso silenciando las reglas de pack que seleccionan el provider; los 4
`handle-pick` /`drag-scrub` con `e.currentTarget` eran no-op sobre el
scrubber singleton registrado) ·
~~drag-drop · path-trace · rotate-align · float-panel~~ (**HECHOS
2026-08-13, censo 64 → 51**: los «campos de elemento propios» eran los
MISMOS elementos que las partes registran — 10 no-op retirados; las 3
emisiones de drag-drop ancladas por identidad a sus partes repetidas
[`source`/`target` SON los refs registrados, verificado]; y la pregunta de
float-panel la respondió la fila de la doctrina del sello: el content ES
la posición → retirar los 4, el `?? dragEl` era cinturón de la era
fallbackTarget) · ~~metrics/menu-dial/onion-menu~~ (**HECHOS 2026-08-13,
censo 38 → 29**: las vistas REGISTRAN — metrics `value` se registra y
emite ANCLADO él mismo, el contexto deja de acarrear elementos; menu-dial
registra `list` + cada `action` [el attachment atraviesa
Fab→Button→button, medido: ids del runtime en los mini-FABs]; onion-menu
registra `surface` + cada sector `item` por attachment CACHEADO por clave
[la membresía es el stream, F2] — todo medido en navegador: expand sella
el sector d1 pulsado, select la hoja d2, y el update de metrics aterriza
en el Value del bloque live con 4 instancias registradas, no en el más
nuevo. ⚠️ Trampa pagada: importar `state` en un `.svelte` con runas
`$state` rompe TODOS los `$state<T>` del fichero — alias
`state as refState` ) ·
fix(morfo,soma): ola 5 de F3 — select y combobox dejan de sellar el trigger, y present-rise suelta el boton
El superviviente A-36 mas puro de la campaña: el open YA era post (el content
monta antes del emit) pero el provider seguia forzando targetOverride al
trigger — un vestigio de la era pre LEGALIZADO por el allowedTargets del
2026-08-10, que era el eje equivocado (estado de montaje, no eleccion del
llamante). Con el, la firma de motion present-rise corria SOBRE el boton: la
regresion firmada en §1.2 del handoff de normalizacion, medida hoy.
A/B en navegador (la disposicion firmada mandaba medir primero):
ANTES emerge-open TRIGGER · emerge-close TRIGGER · present-rise en el boton
AHORA emerge-open CONTENT · emerge-close CONTENT · present fuera del boton
commit-select → el ITEM elegido («Pear Soft»), anclado por identidad
Receta (la de F4/ola-1): el open pierde allowedTargets y el override; el close
declara targetFallback ([trigger] en select, [trigger, input] en combobox — un
combobox puede vivir sin boton); la seleccion ancla por partInstance al item
registrado, con el nombre computado de ListSelection (SelectionEvent) por la
puerta tipada.
Verificado: suites 24/24 · censo sin un solo sitio de select/combobox ·
check 69 = base, 0 propios · consola limpia (404s = raiz/favicon del arranque).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
~~select/combobox~~ (**HECHOS 2026-08-13**: NO era elección del llamante —
era el superviviente A-36 con el open YA en `post` : el override al trigger
forzaba el aterrizaje pobre con el content montado, y la firma de motion
`present-rise` corría SOBRE el botón [medido ANTES/DESPUÉS: open/close
TRIGGER→CONTENT, present fuera del trigger, commit-select→el ITEM elegido
anclado]. Receta: open pierde `allowedTargets` [vestigio legalizado], close
pasa a `targetFallback` [trigger] / [trigger, input], selección por
`partInstance` ) ·
feat(morfo,soma): la emisión deja de aceptar un elemento anónimo
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>
2 months ago
dropdown/context open+close (F4, gestureTarget) · **palabras** (13 filas,
refactor(morfo,soma): ola 6 de F3 — las colecciones, y el censo baja a 11 (solo palabras)
Ocho componentes, cuatro clases, cada sitio con la pregunta A-36 primero:
NO-OP DE SINGLETON, retirados: virtual-list ×2 y virtual-grid (viewportRef lo
escribe la propia registracion del viewport) y css-field signal-warn-invalid
(el elemento ERA runtime.partRef('input'), el destino declarado — se lee para
clearTarget, no para redirigir).
ANCLAJE POR IDENTIDAD en las repetidas: menubar trigger · navigation-menu
trigger ×2 + link · tags-input item · file-upload item. En color-field y
time-field no es adorno: DOS partes registran el kebab `input` (grupo visible
+ input oculto), y fieldNode es el ref de la visible, asi que partInstance la
nombra sin depender de quien monto ultimo.
TERMINALES devueltos al provider declarado: tags-input commit-set-add ×3 y
commit-reset; file-upload commit-set-add, signal-warn-reject y commit-reset.
El dropzone que recibe el drop y el boton que ordena el borrado son la
superficie del GESTO, no el sujeto de la ocurrencia — la fila de la doctrina
del sello. Con ellos caen los parametros `target` muertos y sus llamantes.
allowedTargets NUEVO, y es el eje correcto: file-upload contact-trigger-picker
declara [dropzone] junto a su target `trigger`. Es familia contact — el gesto
se sella donde esta la MANO — y las dos superficies son eleccion del llamante
en operacion normal, no estado de montaje.
Medido en navegador: nav-menu abre y cierra sobre el trigger PULSADO
(«Solutions», el 2º de 2, que es la prueba de la identidad) y commit-select
sobre el link «Pricing»; file-upload sella DROPZONE al pulsar la zona y
TRIGGER al pulsar el boton.
⚠️ Hallazgo anotado, NO tocado (preexistente, verificado byte a byte contra
HEAD): pulsar el Trigger de file-upload emite contact-trigger-picker DOS veces
— vive dentro del Dropzone, cuyo onclick no filtra el click burbujeado. Ficha
en el handoff; el arreglo es un guard de propiedad y no se cascadea aqui.
Verificado: suites 46/46 · censo 29 → 11 (solo palabras) · check 69 = base, 0
propios · docs:check 0/623 · prettier limpio en lo que estaba limpio en HEAD.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
diseño fino por evento — **lo ÚNICO que queda del censo: 11 sitios** ) ·
~~navigation-menu · menubar · virtual-list/grid · time/color-field ·
css-field~~ (**HECHOS 2026-08-13**: no-op de singleton retirados
[virtual-list × 2 · virtual-grid · css-field, donde el elemento ERA
`partRef` del destino declarado, leído para `clearTarget` y no para
redirigir]; anclaje por identidad en las repetidas [menubar trigger ·
navigation-menu trigger × 2 + link · tags-input item · file-upload item];
terminales devueltos al provider declarado [tags-input `commit-set-add`
× 3 + `commit-reset` , file-upload `commit-set-add` · `signal-warn-reject` ·
`commit-reset` ]; y `contact-trigger-picker` de file-upload gana
`allowedTargets: [dropzone]` — el gesto se sella donde está la MANO y las
dos superficies son elección del llamante, no estado de montaje. Medido:
nav-menu abre/cierra sobre el trigger PULSADO [«Solutions», el 2º de 2] y
`commit-select` sobre el link «Pricing»; file-upload sella DROPZONE al
pulsar la zona y TRIGGER al pulsar el botón. En color/time-field el
`input` lo registran DOS partes [grupo visible + input oculto], así que
ahí `partInstance` no es adorno: nombra la visible por identidad) ·
**chronos** (los botones del editor no son partes;
docs(process): dos correcciones de registro — mi censo repetia uno que ya existia, y peor
D13 mezclaba tres clases en un saco
------------------------------------
`CONTINUE-sema-audit.md` §3.1 YA habia medido esto — «eventos huerfanos: 14 de
249 en 104 morfos con soma» — y, sobre todo, ya los habia CLASIFICADO. Verificado
componente a componente, su clasificacion es la correcta y la mia era gruesa:
- **contrato muerto** — `tooltip` ×3. El unico defecto de la clase D1/D3/D4, y el
unico accionable. Cero `trigger(` en todo su directorio soma.
- **punto de extension documentado** — `virtual-list` / `virtual-grid`
`handle-scroll*`. Los emite la APP; el README los diseca en prosa con su
doctrina. Lo que falta no es cableado: es que el morfo no tiene forma de
DECLARARLO (la auditoria propone `emission: 'app'`).
- **verbo delegado** — `gradient-picker.commit-reset` (S-14, 1 de 6 identicos).
NO se arregla como D3: `GradientPickerProvider` no tiene `clear()` — su
docblock dice que open/commit/cancel/clear viven en el `PickerProvider`
generico compuesto y que esta clase «solo posee el runtime del morfo». No hay
donde enganchar.
Lo que el guard añade sobre aquel censo no es el hallazgo: es que deja de ser una
medicion de una vez y bloquea la regresion. Eso lo demostro D9 al deshacerlo.
`CONTINUE-perceptual-surface.md` afirmaba algo falso sobre chronos
-------------------------------------------------------------------
Su fila de la migracion de `targetOverride` dice «sus chips tampoco REGISTRAN —
plain trigger fallaria igual». No se sostiene, y lo se porque acabo de medirlo
cableando el acarreo (D9): `partInstance('event-chip', el)` RESUELVE, el sello de
`handle-drag` aterriza en el `event-chip`, y el arrastre del tirador da 6
`handle-resize` sobre `event-resize-handle`. Los chips registran.
La otra mitad de la fila (los botones del editor no son partes) queda, y se le
añade lo que dice la auditoria de sema: la exclusion de escritura de chronos se
levanto el 2026-08-10.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
la exclusión de escritura se levantó el 2026-08-10 —
[`CONTINUE-sema-audit.md` ](./CONTINUE-sema-audit.md ) §3.1: *«ya
accionables»*). ⚠️ **La otra mitad de esta fila es falsa y se corrige el
2026-08-12: «sus chips tampoco REGISTRAN» no se sostiene.** Medido al cablear
el acarreo (ledger D9): `runtime.partInstance('event-chip', el)` RESUELVE, y
el sello de `handle-drag` aterriza en el `event-chip` ; el arrastre del
tirador da 6 `handle-resize` sobre `event-resize-handle` . Los chips
registran, así que el `plain trigger` de esta migración no fallaría por ese
feat(soma,morfo)!: targetOverride BORRADO — el estado ilegal del eje es inexpresable
El endgame que §3.0 firmo al abrir F3: con el censo a 0, la opcion de elemento
anonimo sale de TriggerOptions, y con ella assertTargetOverride (existia solo
para vigilarla; queda assertAnchor, el guard de la puerta que la sustituye).
Las dos resoluciones `opts.targetOverride ?? resolveEmitTarget(...)` (emit y
foco a11y) quedan en la resolucion unica, y los Omit<..., 'targetOverride'>
de los triggers anclados se vuelven vacuos y desaparecen.
El tipo lleva LAPIDA doctrinal: la opcion nacio fallbackTarget, se renombro el
2026-08-10 como precondicion del censo, y compensaba un registro sin identidad
de instancia — es lo que dejo a tres auditorias (fable S1, sema S-17, blocks
A-36/A-65) encontrar la misma deriva sin poder cerrarla. No volver a añadir
una opcion de elemento.
Y la tesis se probo sola al ejecutarla: el compilador, ya como censo, cazo 9
usos en TESTS que el grep del scratch nunca escaneo. De ellos, 3 eran
redundantes (la parte registraba el mismo elemento), 2 del test de metrics
migran al registro + trigger plano, y 4 fijaban la conducta borrada — el
describe «targetOverride contract» entero y el test del override preferente,
retirados con lapida; el guard A-36 de los overlays (morfo sin redireccion +
post) se conserva, des-anidado. El scratch del censo, retirado: el censo es
`npm run check`.
Docs vivos adjudicados: morfo.md §allowedTargets describe la puerta anclada
(EventNameTargeting + assertAnchor) y §repeated-part nombra partInstance donde
decia targetOverride; los docblocks de las 3 factorias de vistas de eidos
dicen la forma nueva; errors.ts deja de ofrecer la opcion muerta como remedio.
Las menciones historicas (A-36, «the retired...») se conservan como historia.
Verificado: soma navegador 1278✓/1 (el timeout ajeno preexistente de
soma-attr-audit, documentado en la base) · runtime+metrics 53/53 · check 69 =
base, 0 propios · docs:check 0/623 · prettier limpio en lo que estaba limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
motivo. ~~Al llegar a 0: BORRAR `targetOverride` + `assertTargetOverride` +
retirar el script scratch.~~ **EJECUTADO 2026-08-13** (ver la fila F3).
feat(morfo,soma): la emisión deja de aceptar un elemento anónimo
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>
2 months ago
`allowedTargets` (elección del llamante) y `targetFallback` (estado de
montaje) son dos ejes, precedente `intentRequirement` /`intentGuidance`.
- **F4** — dropdown `open` pre→post (receta float-panel) + decisiones de autor.
docs(process): F5 cerrada — los tres sitios de las tres auditorias, medidos DESPUES de la campana
F5 no era «volver a medir cada ola»: es la comprobacion de regresion del eje
entero sobre los tres sitios que fable S1, sema S-17 y blocks A-36/A-65
encontraron por separado — y ahora con la API de emision ya borrada.
site-header 375px contact-activate TRIGGER +8,4ms · emerge-open CONTENT +10,3ms,
press-squeeze corriendo SOBRE el trigger, close en el content
knob handle-drop y commit-set separados por 245,4 ms
dropdown+Button +109 / +112,7 ms, close en content, commit-select en el item
PULSADO («Log out», 3.º de 3)
El numero que cierra S-17 es 245,4: el commit encolado entra justo tras el hold
de 240 del drop — ni aplastado a los 24 ms que S-17 midio, ni 1,6 s tarde como
midio el falso verde de `queue`. Y los tres sitios son la misma geometria: un
elemento con DOS morfos (trigger+button, control para drop+commit).
Anotados los dos artefactos del entorno para que no vuelvan como hallazgos: con
el panel sin componer, rAF esta suspendido (el handle-drag continuo del knob no
estampa nunca) y la linea de tiempo de las animaciones no avanza (el
desestampado cae SIEMPRE en el tope de 1500 ms de la espera de expresion, de ahi
los ~1,7 s de limpieza en las tres trazas). getAnimations() dice que corre sobre
que; para duracion de pintura hace falta Chrome real.
docs:check 0/623.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **F5 ✅ (2026-08-13)** — navegador, no opcional: los tres sitios que las tres
auditorías encontraron, medidos **después** de la campaña entera y del borrado
de la API (es la comprobación de regresión del eje, no la de cada ola):
```
site-header 375px contact-activate TRIGGER +8,4ms · emerge-open CONTENT +10,3ms
press-squeeze RUNNING sobre el TRIGGER · emerge-close en CONTENT
knob handle-pick · handle-drop +0ms · commit-set +245,4ms (todo en control)
valor 40 → 56, o sea el commit existe porque el gesto cambió algo
dropdown+Button contact-activate TRIGGER +109ms · emerge-open CONTENT +112,7ms
press-squeeze TRIGGER + slide-from-top/scale-in/fade-in CONTENT
emerge-close en CONTENT (Escape) · commit-select en el ítem PULSADO
(«Log out», el 3.º de 3 — no el primero ni el último registrado)
```
El número que cierra S-17 es **245,4 ms** : el `commit-set` encolado entra
justo tras el hold de 240 del `handle-drop` . Ni aplastado a los 24 ms (lo que
S-17 midió), ni 1,6 s tarde (el falso verde de `queue` , §2.5). Los tres sitios
son el mismo elemento con DOS morfos —`[data-drawer-trigger]`+`[data-button]`,
`[data-dropdown-menu-trigger]` +`[data-button]`, `control` para drop+commit— y
en los tres las dos ocurrencias expresan.
⚠️ **Dos artefactos del entorno, no hallazgos** (el panel del navegador no
compone frames en esta sesión): `requestAnimationFrame` está SUSPENDIDO, así
que el `handle-drag` del knob —tick continuo, rAF-throttled— no llega a
estampar nunca y un muestreo por fotograma da 0 muestras; y la línea de tiempo
de las animaciones no avanza, así que `getAnimations()` sirve para decir
*cuál corre sobre qué elemento* pero no cuánto dura, y el desestampado cae
siempre en el tope de 1500 ms de la espera de expresión (de ahí los ~1,7 s de
limpieza en las tres trazas, donde el hold real es 120/240). Para medir
DURACIÓN de pintura hace falta Chrome real.
docs(process): el handoff dice lo que paso, incluido donde el diagnostico viejo mentia
Dos diagnosticos heredados eran falsos y mandaban al sitio equivocado, asi que
se corrigen en su sitio en vez de anadir una nota al final: el crash del guard
de traducciones no era una url multilinea de un catalogo (era el stripper de
comentarios rompiendo un https:// dentro de una cadena) y el guard, ademas,
nunca escaneo los call sites que se le atribuian.
Dos secciones daban ordenes OPUESTAS a §3.0 y llevaban un dia haciendolo: el
hallazgo «chronos estampa fuera del morfo» pedia una decision que C1 ya habia
tomado el mismo dia, y «la cola de la campana» listaba como pendientes nueve
componentes que las olas 2-3 habian cerrado. Marcadas resuelta y congelada, con
los cerrados tachados y remision a la cola viva.
Y se escribe lo aprendido en el eje del nombrado: las cinco clases de cadena,
las cuatro razones por las que un default NO puede subir al contrato (estado,
interpolacion, cross-element, supresion por `<label for>`), la trampa del
spread condicional, la del harness cuyo `translate` diverge de `langs.ts`, y la
regla que resume la clase — antes de mover un default, mira si el elemento es
tuyo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **F6 ✅ (2026-08-11)** — `targetFallback` estaba en el corpus SÓLO como JSDoc:
ni `morfo.md` ni `sema.md` lo nombraban, con 8 componentes ya gobernados por
él. Escrito: `morfo.md` §Step 5.5 (los DOS ejes en tabla, invariantes del
schema, `resolveEmitTarget` única resolución con el foco a11y, el anclado
EXCLUYE el fallback) · `sema.md` §ONE SLOT (el close de los overlays como
mount-state declarado, no `targetOverride` ) · y la aserción de
`pack-census.test.ts` deja de mentir sobre su criterio (nombraba
target+`allowedTargets` mientras `stampPartsOf` contaba TRES fuentes).
⚠️ Nota perceptual escrita en ambos: una parte de `allowedTargets` se
estampa en uso rutinario; una de `targetFallback` sólo en montaje degradado
— una regla de sonido que case SÓLO fallback casi nunca suena (hoy: 0
reglas así; si aparece una, valorar el warn por-evento del census).
feat(morfo,soma): la emisión deja de aceptar un elemento anónimo
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>
2 months ago
feat(soma,morfo)!: targetOverride BORRADO — el estado ilegal del eje es inexpresable
El endgame que §3.0 firmo al abrir F3: con el censo a 0, la opcion de elemento
anonimo sale de TriggerOptions, y con ella assertTargetOverride (existia solo
para vigilarla; queda assertAnchor, el guard de la puerta que la sustituye).
Las dos resoluciones `opts.targetOverride ?? resolveEmitTarget(...)` (emit y
foco a11y) quedan en la resolucion unica, y los Omit<..., 'targetOverride'>
de los triggers anclados se vuelven vacuos y desaparecen.
El tipo lleva LAPIDA doctrinal: la opcion nacio fallbackTarget, se renombro el
2026-08-10 como precondicion del censo, y compensaba un registro sin identidad
de instancia — es lo que dejo a tres auditorias (fable S1, sema S-17, blocks
A-36/A-65) encontrar la misma deriva sin poder cerrarla. No volver a añadir
una opcion de elemento.
Y la tesis se probo sola al ejecutarla: el compilador, ya como censo, cazo 9
usos en TESTS que el grep del scratch nunca escaneo. De ellos, 3 eran
redundantes (la parte registraba el mismo elemento), 2 del test de metrics
migran al registro + trigger plano, y 4 fijaban la conducta borrada — el
describe «targetOverride contract» entero y el test del override preferente,
retirados con lapida; el guard A-36 de los overlays (morfo sin redireccion +
post) se conserva, des-anidado. El scratch del censo, retirado: el censo es
`npm run check`.
Docs vivos adjudicados: morfo.md §allowedTargets describe la puerta anclada
(EventNameTargeting + assertAnchor) y §repeated-part nombra partInstance donde
decia targetOverride; los docblocks de las 3 factorias de vistas de eidos
dicen la forma nueva; errors.ts deja de ofrecer la opcion muerta como remedio.
Las menciones historicas (A-36, «the retired...») se conservan como historia.
Verificado: soma navegador 1278✓/1 (el timeout ajeno preexistente de
soma-attr-audit, documentado en la base) · runtime+metrics 53/53 · check 69 =
base, 0 propios · docs:check 0/623 · prettier limpio en lo que estaba limpio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
~~El censo scratch queda como herramienta de MEDIDA hasta que F3 lo deje en 0 y
se retire.~~ **RETIRADO 2026-08-13** : con la opción borrada del tipo, el censo
es `npm run check` — un error de compilación en vez de un informe. Lo de abajo
(§3.1) se conserva como inventario de hallazgos.
feat(morfo,soma): la emisión deja de aceptar un elemento anónimo
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>
2 months ago
### 3.1 Campaña de emisión — censada, clasificada — SUPERADA POR §3.0
fix(sema,morfo,soma): una superficie no admite dos ocurrencias, y el nombre ya no miente
`data-event-*` es UNA RANURA por elemento. Tres auditorias independientes
encontraron el mismo defecto y ninguna lo cerro: fable S1 (2026-07-01, con el
README diciendo «findings executed»), sema S-17 (2026-08-05, midio el estampado
del knob muriendo a los 24ms de un hold de 240) y blocks A-36/A-65 (reproducido
en navegador, dos estampados a 1,1ms). Lo que faltaba no era diagnostico: nadie
podia distinguir una redireccion legitima de una deriva, porque la opcion que
redirigia se llamaba `fallbackTarget` y hacia lo contrario de lo que decia.
EL NOMBRE PRIMERO. `fallbackTarget` -> `targetOverride` (202 apariciones, 64
ficheros, dirigido con `git ls-files`). Siempre GANO sobre el ref registrado; el
nombre honesto es la precondicion para auditarlo. Las cronicas conservan el viejo
por diseño.
LA PROPIEDAD. `unstampEventAttrs` recibia el signal y lo DESCARTABA, asi que la
ocurrencia que terminaba antes borraba a la que tenia la ranura. Ahora comprueba
`data-event-id`. Con eso, `replace` deja de ser el comportamiento roto y pasa a
ser el suelo correcto.
A-36, SIN `regime`. Los cuatro overlays cedian el estampado del `open` al
trigger por un workaround de la era `sequence: 'pre'`; con `post` el content ya
esta montado. Retirado el override (float-panel migrado `pre`->`post`, ultimo
fuera de la doctrina que sema.md escribe). Medido en /blocks/site-header/preview
a 375px: contact-activate en el TRIGGER +18,7ms y open en el CONTENT +20,6ms,
con press-squeeze y slide-from-right-full corriendo. Antes: los dos en el
trigger, press-squeeze jamas.
`regime` DEFINIDO — y son dos valores, no cuatro. `replace` (default) y `queue`,
este solo para las 4 parejas irreducibles: los tres toggles (el provider ES el
boton) y el knob. `collapse` retirado (su caso murio con la propiedad: medido, 0
huecos en 5 emisiones a 72ms) y `lock` retirado (significaria «un cierre que no
se anuncia»), con sus 4 declaraciones muertas.
El guard de `queue` PASO EN VERDE SIENDO INCORRECTO: con timers falsos no hay
animaciones, asi que awaitExpression volvia al instante. El navegador midio el
commit encolado llegando 1,6s tarde. `queue` espera el HOLD, no la expresion.
Medir la envolvente no es medir la salida.
`renderAttrs` — los attrs que no son partes. Vaciar ACTIVE_DEV_TRACK (palabras y
chronos ya no estan excluidos) destapo 23 `data-palabras-*` que ningun morfo
declaraba y que palabras.css SI estiliza: aterrizan en el arbol que el usuario
escribio, asi que no pueden ser partes. Campo nuevo con la linea afilada —si el
consumidor puede componerlo, es una PARTE— y 3 consumidores (palabras, waveform,
aura). Declarado en types.ts Y en el esquema sium: la leccion de MorfoElement.
Ademas: 5 selectores muertos en el predicado del focus-scope de palabras
(barrido de todo el repo: aparecian solo ahi) · RTL-1 real en palabras-chrome
(inset logico + translate fisico = doble volteo; rtl:check 1->0) · el union
inline al `Direction` canonico, que destapo que el censo de direccion grepea el
NOMBRE del tipo · deuda de chronos (scope, barrel, README).
Y de la cola: S-36 (barrel 68->71 + guard), S-31 (vibrate(NaN)), S5 (la
preferencia de reduced-motion del usuario ya gana a los channels del morfo), SO2
(el warn que el JSDoc prometia), SO4 (validar el target antes del prewrite).
Todos los guards nuevos vistos fallar antes de arreglar nada.
Base: check 74 (= base) · sema+morfo+contracts 454 verdes / 6 ajenos
preexistentes · soma navegador 1258/1259 (el rojo es un timeout ajeno,
reproducido con los cambios en stash) · docs:check 0/618 · rtl:check 0/177.
Handoff: docs/process/CONTINUE-perceptual-surface.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Redirigen el estampado con `targetOverride` y su morfo no lo declara. El guard
del runtime los señala uno a uno al ejecutarse.
**No anotar a ciegas.** Por cada uno, la pregunta primero es *¿sobra la
redirección?* — como en A-36, donde el arreglo fue retirarla. Anotar una
redirección innecesaria la fosiliza. Sólo cuando es legítima (parte repetida: el
`day` pulsado, el `item` clicado) se declara `allowedTargets` .
feat(morfo,soma): la emisión deja de aceptar un elemento anónimo
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>
2 months ago
#### El censo, ahora reproducible
`scripts/__scratch-target-override-census.ts` (SCRATCH, sin commitear):
```
node --import tsx/esm scripts/__scratch-target-override-census.ts
```
Cruza cada `trigger(name, { targetOverride })` de `soma/components` +
`eidos/components` + `soma/layers` contra `target` + `allowedTargets` , **y
resuelve en qué parte aterriza cada override** con el AST de TypeScript en dos
fases. Hace falta interprocedimental: el elemento llega casi siempre como
PARÁMETRO (`select(date, target?)`), y quien lo pasa vive en otro fichero.
Marcas del informe: `~` la parte se dedujo del `e.currentTarget` siguiendo dónde
se cablea el handler · `↑` la trajo un llamante · `«markup»` la trae el template
de un `.svelte` , que el censo NO lee.
**El número real no era 71/33 sino 119 eventos / 46 componentes** (13
declarados). La diferencia no es un error del censo viejo: incluye
`eidos/components` y los nombres computados.
Tres trampas medidas al construirlo, por si se re-hace:
- Resolver el nombre del método **globalmente** importa partes de otro
componente: `select` / `prev` / `next` los tienen doce, y Calendar heredó el
`branch-control` de TreeView. El salto no puede salir del componente.
- `opts.ref.current` como RECEPTOR de una consulta (`…current.querySelector(…)`)
es la raíz donde se busca, no el aterrizaje. Contarlo inventaba un `provider`
en carousel, command y todo el que busca su item desde la raíz.
- `e.currentTarget` NO es la parte de la clase que declara el handler:
`CalendarProvider` aloja el keydown del día y su clase registra `provider` .
Hay que seguir el handler hasta dónde se cablea.
Punto ciego conocido: un override pasado en una VARIABLE
(`trigger(name, opts)`) no se detecta — se mira el texto del segundo argumento.
#### Las cuatro clases
| Clase | Qué | Qué se hace |
| --- | --- | --- |
| **A · no-op** | el override re-dice el destino declarado, que es singleton | RETIRAR — mientras esté, el guard no distingue una redirección real |
| **B · parte repetida** | `day` · `item` · `row` pulsado | nada: la parte YA es el `target` |
| **C · partes hermanas** | `checkbox-item` / `radio-item` junto a `item` ; `prev-trigger` / `next-trigger` junto al provider | declarar `allowedTargets` |
| **D · contradicción** | aterriza donde el morfo no dice, o en algo que NO es parte | decidir: retirar, o extender el morfo |
#### Cerrado en este pase
- **aura** — `delegate-offer` y `delegate-escalate` estampaban con el ref del
provider, que es exactamente lo que el runtime resuelve solo: no-op retirado.
`sustain-processing` (declara `indicator` ) y los tres terminales (declaran
`ring` ) caen al provider cuando la parte decorativa no está montada: override
**sólo en la ausencia** + `allowedTargets: [provider]` en los cuatro.
⚠️ Retirar el override de `sustain-processing` del todo PARECÍA correcto —
«es `post` , sólo se pierde el ornamento»— y el navegador lo desmintió: la
prueba monta el Aura sin Indicator y salieron 10 rechazos de
`SomaRuntimeTargetError` . El ornamento no se pierde en silencio.
- **checkbox** — el provider ES el botón (`archetype: 'trigger'`), así que los
dos handlers le pasaban al runtime el mismo elemento que ya resuelve. Retirado
el parámetro `triggerEl` de `toggle()` y actualizados los 6 llamantes.
- **Verificados SIN cambio** (clase B, el override elige la instancia correcta
de una parte repetida): calendar `commit-select` /`unselect` · carousel ·
clipboard · color-field (dos partes registran `input` ; pasa la visible a
propósito) · command · listbox · radio-group · range-calendar `commit-*` ·
toggle-group · tree-grid · tree-view · month-grid `commit-set` ·
table `commit-select` · tabs · feed · proof-of-human · form `commit-submit` .
#### ⚠️ Clase NUEVA — submenús mudos (onion-menu · dropdown-menu · context-menu)
No es deriva de destino: **el morfo no declaraba el evento** , así que el
provider no tenía qué emitir. Tres componentes, el mismo agujero, y en los tres
la anatomía YA lo anticipaba (`item` de onion-menu declaraba
`states: ['expanded','collapsed']` ; `sub-content` de los menús declaraba
`states: ['open','closed']` y su `data-state` ).
- **onion-menu** — desplegar un submenú, el gesto que DEFINE un menú radial,
emitía nada: `clickItem()` sólo emitía en la rama hoja. Añadidos
`emerge-expand` / `emerge-collapse` sobre `item` , con la forma de TreeView
(misma familia+verbo: un nivel revela sus hijos en el sitio, libro cap. 26).
Tocar fondo del drill sigue siendo `close` , no un collapse.
Medido: `emerge-expand` estampa el sector (depth 1) y desestampa limpio;
`Backspace` → `emerge-collapse` , depths 2→1.
⚠️ Probé primero con `Escape` esperando el collapse y salió `close` : la
tecla de `drill-up` es ** `Backspace` ** según el mapa del propio morfo. El
código estaba bien; la prueba estaba mal.
- **dropdown-menu y context-menu** — `MenuSubProvider.handleOpen/Close` sólo
volteaban estado. Añadidos `sub-open` / `sub-close` sobre `sub-content` en
AMBOS gemelos (arreglar uno solo era la incoherencia que este eje persigue).
Eventos distintos del `open` raíz a propósito: el sub es su propia superficie
flotante, su `commits` escribe su propio `data-state` , y un pack debe poder
distinguir «se abrió el menú» de «se abrió el submenú».
`sub-open` nace ** `post` ** y `sub-close` `pre` + `targetFallback:
[sub-trigger]` — la receta de float-panel, para no repetir A-36 en código
nuevo. Medido: `sub-open` estampa `data-dropdown-menu-sub-content` .
⚠️ `sub-close` NO se ejerció en navegador: su camino es la coordinación
SafePolygon (`onPointerExit`), que un `pointerleave` sintético no dispara.
Queda por medir.
#### ✅ F4 CERRADO (2026-08-10) — el A/B de A-36, ahora en verde
Aplicada la receta de float-panel a **dropdown-menu Y context-menu** (los dos
gemelos; arreglar uno era la incoherencia de siempre): `open` `pre` →`post`,
`close` sigue `pre` + `targetFallback: [trigger]` declarado, y los providers
dejan de resolver `contentRef ?? gestureTarget ?? partRef('trigger')` a mano.
El `gestureTarget` desaparece de la firma: pasarlo era justo lo que pisaba el
`contact-activate` del Button compuesto.
Medido en `/uix/components/dropdown-menu` , el A/B que define el eje:
```
ANTES contact-activate TRIGGER · contact-activate TRIGGER (+1 ms) · open PERDIDO
AHORA contact-activate TRIGGER +9903ms · open CONTENT +9906ms
close CONTENT (Escape)
```
**Dos superficies, ambas expresando.** El `open` ya no se pierde: bajo `pre` su
destino declarado no había montado y no aterrizaba en ningún sitio.
`check` DELTA CERO · 440✓ (ambos menús + morfo + sema) · chronos sigue sano
tras el pase (42 celdas · 17 chips · 3 botones · SSR ok).
#### ⚠️ F4 — cómo se descubrió (medida previa, 2026-08-10)
Instrumentando el submenú salió la prueba del superviviente de A-36: al abrir
el menú raíz se estampan **dos `contact-activate` a 1 ms** sobre
`data-dropdown-menu-trigger` , y el `open` de la raíz **no aparece en absoluto**
— es `pre` con `target: content` , y a esa altura el content no ha montado. No
es que colisione: es que se pierde. La receta de F4 (open `pre` →`post`) sigue
siendo la correcta y ahora está medida, no inferida.
#### ⚠️ Hallazgo — A-36 tiene UN superviviente: `dropdown-menu`
Dos componentes comparten la FORMA del código de A-36 —`open`/`close` con
`target: content` en `sequence: 'pre'` y el provider resolviendo
`contentRef ?? gestureTarget ?? partRef('trigger')` — pero sólo uno comparte el
DEFECTO, y la diferencia está en la composición, no en el provider:
- **`dropdown-menu` SÍ.** `eidos/…/dropdown-menu-trigger.svelte` renderiza el
Trigger de soma por su snippet `child` con un `<Button>` dentro, y su propia
cabecera lo dice: «el `[data-dropdown-menu-trigger]` aterriza en el MISMO
`<button>` que emite Button». Un elemento, dos morfos — la geometría exacta
del Drawer en A-36.
- **`context-menu`, no POR DEFECTO — pero alcanzable.** Su Trigger renderiza un
`<span>` envoltorio, así que lo que el consumidor mete dentro es un HIJO: dos
elementos, no uno. Pero el Trigger acepta snippet `child` , y con él el
consumidor pone su propio control en el MISMO nodo — la colisión vuelve. Y el
`<span>` es a su vez un defecto aparte, ver abajo.
⚠️ Esta distinción salió de MIRAR la composición. Al primer pase los agrupé por
el parecido del provider y di los dos por supervivientes — el mismo error de
clase que dejó vivo a `dropdown-menu` cuando se cerró A-36: casar patrones en
vez de medir.
#### ⚠️ Hallazgo aparte — el Trigger de `context-menu` viola su propio contrato
Encontrado al comprobar lo anterior. Tres fuentes, dos respuestas:
- el morfo declara `defaultElement: 'div'` · `archetype: 'trigger'` ;
- `components/context-menu-trigger.svelte` renderiza un ** `<span>` **, y el README
de soma y la cabecera del componente de eidos lo documentan como `<span>` ;
- no hay decisión escrita en `docs/` que justifique el span.
Y el elemento no cumple lo que su arquetipo promete: sus props son
`data-state` + `data-disabled` + `oncontextmenu` , **sin `tabindex` , sin `role` y
sin `onkeydown` ** — mientras el morfo le cuelga `aria-haspopup="menu"` y
`aria-expanded` , que son estado de widget y no significan nada sobre un elemento
no interactivo ni enfocable. Tampoco hay camino de teclado (APG: Shift+F10 y la
tecla de menú contextual); el componente es sólo ratón / pulsación larga. Los
demás `archetype: 'trigger'` del árbol (checkbox, dialog, dropdown-menu) sí son
controles reales.
Para `dropdown-menu` , la receta ya está escrita en float-panel:
- morfo `open` : `sequence: 'post'` (el content monta durante el trigger y el
trigger conserva su superficie para sus propios eventos).
- morfo `close` : sigue `pre` + `allowedTargets: [v.partRef('trigger')]` .
- provider: el flip de estado pasa a `events: { open, close }` y el `open` emite
**sin override** ; el `close` resuelve `content ?? partRef('trigger')` — sin
`gestureTarget` , que es lo que hoy arrastra `item` / `checkbox-item` /
`radio-item` / `sub-trigger` al aterrizaje.
No lo he tocado: son dos overlays muy usados, el cambio de `pre` a `post` mueve
el `commits` de sitio y §5 exige medirlo en navegador. Es lo siguiente.
#### ⚠️ chronos — el diagnóstico real y la decisión revocada (2026-08-10)
Auditado con `scratchpad/chronos-parts-audit.mjs` : chronos declara **26 partes y
registra UNA** (`provider`). Los ~25 marcadores restantes, y **17 `role=`** , están
escritos a mano en el template de eidos, así que el ARIA declarado en el morfo
(p. ej. `grid` : `aria-labelledby` →heading, `aria-readonly` , `aria-disabled` )
**no llegaba nunca al DOM**. Por eso chronos dependía de `targetOverride` : sin
registro, `partInstance` devuelve `null` para todo.
**Decisión revocada.** `29ffe902a` (2026-07-13) fijó «reuse of a piece of chronos
rides the pure engine, not a public compound API». La mitad de REUTILIZACIÓN es
correcta y se mantiene (un componente no es librería de otro). La mitad que el
comentario de `exports.ts` extendió a «no public compound parts» como regla de
API de consumidor **se anula** : contradecía la doctrina composicional
(`feedback-compositional-not-data-driven`, con precedente de rechazo en
OnionMenu), dejaba a chronos como el ÚNICO de su clase con estructura cerrada
(Table y Calendar exponen la suya), y produjo el monolito que causó lo de arriba.
Su cautela declarada («hasta que RRULE/resources estabilicen las firmas») ya
había caducado. Los datos siguen siendo datos (como el `TableInstance` de Table);
lo que se abre es la ESTRUCTURA.
**Hecho: `event-chip` de punta a punta** — `ChronosEventChipProvider` + componente
de soma + barrels + recableado del chip de mes. Cadena
`Tooltip.Trigger → EventChip → DragDrop.Draggable → un solo elemento` , con los
tres morfos fundidos en él. Medido en navegador (17 chips): el contrato sale ya
del morfo — `role` , `aria-label` , `data-event-id` y ** `data-color` , que no se
había emitido nunca**. Consola limpia; 228✓; `check` sin errores nuevos.
⚠️ **Y el navegador cazó un defecto que la declaración escondía** : el morfo daba
al chip `defaultElement: 'button'` + `type` literal, y el primer render por el
runtime puso `type="button"` sobre un `<div>` . La causa es de anatomía, no de
cableado: **el chip CONTIENE el asa de redimensionado** , y el modelo de contenido
de `<button>` no admite un control anidado. Corregida la DECLARACIÓN (div +
`role='button'` , `tabindex` propiedad de soma), no el DOM. Era invisible mientras
nadie registraba la parte.
**C1 CERRADO (2026-08-10)** — las 6 partes que emiten registran ya:
`event-chip` · `event-resize-handle` · `day-cell` (× 42) · `prev` /`next`/
`today-button` . Cuatro clases de provider (los tres botones comparten una:
mismo contrato, parte distinta — patrón de paginación de Calendar), cuatro
componentes de soma, barrels, y la vista recableada por `child` .
**chronos ya no usa `targetOverride` en ninguna emisión** (censo global 99 → 91):
- `shift-navigate` — redirección RETIRADA. El morfo declara `provider` sin
`allowedTargets` ; ceder el botón pulsado era deriva (lección A-36).
- `commit-save` / `commit-delete` / `commit-move` — anclados en el CHIP que
nombran vía `partInstance` , con `targetFallback: [provider]` declarado para
cuando no hay chip (un alta, o el borrado que acaba de quitarlo). Esto
responde al bloqueo original: los botones Guardar/Eliminar son `IconButton`
compuestos dentro de un `Dialog` compuesto y no son partes de chronos — el
SPEC ya decía que el morfo del editor lo aporta Dialog.
- `commit-restore` / `commit-apply` — parámetros muertos retirados.
Medido en navegador: las 4 partes con `id` del runtime, `aria-label` traducido
saliendo del morfo («Hoy», «lunes, 1 de junio de 2026»), 0 errores de consola,
0 quinientos. 260✓ (6 fallos = base ajena de `contracts.test.ts` ); check DELTA
CERO.
⚠️ **Trampa de verificación medida dos veces** : al exportar una parte nueva del
barrel, el grafo de módulos SSR de Vite va por detrás y la página revienta con
`X is not defined` (500) mientras el cliente ya renderiza bien. Se resuelve
navegando de nuevo. No confundirlo con un fallo del código — y NO reiniciar el
dev server (ver [[reference-dev-server-restart-orphans-browser]]).
**C2 CERRADO (2026-08-10)** — clase `ChronosRegionProvider` + componente
`Region` para los 8 singletons estructurales (una clase para todos, como
`SidebarRegionProvider` : misma forma, parte distinta, sin ramas). Migrados
**toolbar · heading · grid · grid-head · grid-body · time-grid · all-day-row ·
agenda**, medidos en las TRES vistas (mes 42 celdas · semana 168 slots ·
agenda 18 grupos), porque cada vista monta regiones distintas y una sola no
las cubre.
Y con eso **el contrato ARIA del morfo está vivo por primera vez** . Medido en
`/uix/components/chronos` :
```
grid → role="grid" · aria-readonly="false" · aria-disabled="false"
aria-labelledby="uix-chronos-region-c519" → «junio de 2026»
grid-head → role="row" (el template ya no lo escribe)
grid-body → role="rowgroup" (idem)
```
`aria-readonly` y `aria-disabled` **no se habían emitido nunca** . Y los `role`
de grid-head/grid-body se han BORRADO del template: los declara el morfo, que
es el objetivo de C2.
⚠️ **La trampa que costó el hallazgo, y que se repetirá en C3** : registrar la
parte NO basta para un `partRef` . `aria-labelledby` en `grid` es
`v.partRef('heading')` gateado por `part-present` , y un `partRef` lee el bag
`parts` **del provider** , no el registro. Chronos construía su runtime con
`{}` , así que el grid registraba, su `role` y sus dos aria aparecían, y
`aria-labelledby` se quedaba **nulo en silencio** . Arreglado publicando
`headingId` como fuente (`parts: { heading: … }`), que es lo que Dialog hace
desde siempre. Cualquier parte que otra referencie por `partRef` necesita lo
mismo.
⚠️ **La misma trampa mordió DOS veces, por el otro extremo** : omitir una fuente
que la parte SÍ declara hace desaparecer el atributo en silencio. Al quitar el
`aria-label` a mano del `time-grid` —que el morfo declara como
`propRef('ariaLabel')` — la región se quedó sin nombre accesible hasta añadir
`ariaLabel` al bag de props del Region. Igual con el `aria-label` del grid, que
depende de que `heading` esté registrado. **Regla** : al migrar una parte, cruzar
sus `aria` /`data` declarados con las fuentes que el provider publica, y
comprobar el VALOR en el DOM — no que el atributo exista.
Por eso la medida fue en las tres vistas: el `time-grid` y el `all-day-row` sólo
existen en semana/día, y la agenda en la suya. Verificar en la vista por defecto
habría dado verde con dos regiones sin comprobar.
**C3 EN CURSO** — las repetidas se parten en dos por una línea que conviene ver:
- **Sin fuentes de valor** (sólo `role` , o un attr literal): `grid-row` ,
`head-cell` , `now-indicator` . **Migradas** reusando la MISMA clase `Region` ,
sin componente nuevo: el registro por instancia de F2 es lo que hace segura
una clase para muchos nodos. Medido: 6 filas + 7 cabeceras registradas por
instancia, con `role="row"` / `role="columnheader"` saliendo del morfo.
- **Con `propRef` ** (necesitan valores por instancia): en vez de una clase por
parte, `Region` recibe un bag `sources` — un **thunk, no una instantánea** ,
porque el runtime lo re-lee y así los attrs siguen siendo reactivos cuando el
mes cambia debajo. Migrados así ** `day-number` ** (42/42, `data-value` desde el
`propRef` ), ** `day-column` **, ** `time-slot` ** y ** `agenda-group` **.
- **`agenda-item`**: clase propia (`ChronosAgendaItemProvider` + componente),
porque renderiza un `<button>` REAL — su morfo declara `role` + el literal
`type` , y a diferencia del chip del mes no anida ningún control, así que aquí
el elemento nativo es el honesto.
feat(morfo,soma,eidos): chronos deja de tener partes que el morfo no conoce
C3 cierra con `more-link` y C4 con los seis marcadores que eidos estilizaba
sin declaracion. Los tres botones sin fuentes (`day-add`, `edit-action`,
`search-button`) comparten una clase; `undo`/`redo` se cablean sobre la del
nav; `timegrid` pasa a `time-view` porque estaba a un guion de la parte
`time-grid` que vive dentro; `peek-panel` y `day-peek` quedan como partes de
display puro con marcador inline. Cada una renderiza un `<button>` real
cuando el morfo lo declara: una `Region` que cae a `<div>` pondria el `type`
declarado sobre un no-boton.
La lupa deja de ser un adorno sin `onclick`: abre una paleta `Command.Dialog`
sobre TODOS los eventos, no un filtro de la vista, con `goToDate` nuevo en el
provider — la generalizacion de la que `goToday` era un caso particular. El
texto buscable viaja en `keywords` porque el scorer de Command lee value +
keywords y NUNCA los hijos renderizados, asi que se busca por titulo, por tipo
y por fecha.
Y tres etiquetas que mentian en silencio: `translationRef` / `commonRef`
normalizan la clave LITERAL y no consultan ningun mapa, asi que
`prevMonth` / `nextMonth` / `moreEvents` y `action.undo` / `action.redo` caian
al fallback ingles en una pagina `es`. Corregidas contra el catalogo, con
`common.buttons.undo` / `redo` anadidas.
Verificado: check 74 = base (delta cero) · 458 vitest · docs:check 0/621 ·
rtl:check 0/177 · eidos-lint chronos morfo-backed 32 -> 44, invalid 0 ·
navegador limpio en las tres vistas, con el A/B del undo medido
(18 jun -> drag -> 25 jun -> undo -> 18 jun -> redo -> 25 jun) y el salto de
la paleta (noviembre -> elegir Retro -> junio).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**C3 CERRADO 9/9 (2026-08-10)** — `more-link` migrado. Clase propia
(`ChronosMoreLinkProvider` + componente), NO `Region` : el precedente es
`agenda-item` — renderiza un `<button>` real y `Region` cae a `<div>` , que
pondría el `type` declarado sobre un no-botón (el defecto que destapó el chip).
Cadena `Popover.Trigger → MoreLink → Button → un solo elemento` .
⚠️ El bag del popover va **el último** en el spread: el `aria-labelledby` de su
content cae al id del trigger, así que esa identidad tiene que ganar.
Medido en `/uix/components/chronos` : `data-count="6"` (nunca se había emitido),
`role` , `type` y `aria-label` del morfo conviviendo con `aria-haspopup` /
`aria-expanded` / `aria-controls` / `data-state` del popover en el MISMO nodo.
`check` 74 = base, DELTA CERO · 458✓.
⚠️ **CUARTA declaración falsa destapada al registrar** — y esta no es de chronos:
`v.translationRef('moreEvents')` **normaliza la clave literal**
(`compile.ts:normalizeTranslationRef` → `#?components.chronos.moreEvents` ), NO
consulta el mapa `texts` del morfo. El catálogo la tiene como `more-events` , así
que el lookup falla y sale el FALLBACK inglés. Medido con la página en `es` :
```
today-button aria-label="Hoy" ← clave 'today', coincide con el catálogo
prev-button aria-label="Previous month" ← 'prevMonth' ≠ 'prev-month' → fallback
next-button aria-label="Next month" ← 'nextMonth' ≠ 'next-month' → fallback
more-link aria-label="Show more events" ← 'moreEvents' ≠ 'more-events' → fallback
day-add aria-label="Nuevo evento" ← eidos tr() con clave guionada: SÍ traduce
```
El guionado no es el problema: `tr()` lo resuelve. La regla real es que
`translationRef` exige **la clave del catálogo** , y un alias camelCase muere en
docs(process): el handoff dice lo que paso, incluido donde el diagnostico viejo mentia
Dos diagnosticos heredados eran falsos y mandaban al sitio equivocado, asi que
se corrigen en su sitio en vez de anadir una nota al final: el crash del guard
de traducciones no era una url multilinea de un catalogo (era el stripper de
comentarios rompiendo un https:// dentro de una cadena) y el guard, ademas,
nunca escaneo los call sites que se le atribuian.
Dos secciones daban ordenes OPUESTAS a §3.0 y llevaban un dia haciendolo: el
hallazgo «chronos estampa fuera del morfo» pedia una decision que C1 ya habia
tomado el mismo dia, y «la cola de la campana» listaba como pendientes nueve
componentes que las olas 2-3 habian cerrado. Marcadas resuelta y congelada, con
los cerrados tachados y remision a la cola viva.
Y se escribe lo aprendido en el eje del nombrado: las cinco clases de cadena,
las cuatro razones por las que un default NO puede subir al contrato (estado,
interpolacion, cross-element, supresion por `<label for>`), la trampa del
spread condicional, la del harness cuyo `translate` diverge de `langs.ts`, y la
regla que resume la clase — antes de mover un default, mira si el elemento es
tuyo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
silencio. ~~**`npm run translations:check` —el guard que lo cazaría— CASCA**~~
**CORREGIDO 2026-08-11**: el crash era el stripper de comentarios rompiendo un
`https://` dentro de una cadena (no una «url multilínea»), y además el guard
nunca escaneó las llamadas `translationRef` /`commonRef` — esta clase pasaba en
verde con el script sano. Reescrito sobre el contrato compilado y censados los
166 morfos (§0 punto 3): 172 hallazgos arreglados, 0 errores.
feat(morfo,soma,eidos): chronos deja de tener partes que el morfo no conoce
C3 cierra con `more-link` y C4 con los seis marcadores que eidos estilizaba
sin declaracion. Los tres botones sin fuentes (`day-add`, `edit-action`,
`search-button`) comparten una clase; `undo`/`redo` se cablean sobre la del
nav; `timegrid` pasa a `time-view` porque estaba a un guion de la parte
`time-grid` que vive dentro; `peek-panel` y `day-peek` quedan como partes de
display puro con marcador inline. Cada una renderiza un `<button>` real
cuando el morfo lo declara: una `Region` que cae a `<div>` pondria el `type`
declarado sobre un no-boton.
La lupa deja de ser un adorno sin `onclick`: abre una paleta `Command.Dialog`
sobre TODOS los eventos, no un filtro de la vista, con `goToDate` nuevo en el
provider — la generalizacion de la que `goToday` era un caso particular. El
texto buscable viaja en `keywords` porque el scorer de Command lee value +
keywords y NUNCA los hijos renderizados, asi que se busca por titulo, por tipo
y por fecha.
Y tres etiquetas que mentian en silencio: `translationRef` / `commonRef`
normalizan la clave LITERAL y no consultan ningun mapa, asi que
`prevMonth` / `nextMonth` / `moreEvents` y `action.undo` / `action.redo` caian
al fallback ingles en una pagina `es`. Corregidas contra el catalogo, con
`common.buttons.undo` / `redo` anadidas.
Verificado: check 74 = base (delta cero) · 458 vitest · docs:check 0/621 ·
rtl:check 0/177 · eidos-lint chronos morfo-backed 32 -> 44, invalid 0 ·
navegador limpio en las tres vistas, con el A/B del undo medido
(18 jun -> drag -> 25 jun -> undo -> 18 jun -> redo -> 25 jun) y el salto de
la paleta (noviembre -> elegir Retro -> junio).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
⚠️ **Label-in-Name (WCAG 2.5.3) en `more-link`** : el texto visible es
`{count} more…` (además SIN traducir, a diferencia del resto del fichero) y el
nombre accesible pasa a ser el `aria-label` del morfo, que no lleva el número.
Decisión de C4: o el `aria-label` se hace `propRef` con el conteo, o se retira y
nombra el contenido — pero entonces el texto visible tiene que traducirse.
feat(morfo,soma): la emisión deja de aceptar un elemento anónimo
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>
2 months ago
`ChronosRegionProps` admite `class` y `style` explícitos: el morfo posee
`role` /aria, la receta posee la caja. Declarados uno a uno en vez de tragados
por la firma de índice, para que el reparto se lea en el tipo.
#### ⚠️ REGISTRAR UNA PARTE DESTAPA DECLARACIONES FALSAS — tres veces ya
El patrón del pase, y hay que contar con él en lo que queda: mientras nadie
registraba la parte, el morfo podía mentir sin consecuencia. En cuanto el
runtime emite lo declarado, la mentira se materializa.
| Parte | Declaraba | Qué pasó al registrarla |
| --- | --- | --- |
| `event-chip` | `defaultElement: 'button'` + literal `type` | `type="button"` sobre un `<div>` — el chip ANIDA el asa de resize y el modelo de contenido de `<button>` no lo admite |
| `grid-row` | `archetype: 'item'` | **La semana entera se pintó como fila de lista** : `archetypes.css` da a `[data-archetype='item']` cursor pointer, suelo de altura y FONDO EN HOVER. Una semana es estructura; el item es la celda |
| `grid` / `time-grid` | aria por `propRef` | el atributo desaparecía en SILENCIO si el provider no publicaba la fuente |
En los tres casos el arreglo fue **la declaración** , no el DOM ni el CSS. Y sólo
el del `grid-row` era visible a simple vista — los otros dos exigieron mirar el
valor concreto en el DOM.
#### ✔ El botón «Hoy» NO tiene bug (verificado 2026-08-10)
Reportado como que sólo movía el día y no el mes. **Medido: desde noviembre de
2026 → pulsar Hoy → agosto de 2026.** Correcto.
Lo que despista: **hoy es 2026-08-10** y el demo arranca en junio porque le pasan
`date` fijo. Estando ya en agosto, Hoy no tiene mes que cambiar y sólo mueve el
foco. ⚠️ Mi primera medición dio un falso positivo por esto mismo — avancé dos
meses desde junio y aterricé sin darme cuenta en el mes actual. **Al probar
navegación temporal, salir primero del mes de hoy.**
#### Estado de chronos al cerrar la sesión del 2026-08-10
| | |
| --- | --- |
| C1 · partes que emiten | ✅ (6) — chronos con CERO `targetOverride` |
| C2 · singletons | ✅ (8) — medidos en las TRES vistas |
feat(morfo,soma,eidos): chronos deja de tener partes que el morfo no conoce
C3 cierra con `more-link` y C4 con los seis marcadores que eidos estilizaba
sin declaracion. Los tres botones sin fuentes (`day-add`, `edit-action`,
`search-button`) comparten una clase; `undo`/`redo` se cablean sobre la del
nav; `timegrid` pasa a `time-view` porque estaba a un guion de la parte
`time-grid` que vive dentro; `peek-panel` y `day-peek` quedan como partes de
display puro con marcador inline. Cada una renderiza un `<button>` real
cuando el morfo lo declara: una `Region` que cae a `<div>` pondria el `type`
declarado sobre un no-boton.
La lupa deja de ser un adorno sin `onclick`: abre una paleta `Command.Dialog`
sobre TODOS los eventos, no un filtro de la vista, con `goToDate` nuevo en el
provider — la generalizacion de la que `goToday` era un caso particular. El
texto buscable viaja en `keywords` porque el scorer de Command lee value +
keywords y NUNCA los hijos renderizados, asi que se busca por titulo, por tipo
y por fecha.
Y tres etiquetas que mentian en silencio: `translationRef` / `commonRef`
normalizan la clave LITERAL y no consultan ningun mapa, asi que
`prevMonth` / `nextMonth` / `moreEvents` y `action.undo` / `action.redo` caian
al fallback ingles en una pagina `es`. Corregidas contra el catalogo, con
`common.buttons.undo` / `redo` anadidas.
Verificado: check 74 = base (delta cero) · 458 vitest · docs:check 0/621 ·
rtl:check 0/177 · eidos-lint chronos morfo-backed 32 -> 44, invalid 0 ·
navegador limpio en las tres vistas, con el A/B del undo medido
(18 jun -> drag -> 25 jun -> undo -> 18 jun -> redo -> 25 jun) y el salto de
la paleta (noviembre -> elegir Retro -> junio).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| C3 · repetidas | ✅ **9 / 9** |
| C4 · morfo | ✅ **7 / 7** |
| C5 · composición pública | **rebanadas 1-2 de 4** — toolbar + overlays |
feat(morfo,soma): la emisión deja de aceptar un elemento anónimo
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>
2 months ago
feat(morfo,soma,eidos): chronos deja de tener partes que el morfo no conoce
C3 cierra con `more-link` y C4 con los seis marcadores que eidos estilizaba
sin declaracion. Los tres botones sin fuentes (`day-add`, `edit-action`,
`search-button`) comparten una clase; `undo`/`redo` se cablean sobre la del
nav; `timegrid` pasa a `time-view` porque estaba a un guion de la parte
`time-grid` que vive dentro; `peek-panel` y `day-peek` quedan como partes de
display puro con marcador inline. Cada una renderiza un `<button>` real
cuando el morfo lo declara: una `Region` que cae a `<div>` pondria el `type`
declarado sobre un no-boton.
La lupa deja de ser un adorno sin `onclick`: abre una paleta `Command.Dialog`
sobre TODOS los eventos, no un filtro de la vista, con `goToDate` nuevo en el
provider — la generalizacion de la que `goToday` era un caso particular. El
texto buscable viaja en `keywords` porque el scorer de Command lee value +
keywords y NUNCA los hijos renderizados, asi que se busca por titulo, por tipo
y por fecha.
Y tres etiquetas que mentian en silencio: `translationRef` / `commonRef`
normalizan la clave LITERAL y no consultan ningun mapa, asi que
`prevMonth` / `nextMonth` / `moreEvents` y `action.undo` / `action.redo` caian
al fallback ingles en una pagina `es`. Corregidas contra el catalogo, con
`common.buttons.undo` / `redo` anadidas.
Verificado: check 74 = base (delta cero) · 458 vitest · docs:check 0/621 ·
rtl:check 0/177 · eidos-lint chronos morfo-backed 32 -> 44, invalid 0 ·
navegador limpio en las tres vistas, con el A/B del undo medido
(18 jun -> drag -> 25 jun -> undo -> 18 jun -> redo -> 25 jun) y el salto de
la paleta (noviembre -> elegir Retro -> junio).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
#### C4 — hecho (2026-08-10)
Los 6 marcadores `data-chronos-*` que el morfo no conocía, resueltos así:
| Marcador | Verdicto | Cómo |
| --- | --- | --- |
| `day-add` | **PARTE pública** | Registrada: `ChronosDayAddProvider` + componente `<DayAdd>` ; el `<IconButton>` de la vista se compone por `child` . El `aria-label` a mano DESAPARECE — sale del morfo. 42 instancias medidas |
| `timegrid` → ** `time-view` ** | **PARTE privada, RENOMBRADA** | Era el shell de semana/día, a UN guion de la parte `time-grid` que vive dentro: trampa. Marcador inline (parte de display puro), 4 reglas CSS renombradas |
| `peek-panel` · `day-peek` | **PARTES privadas** | Sin role ni aria: marcador inline, que es la forma sancionada para una región estructural que no debe ser class-hook |
| `edit-action` | **PARTE pública, registrada** | `ChronosActionProvider` + `<EditAction>` sobre el `IconButton` del editor. Ver decisión 1 |
| `search-button` | **PARTE pública, registrada** | Abre una paleta `Command.Dialog` sobre TODOS los eventos. Ver decisión 2 |
⚠️ **La premisa del plan era falsa en dos puntos, medidos** :
1. Decía «las 5 partes reales **que el CSS de eidos estiliza** ». `search-button`
**no tiene ni una regla CSS ni un `onclick`** en todo el repo: una sola
aparición, el atributo. No es una parte por declarar, es una lupa que no
hace nada.
2. Suponía que estos marcadores estaban en rojo. `eidos-lint chronos` medido
antes y después (restaurando morfo+css de HEAD): ** `invalid: 0` en ambos**.
Los clasificaba como *eidos-only* , no como inválidos. Lo que cambia es
**morfo-backed 32 → 43** (y eidos-only 53 → 42): el contrato pasa de señal
DOM sin declarar a declarado. Ese es el valor, no un guard en rojo.
⚠️ ** `class-hooks: 115` ** en chronos, sin cambio. La norma S1 de eidos-lint los
prohíbe (`component:audit` exige 0). Es la familia `.chronos-tg-*` /
`.chronos-peek-*` entera: deuda grande, PREEXISTENTE, y un eje propio.
**Y de paso, el arreglo de i18n medido** (§C3): con las claves del catálogo,
`prev` /`next`/`more-link` pasan de fallback inglés a ** «Mes anterior» ·
«Mes siguiente» · «Ver más eventos»** en una página `es` .
Verificado: `check` **74 = base, DELTA CERO** · **458 ✓** · `contracts.test.ts`
los MISMOS 6 fallos ajenos (ninguna fila de chronos) · `docs:check` 0/621 ·
`rtl:check` 0/177 · navegador en las TRES vistas + el peek abierto, **consola
limpia en pestaña nueva**.
⚠️ **Dos trampas de verificación en este pase** :
- `prettier --check` sobre una copia del fichero FUERA del proyecto no valida
nada: no resuelve el plugin de svelte y **falla parseando** , lo que se lee
como «pasa» si sólo miras la lista de warns. `chronos-view.svelte` YA estaba
sucio en HEAD; lo descubrí sólo al comprobarlo in situ.
- `prettier --write` sobre él lo pasó a LF y produjo un diff de **719 líneas**
([[reference-git-crlf-wholefile-diff]]). Revertido y reaplicados los tres
cambios a mano: el diff real es **34 líneas** . No formatear ficheros que ya
estaban sucios.
#### C4 — las 3 decisiones, resueltas por el autor (2026-08-10)
**1 · `edit-action` → DECLARADA.** El plan decía retirarla; el autor decidió lo
contrario y es lo coherente: retirarla no era ejecutable (su única regla CSS la
posiciona en la esquina del diálogo, y cambiar el gancho por una clase es justo
lo que la norma S1 prohíbe), y el precedente del mismo día lo contradecía —
`more-link` dentro de un Popover compuesto y `day-add` dentro de un IconButton
compuesto SON partes. Alcanzar un componente compuesto con **tu propio attr con
namespace** es el patrón sancionado. Medido: `role` · `type` ·
`aria-label="Editar evento"` del morfo, la caja sigue a 20/20 px de la esquina
del diálogo (medido contra el rect real), y el click sigue abriendo la edición
(19 campos `data-readonly` → 0).
**2 · `search-button` → PARTE + PALETA sobre TODOS los eventos.** No había spec
en ninguna parte (ni `SPEC.md` ni `CONTINUE.md` ; el README sólo lo mencionaba
como composición) y en soma/engine no había una línea de búsqueda, así que la
conducta la eligió el autor: paleta que busca en todo el almacén, no filtro de
la vista. Construido con ** `Command.Dialog` del propio framework** — que YA
existe (paleta modal sobre el Dialog de eidos, con `mod+k` de serie) y que
estuve a punto de recomponer a mano: leer el barrel antes de escribir markup.
- Provider: ** `goToDate(date)` **, la generalización de la que `goToday` era un
caso particular (mismo ancla rovinga, anuncio y emisión `shift-navigate` ).
- El texto buscable viaja en ** `keywords` **: el scorer de Command lee
`value` + `keywords` y **NUNCA** los hijos renderizados, y el `value` es el id
del evento. Por eso se busca por título, por tipo Y por fecha.
- Una serie recurrente sale UNA vez, igual que `openEdit` , que edita la serie.
Medido: `ctrl+k` ✓ · `meta+k` ✓ · el botón ✓ · 18 eventos listados con
«Título — 8 jun 2026 · Todo el día» · filtro por título («trip» → 1) y **por
fecha** («18 jun» → Retro) · vacío traducido · y el salto de marco:
**noviembre de 2026 → elegir «Retro» → junio de 2026** con el editor abierto.
⚠️ **Lo que NO se pudo verificar** : que la paleta se DESMONTE al elegir. Su
`data-state` pasa a `closed` (correcto), pero el nodo permanece porque **el
panel oculto congela las animaciones de salida** — medido: `document.hidden =
true` y `scale-out` /`fade-out` en `running` con `currentTime: 0` . Control: el
diálogo del editor tampoco cierra con Escape en ese estado (22 animaciones
congeladas). Es el entorno, no el código — pero queda por confirmar con el
panel visible.
**3 · `undo-button` / `redo-button` → CABLEADOS.** `ChronosNavPart` pasa de 3 a
5 partes (misma clase, el patrón de paginación de Calendar) y la barra los
renderiza en su propio `chronos-nav-group` . Medido de punta a punta:
```
Retro, 18 jun 2026 → drag → 25 jun → undo → 18 jun → redo → 25 jun
undo deshabilitado al arrancar (aria-disabled=true + data-disabled), habilitado tras el move
```
⚠️ **Y destapó la MISMA clase de fallo de traducción, tercera vez** : los dos
declaraban `v.commonRef('action.undo')` y ** `common.action` no existe** — el
grupo es `common.buttons` . Añadidos `buttons.undo` / `buttons.redo`
(«Deshacer» / «Rehacer») y repuntadas las declaraciones. `docs:vocabularies`
regenerado (38 → 40 shared strings), que es lo que puso `docs:check` en rojo
hasta regenerarlo.
⚠️ **La abstracción llegó a sus dos consumidores** : `ChronosDayAddProvider` pasa
a `ChronosActionProvider` con parte `'day-add' | 'edit-action'` — mismo
contrato (botón real, role + type + aria-label traducido, CERO fuentes), el
patrón de `NavButton` . Los componentes siguen siendo dos con nombre propio
(`DayAdd`, `EditAction` ), que es lo que C5 necesita para la API compuesta.
feat(eidos): chronos abre su estructura, y el cuerpo por defecto la estrena
C5 rebanada 1 de 4. Que partes son publicas no es cuestion de gusto: es
`kind` en el morfo (21 publicas, 12 privadas). Namespace `Chronos.*` con la
forma de Table/Calendar, y la raiz gana ranura de composicion — pasar
`children` REEMPLAZA el cuerpo por defecto. Antes se renderizaban ADEMAS de
la vista completa, un segundo arbol colgando de un calendario entero; ningun
consumidor los pasaba.
Ocho partes de toolbar (`Toolbar`, `Heading`, `Prev`/`Next`/`Today`/`Undo`/
`Redo`/`SearchButton`), ergonomicas al estilo `Calendar.PrevButton`: componen
el IconButton/Button del sistema, su glifo y la llamada al provider, asi que
el consumidor recibe un control que FUNCIONA y no un gancho vacio. El cuerpo
por defecto se recompone desde ellas — no es un camino privilegiado, es el
primer consumidor de la API compuesta.
`searchOpen` sube al provider: en un toolbar compuesto el boton y la paleta
viven en subarboles distintos y el provider es el unico sitio que alcanzan
los dos. Y la prueba compuesta destapo un hueco real — el SearchButton
compuesto no abria nada porque la paleta vivia en el cuerpo por defecto. Los
overlays pertenecen a la RAIZ: extraida a `chronos-search-palette.svelte`.
El Dialog del editor tiene el mismo hueco y sigue dentro de la vista; es lo
primero de la rebanada 2.
Dos declaraciones falsas mas, del patron de siempre: `toolbar` declaraba
`header` y se pintaba un `div`; `heading` declaraba `div` y se pintaba un
`span`. Los wrappers renderizan lo DECLARADO, medido sin desplazamiento.
Medido con dos instancias en la misma pagina: next en la compuesta mueve solo
la compuesta (junio -> julio), prev en la de por defecto mueve solo esa
(junio -> mayo), ids de heading distintos, y la paleta de cada raiz lista sus
18 eventos por separado. check 74 = base · 458 vitest · morfo:check PASS
chronos · docs:check 0/621 · rtl:check 0/177 · eidos-lint invalid 0 · consola
limpia en las tres vistas.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
#### C5 — composición pública, rebanada 1 de 4 (2026-08-11)
**Qué partes son públicas NO es cuestión de gusto: es `kind` en el morfo.**
21 públicas · 12 privadas (los internos del time-grid, las filas de agenda, las
superficies del day-peek). Ese es el criterio, y evita inventarse una API.
Hecho en esta rebanada:
- **Namespace `Chronos.*` ** con la forma de Table/Calendar (`index.ts`), y la
raíz con **ranura de composición** : pasar `children` **REEMPLAZA** el cuerpo
por defecto. Antes se renderizaban *además* de la vista completa —un segundo
árbol colgando de un calendario entero—; ningún consumidor los pasaba.
- **8 partes de toolbar**: `Toolbar` · `Heading` · `PrevButton` · `NextButton` ·
`TodayButton` · `UndoButton` · `RedoButton` · `SearchButton` . Ergonómicas al
estilo `Calendar.PrevButton` : componen el `IconButton` /`Button` del sistema,
su glifo y la llamada al provider, así que el consumidor recibe un control que
FUNCIONA, no un gancho vacío. `children` sustituye el cuerpo de cada una.
- **El cuerpo por defecto se recompone desde ellas.** No es un camino
privilegiado: es el primer consumidor de la API compuesta. Sólo queda literal
el cromo que no es parte del morfo (insignia de fecha, bloque de título,
select de vista).
- **`searchOpen` sube al provider.** En un toolbar compuesto el botón y la
paleta viven en subárboles distintos; el provider es el único sitio que
alcanzan los dos. Es estado de componente, como `editing` .
- ⚠️ **Y la prueba compuesta destapó un hueco real** : el `SearchButton`
compuesto **no abría nada** , porque la paleta vivía en el cuerpo por defecto.
Los overlays pertenecen a la RAÍZ. Extraída a `chronos-search-palette.svelte`
y renderizada siempre.
#### C5 — rebanada 2 de 4: los overlays a la raíz (2026-08-11)
El editor tenía el mismo hueco que la paleta y se cierra igual: extraído a
`chronos-editor.svelte` (Dialog + `editorCtx` + `save` /`remove`) y renderizado
por la RAÍZ. Un árbol compuesto que coloque `EditAction` o celdas de día —
partes públicas— ya recibe editor cuando algo llama a `openEdit` /`openCreate`;
antes, sencillamente, no tenía ninguno. `chronos-view.svelte` baja de **1199 a
893 líneas** y pierde 7 imports que la extracción dejó huérfanos.
Medido con el discriminador que sí distingue de quién es el editor (el portal
del Dialog hace que `contains()` no sirva): mover **sólo la instancia
compuesta** 5 meses y luego elegir desde SU paleta un evento de junio.
```
away → [junio (defecto), noviembre (compuesta)]
elegir «Retro» en la paleta de la COMPUESTA
after → [junio (defecto), junio (compuesta)] ← la de por defecto NUNCA se movió
1 editor abierto · title «Evento» · EditAction presente
```
Es decir: el handler corrió en el provider de la instancia compuesta, y el
editor que apareció es el suyo. `check` 74 = base · 458 ✓ · consola limpia.
feat(eidos): chronos abre su estructura, y el cuerpo por defecto la estrena
C5 rebanada 1 de 4. Que partes son publicas no es cuestion de gusto: es
`kind` en el morfo (21 publicas, 12 privadas). Namespace `Chronos.*` con la
forma de Table/Calendar, y la raiz gana ranura de composicion — pasar
`children` REEMPLAZA el cuerpo por defecto. Antes se renderizaban ADEMAS de
la vista completa, un segundo arbol colgando de un calendario entero; ningun
consumidor los pasaba.
Ocho partes de toolbar (`Toolbar`, `Heading`, `Prev`/`Next`/`Today`/`Undo`/
`Redo`/`SearchButton`), ergonomicas al estilo `Calendar.PrevButton`: componen
el IconButton/Button del sistema, su glifo y la llamada al provider, asi que
el consumidor recibe un control que FUNCIONA y no un gancho vacio. El cuerpo
por defecto se recompone desde ellas — no es un camino privilegiado, es el
primer consumidor de la API compuesta.
`searchOpen` sube al provider: en un toolbar compuesto el boton y la paleta
viven en subarboles distintos y el provider es el unico sitio que alcanzan
los dos. Y la prueba compuesta destapo un hueco real — el SearchButton
compuesto no abria nada porque la paleta vivia en el cuerpo por defecto. Los
overlays pertenecen a la RAIZ: extraida a `chronos-search-palette.svelte`.
El Dialog del editor tiene el mismo hueco y sigue dentro de la vista; es lo
primero de la rebanada 2.
Dos declaraciones falsas mas, del patron de siempre: `toolbar` declaraba
`header` y se pintaba un `div`; `heading` declaraba `div` y se pintaba un
`span`. Los wrappers renderizan lo DECLARADO, medido sin desplazamiento.
Medido con dos instancias en la misma pagina: next en la compuesta mueve solo
la compuesta (junio -> julio), prev en la de por defecto mueve solo esa
(junio -> mayo), ids de heading distintos, y la paleta de cada raiz lista sus
18 eventos por separado. check 74 = base · 458 vitest · morfo:check PASS
chronos · docs:check 0/621 · rtl:check 0/177 · eidos-lint invalid 0 · consola
limpia en las tres vistas.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Dos declaraciones falsas más, del mismo patrón de siempre: `toolbar` declaraba
`header` y la vista pintaba un `div` ; `heading` declaraba `div` y pintaba un
`span` . Los wrappers renderizan **lo declarado** , medido sin desplazamiento
(la fila del título es flex, así que el `div` sigue en la misma línea que la
insignia de semana, y el `aria-labelledby` del grid sigue resolviendo).
Medido con DOS instancias en la misma página (la de por defecto y una
compuesta a `size="sm"` ):
```
next en la COMPUESTA → junio → julio, la de por defecto NO se mueve
prev en la DEFECTO → junio → mayo, la compuesta NO se mueve
ids de heading distintos (s15 / s347) → registro por instancia (F2) sostiene
la paleta de cada raíz lista sus 18 eventos por separado
```
`check` 74 = base · 458 ✓ · `morfo:check` PASS chronos · `docs:check` 0/621 ·
`rtl:check` 0/177 · `eidos-lint` invalid 0 · consola limpia en las tres vistas.
fix(chronos): una ocurrencia recurrente deja de fingir que se puede arrastrar
Arrastrar una ocurrencia iluminaba la celda de destino y al soltar no pasaba
nada: su id es sintetico (`serie::YYYY-MM-DD`) y `moveEventToDay` lo buscaba en
`calendar.events`, que solo guarda la SERIE, asi que `find` devolvia undefined y
salia en silencio.
Iba a resolver el id a la serie —como hace `openEdit`— hasta ver que `canResize`
YA excluye las ocurrencias de forma explicita («those edit via the series in
v1»). La politica escrita no es desplazar la serie: es bloquear la manipulacion
directa y editar por el dialogo. El defecto no era la resolucion del id, era que
la afordancia mentia.
`canMove(event)` en el provider, espejo de `canResize` y con su razon escrita, y
`disabled` en los TRES `DragDrop.Draggable` (mes · all-day · timed). De paso, el
handler de teclado gateaba las DOS permisos con `canResize`; ahora cada flecha
consulta la suya (Shift → resize, flecha sola → move). Hoy los dos predicados
valen lo mismo, asi que en el teclado no cambia la conducta, solo el
significado.
Medido en navegador: la ocurrencia ya no INICIA el arrastre (ni `data-dragover`
ni `data-dragging`), y un evento normal sigue cayendo — Retro 18 jun -> 25 jun.
Teclado: flecha sobre la ocurrencia no mueve ni agarra; sobre `Launch` mueve
15 jun -> 16 jun.
Queda la mitad cosmetica, anotada: el chip bloqueado sigue con `cursor: grab` y
sin `data-disabled`, porque `Draggable` no lo estampa. Pintarlo pide un gancho
declarado — decision, no bug.
Y el handoff registra por que la rebanada 3 de C5 no es mecanica: casi todos los
helpers de la vista de mes los comparten semana y agenda, y varios son
COMPORTAMIENTO que pertenece a soma. Partir la vista antes de mover eso repartia
la fuga entre cuatro ficheros en vez de arreglarla.
check 74 = base · 458 vitest · docs:check 0/621 · rtl:check 0/177 · consola
limpia.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
#### ⚠️ C5 rebanada 3 — el bloqueo real, medido antes de empezarla
Extraer `MonthView` NO es mecánico, y la razón cambia el plan. Cruzando los
helpers de la vista contra sus tres regiones:
| Helper | mes | semana/agenda |
| --- | --- | --- |
| `tooltipBody` · `tooltipCtx` | 1 | 3 |
| `chipKeydown` | 1 | 2 |
| `startResize` / `moveResize` / `endResize` | 1 | 1 |
| `dragPreview` · `eventTime` | 1 | 1 |
| `segsForWeek` · `overflowByDate` · `handleDrop` · `startCreateMonth` | sí | 0 |
Casi todo es COMPARTIDO. Y varios de esos compartidos —`chipKeydown`,
`startResize` , `nudgeGrab` — **son comportamiento, que por doctrina es de soma,
no de eidos**: mapean teclas a llamadas del provider y hacen captura de puntero.
Partir la vista sin mover eso antes no arregla el monolito: **reparte la fuga
entre cuatro ficheros**.
Así que la rebanada 3 empieza por ahí: **subir a soma el comportamiento del
chip** (teclado de mover/redimensionar, captura de puntero del resize,
drag-to-create), y sólo después extraer `MonthView` / `WeekView` / `AgendaView` ,
que entonces sí son composición pura.
feat(eidos): chronos abre su estructura, y el cuerpo por defecto la estrena
C5 rebanada 1 de 4. Que partes son publicas no es cuestion de gusto: es
`kind` en el morfo (21 publicas, 12 privadas). Namespace `Chronos.*` con la
forma de Table/Calendar, y la raiz gana ranura de composicion — pasar
`children` REEMPLAZA el cuerpo por defecto. Antes se renderizaban ADEMAS de
la vista completa, un segundo arbol colgando de un calendario entero; ningun
consumidor los pasaba.
Ocho partes de toolbar (`Toolbar`, `Heading`, `Prev`/`Next`/`Today`/`Undo`/
`Redo`/`SearchButton`), ergonomicas al estilo `Calendar.PrevButton`: componen
el IconButton/Button del sistema, su glifo y la llamada al provider, asi que
el consumidor recibe un control que FUNCIONA y no un gancho vacio. El cuerpo
por defecto se recompone desde ellas — no es un camino privilegiado, es el
primer consumidor de la API compuesta.
`searchOpen` sube al provider: en un toolbar compuesto el boton y la paleta
viven en subarboles distintos y el provider es el unico sitio que alcanzan
los dos. Y la prueba compuesta destapo un hueco real — el SearchButton
compuesto no abria nada porque la paleta vivia en el cuerpo por defecto. Los
overlays pertenecen a la RAIZ: extraida a `chronos-search-palette.svelte`.
El Dialog del editor tiene el mismo hueco y sigue dentro de la vista; es lo
primero de la rebanada 2.
Dos declaraciones falsas mas, del patron de siempre: `toolbar` declaraba
`header` y se pintaba un `div`; `heading` declaraba `div` y se pintaba un
`span`. Los wrappers renderizan lo DECLARADO, medido sin desplazamiento.
Medido con dos instancias en la misma pagina: next en la compuesta mueve solo
la compuesta (junio -> julio), prev en la de por defecto mueve solo esa
(junio -> mayo), ids de heading distintos, y la paleta de cada raiz lista sus
18 eventos por separado. check 74 = base · 458 vitest · morfo:check PASS
chronos · docs:check 0/621 · rtl:check 0/177 · eidos-lint invalid 0 · consola
limpia en las tres vistas.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Rebanadas que quedan:**
3. **Partes del grid de mes** — `Grid` · `GridHead` · `HeadCell` · `GridBody` ·
`GridRow` · `DayCell` · `DayNumber` · `DayAdd` · `EventChip` ·
`EventResizeHandle` · `MoreLink` como wrappers de eidos, y un
`Chronos.MonthView` que los componga (patrón `Calendar.MonthView` , que se
auto-gatea sobre `provider.view` ).
4. ** `WeekView` / `DayView` / `AgendaView` ** con las 12 partes privadas dentro,
y la vista monolítica reducida a un despachador.
feat(morfo,soma): la emisión deja de aceptar un elemento anónimo
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>
2 months ago
Verificación al cierre: `check` **74 = base, DELTA CERO** sostenido en cada
lote · **458 ✓** (chronos + morfo + sema) · navegador limpio en las tres vistas.
Censo global de `targetOverride` : **148 → 89** .
#### ⚠️ Defectos de conducta medidos en el navegador (2026-08-10) — eje NUEVO
**Estado 2026-08-11**: de los tres, el 2 está ARREGLADO, el 3 RETIRADO por no
reproducible, y el 1 espera decisión tuya.
feat(morfo,soma,eidos): chronos deja de tener partes que el morfo no conoce
C3 cierra con `more-link` y C4 con los seis marcadores que eidos estilizaba
sin declaracion. Los tres botones sin fuentes (`day-add`, `edit-action`,
`search-button`) comparten una clase; `undo`/`redo` se cablean sobre la del
nav; `timegrid` pasa a `time-view` porque estaba a un guion de la parte
`time-grid` que vive dentro; `peek-panel` y `day-peek` quedan como partes de
display puro con marcador inline. Cada una renderiza un `<button>` real
cuando el morfo lo declara: una `Region` que cae a `<div>` pondria el `type`
declarado sobre un no-boton.
La lupa deja de ser un adorno sin `onclick`: abre una paleta `Command.Dialog`
sobre TODOS los eventos, no un filtro de la vista, con `goToDate` nuevo en el
provider — la generalizacion de la que `goToday` era un caso particular. El
texto buscable viaja en `keywords` porque el scorer de Command lee value +
keywords y NUNCA los hijos renderizados, asi que se busca por titulo, por tipo
y por fecha.
Y tres etiquetas que mentian en silencio: `translationRef` / `commonRef`
normalizan la clave LITERAL y no consultan ningun mapa, asi que
`prevMonth` / `nextMonth` / `moreEvents` y `action.undo` / `action.redo` caian
al fallback ingles en una pagina `es`. Corregidas contra el catalogo, con
`common.buttons.undo` / `redo` anadidas.
Verificado: check 74 = base (delta cero) · 458 vitest · docs:check 0/621 ·
rtl:check 0/177 · eidos-lint chronos morfo-backed 32 -> 44, invalid 0 ·
navegador limpio en las tres vistas, con el A/B del undo medido
(18 jun -> drag -> 25 jun -> undo -> 18 jun -> redo -> 25 jun) y el salto de
la paleta (noviembre -> elegir Retro -> junio).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
Salieron al responder dos preguntas del autor sobre la vista de mes. Ninguno es
de superficie perceptual; se anotan aquí para que no se pierdan.
docs(process): la 6a fila de chronos no era un defecto — decision revocada por el SPEC
El item llevaba un mes en la cola como «la 6a fila fantasma» y estaba FIRMADO
para arreglarse. Al ir a ejecutarlo aparecio el SPEC del propio componente, que
dice lo contrario en TRES sitios coordinados: rejilla de 6 filas constante
«estabiliza la altura» (SPEC.md:270), inventario de reuso pidiendo «rejilla de
mes 6x7» (:492), y `data-outside-month` como estado declarado de `day-cell` CON
su propio token de tema (:311 y :461). Las celdas de fuera de mes son diseno,
no relleno.
La asimetria con la familia —chronos `true`, los otros cuatro `false`— se leyo
como deriva y es al reves: chronos es el unico que escribio su decision, y
coincide con el default de sus dos referentes de libreria, verificados en la
fuente y no citados de memoria: FullCalendar `fixedWeekCount` viene `true` («the
calendar will always be 6 weeks tall») y Toast UI `month.isAlways6Weeks` viene
`true`. Google/Apple/Outlook si varian las filas, pero porque son aplicaciones
que llenan el viewport; chronos no tiene contrato de altura, asi que la receta
propuesta tenia que inventarse la altura con `calc(6 * cell-min-block)` — la
altura de 6 filas. Era la misma altura con 5 filas mas gordas, y menos
informacion.
Medido antes de opinar: hoy son 115 px por fila y 724 px de rejilla en todos los
meses; con `fixedWeeks:false`, jul→dic 2026 da 609/724/609/609/724/609, o sea el
salto de 115 px que la decision queria evitar. Y crecer las filas no ensenaria
ni un evento mas: `maxLanes` es `$state(3)` fijo.
Corregidos ademas dos errores de la ficha original: el default vive en DOS
sitios (chronos.svelte:20 y engine/state.svelte.ts:64), y chronos.css:278 no es
la altura de las filas del mes sino la pista de carriles de chips dentro de una
fila. La conducta de Google queda anotada como FUNCIONALIDAD con firma propia
(contrato de altura + maxLanes derivado), no como flip de flag.
docs:check 0/623.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
1. ~~**La 6ª fila fantasma.**~~ **NO ERA UN DEFECTO — REVOCADO 2026-08-13, ver
§4.** `fixedWeeks` viene `true` en chronos y `false` en Calendar / DatePicker /
RangeCalendar / DateRangePicker, y de esa asimetría se dedujo una deriva. Es
al revés: chronos es **el único que escribió su decisión** (`SPEC.md:270`,
`:492` , `:311` /`:461`) y coincide con el default de FullCalendar y Toast UI;
los otros cuatro son rejillas en popover, otro problema. La ficha además leyó
mal el CSS y se dejó la mitad de los defaults — el detalle, en §4.
fix(chronos): una ocurrencia recurrente deja de fingir que se puede arrastrar
Arrastrar una ocurrencia iluminaba la celda de destino y al soltar no pasaba
nada: su id es sintetico (`serie::YYYY-MM-DD`) y `moveEventToDay` lo buscaba en
`calendar.events`, que solo guarda la SERIE, asi que `find` devolvia undefined y
salia en silencio.
Iba a resolver el id a la serie —como hace `openEdit`— hasta ver que `canResize`
YA excluye las ocurrencias de forma explicita («those edit via the series in
v1»). La politica escrita no es desplazar la serie: es bloquear la manipulacion
directa y editar por el dialogo. El defecto no era la resolucion del id, era que
la afordancia mentia.
`canMove(event)` en el provider, espejo de `canResize` y con su razon escrita, y
`disabled` en los TRES `DragDrop.Draggable` (mes · all-day · timed). De paso, el
handler de teclado gateaba las DOS permisos con `canResize`; ahora cada flecha
consulta la suya (Shift → resize, flecha sola → move). Hoy los dos predicados
valen lo mismo, asi que en el teclado no cambia la conducta, solo el
significado.
Medido en navegador: la ocurrencia ya no INICIA el arrastre (ni `data-dragover`
ni `data-dragging`), y un evento normal sigue cayendo — Retro 18 jun -> 25 jun.
Teclado: flecha sobre la ocurrencia no mueve ni agarra; sobre `Launch` mueve
15 jun -> 16 jun.
Queda la mitad cosmetica, anotada: el chip bloqueado sigue con `cursor: grab` y
sin `data-disabled`, porque `Draggable` no lo estampa. Pintarlo pide un gancho
declarado — decision, no bug.
Y el handoff registra por que la rebanada 3 de C5 no es mecanica: casi todos los
helpers de la vista de mes los comparten semana y agenda, y varios son
COMPORTAMIENTO que pertenece a soma. Partir la vista antes de mover eso repartia
la fuga entre cuatro ficheros en vez de arreglarla.
check 74 = base · 458 vitest · docs:check 0/621 · rtl:check 0/177 · consola
limpia.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
2. ~~**Una ocurrencia de evento recurrente NO se puede mover.**~~ **ARREGLADO
2026-08-11** — y el arreglo NO era el que parecía. Su id es sintético
(`${serie}::${YYYY-MM-DD}`, `engine/recurrence.ts:145` ) y `moveEventToDay`
la buscaba en `calendar.events` , que sólo guarda la SERIE → `find` devolvía
`undefined` y salía en silencio, después de haber iluminado el destino.
Iba a resolver el id a la serie (como hace `openEdit` ) hasta ver que
** `canResize` ya excluye las ocurrencias explícitamente** («those edit via
the series in v1»): la política escrita es **bloquear la manipulación
directa** y editar por el diálogo, no desplazar la serie. Así que el arreglo
es hacer VISIBLE ese bloqueo: `canMove(event)` en el provider, espejo de
`canResize` , y `disabled` en los TRES `DragDrop.Draggable` (mes · all-day ·
timed). Medido: la ocurrencia ya **no inicia el arrastre** (antes: celda
iluminada + nada al soltar), y un evento normal sigue cayendo
(`Retro` 18 jun → 25 jun).
⚠️ El camino de TECLADO ya estaba bloqueado — por `canResize` , que gateaba
las DOS permisos. Ahora cada flecha consulta la suya (`Shift` → resize,
flecha sola → move); hoy los dos predicados valen lo mismo, así que ahí no
hay cambio de conducta, sólo de significado.
⚠️ **Queda la mitad cosmética** : el chip bloqueado sigue con
`cursor: grab` y sin `data-disabled` (el `Draggable` no lo estampa). Para
pintarlo hace falta un gancho: o `event-chip` declara un estado nuevo, o
`DragDrop.Draggable` estampa `data-disabled` — decisión, no bug.
feat(morfo,soma,eidos): chronos deja de tener partes que el morfo no conoce
C3 cierra con `more-link` y C4 con los seis marcadores que eidos estilizaba
sin declaracion. Los tres botones sin fuentes (`day-add`, `edit-action`,
`search-button`) comparten una clase; `undo`/`redo` se cablean sobre la del
nav; `timegrid` pasa a `time-view` porque estaba a un guion de la parte
`time-grid` que vive dentro; `peek-panel` y `day-peek` quedan como partes de
display puro con marcador inline. Cada una renderiza un `<button>` real
cuando el morfo lo declara: una `Region` que cae a `<div>` pondria el `type`
declarado sobre un no-boton.
La lupa deja de ser un adorno sin `onclick`: abre una paleta `Command.Dialog`
sobre TODOS los eventos, no un filtro de la vista, con `goToDate` nuevo en el
provider — la generalizacion de la que `goToday` era un caso particular. El
texto buscable viaja en `keywords` porque el scorer de Command lee value +
keywords y NUNCA los hijos renderizados, asi que se busca por titulo, por tipo
y por fecha.
Y tres etiquetas que mentian en silencio: `translationRef` / `commonRef`
normalizan la clave LITERAL y no consultan ningun mapa, asi que
`prevMonth` / `nextMonth` / `moreEvents` y `action.undo` / `action.redo` caian
al fallback ingles en una pagina `es`. Corregidas contra el catalogo, con
`common.buttons.undo` / `redo` anadidas.
Verificado: check 74 = base (delta cero) · 458 vitest · docs:check 0/621 ·
rtl:check 0/177 · eidos-lint chronos morfo-backed 32 -> 44, invalid 0 ·
navegador limpio en las tres vistas, con el A/B del undo medido
(18 jun -> drag -> 25 jun -> undo -> 18 jun -> redo -> 25 jun) y el salto de
la paleta (noviembre -> elegir Retro -> junio).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Los eventos normales SÍ caen en las celdas de fuera de mes** — medido:
`Retro` → 2026-07-08 y `Launch` → 2026-07-09 aterrizan y se repintan allí.
3. ~~**El chip pierde `data-event-id` tras un intento de arrastre.**~~
**NO REPRODUCIBLE — RETIRADO 2026-08-11.** Lo reporté como defecto de
contrato C1 (el attr es `propRef` + `emit: 'value'` ) tras verlo faltar en
tres chips. Al ir a arreglarlo no aparece bajo NINGUNA condición. Probado:
| Condición | Resultado |
| --- | --- |
| Carga limpia, lectura inmediata · +50 · +200 · +1000 ms | 17/17 |
| Re-render (mes siguiente y vuelta) | 17/17 |
| Arrastre real que aterriza | 17/17 |
| En mitad del arrastre · mismo tick del `pointerup` · microtask · macrotask · 1 frame · asentado | 17/17 |
La explicación más probable —no medida, y por eso no la doy por buena— es
que fuera estado rancio de HMR: **la misma sesión produjo otros dos
fantasmas** (`DayAdd is not defined` y dos `soma::runtime.part` ) que al
comprobarlos en pestaña nueva no existían. Lección de método: un hallazgo
sobre el DOM en una pestaña que ha sobrevivido a varios HMR **no vale** ;
repetirlo en pestaña nueva ANTES de anotarlo.
feat(morfo,soma,eidos): chronos deja de tener partes que el morfo no conoce
C3 cierra con `more-link` y C4 con los seis marcadores que eidos estilizaba
sin declaracion. Los tres botones sin fuentes (`day-add`, `edit-action`,
`search-button`) comparten una clase; `undo`/`redo` se cablean sobre la del
nav; `timegrid` pasa a `time-view` porque estaba a un guion de la parte
`time-grid` que vive dentro; `peek-panel` y `day-peek` quedan como partes de
display puro con marcador inline. Cada una renderiza un `<button>` real
cuando el morfo lo declara: una `Region` que cae a `<div>` pondria el `type`
declarado sobre un no-boton.
La lupa deja de ser un adorno sin `onclick`: abre una paleta `Command.Dialog`
sobre TODOS los eventos, no un filtro de la vista, con `goToDate` nuevo en el
provider — la generalizacion de la que `goToday` era un caso particular. El
texto buscable viaja en `keywords` porque el scorer de Command lee value +
keywords y NUNCA los hijos renderizados, asi que se busca por titulo, por tipo
y por fecha.
Y tres etiquetas que mentian en silencio: `translationRef` / `commonRef`
normalizan la clave LITERAL y no consultan ningun mapa, asi que
`prevMonth` / `nextMonth` / `moreEvents` y `action.undo` / `action.redo` caian
al fallback ingles en una pagina `es`. Corregidas contra el catalogo, con
`common.buttons.undo` / `redo` anadidas.
Verificado: check 74 = base (delta cero) · 458 vitest · docs:check 0/621 ·
rtl:check 0/177 · eidos-lint chronos morfo-backed 32 -> 44, invalid 0 ·
navegador limpio en las tres vistas, con el A/B del undo medido
(18 jun -> drag -> 25 jun -> undo -> 18 jun -> redo -> 25 jun) y el salto de
la paleta (noviembre -> elegir Retro -> junio).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
docs(process): el handoff dice lo que paso, incluido donde el diagnostico viejo mentia
Dos diagnosticos heredados eran falsos y mandaban al sitio equivocado, asi que
se corrigen en su sitio en vez de anadir una nota al final: el crash del guard
de traducciones no era una url multilinea de un catalogo (era el stripper de
comentarios rompiendo un https:// dentro de una cadena) y el guard, ademas,
nunca escaneo los call sites que se le atribuian.
Dos secciones daban ordenes OPUESTAS a §3.0 y llevaban un dia haciendolo: el
hallazgo «chronos estampa fuera del morfo» pedia una decision que C1 ya habia
tomado el mismo dia, y «la cola de la campana» listaba como pendientes nueve
componentes que las olas 2-3 habian cerrado. Marcadas resuelta y congelada, con
los cerrados tachados y remision a la cola viva.
Y se escribe lo aprendido en el eje del nombrado: las cinco clases de cadena,
las cuatro razones por las que un default NO puede subir al contrato (estado,
interpolacion, cross-element, supresion por `<label for>`), la trampa del
spread condicional, la del harness cuyo `translate` diverge de `langs.ts`, y la
regla que resume la clase — antes de mover un default, mira si el elemento es
tuyo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
#### ~~⚠️ Hallazgo — chronos estampa fuera del morfo~~ RESUELTO POR C1
**Superado (2026-08-10, §C1): chronos tiene CERO `targetOverride` ** — el censo
lo confirma. La decisión que este bloque pedía quedó tomada así:
`shift-navigate` retiró la redirección (deriva, lección A-36);
`commit-save` /`commit-delete`/`commit-move` anclan por `partInstance` al CHIP
que nombran con `targetFallback: [provider]` declarado (los botones del editor
son un `Dialog` compuesto y NUNCA fueron partes de chronos);
`commit-restore` /`commit-apply` retiraron sus parámetros muertos. Se conserva
el texto original sólo como registro de qué preguntaba:
> `commit-save` y `commit-delete` declaraban `event-chip` y aterrizaban en los
> botones Guardar / Eliminar **del editor**; `commit-move` en la celda del
> drop; `shift-navigate` en los botones de navegación y `day-cell`.
#### La cola de la campaña — LISTA CONGELADA, ver §3.0 «QUEDA de las olas 2-4»
⚠️ Esta lista quedó ESCRITA ANTES de las olas 2-3 de F3 y ya no es la cola
real — durante un día dio órdenes opuestas a §3.0 sobre nueve componentes. La
cola VIVA es la de §3.0; de lo de aquí ya están CERRADOS por F3/F4:
~~`context-menu`~~ · ~~`dropdown-menu`~~ (F4, open pre→post + `targetFallback` )
· ~~`editable`~~ ~~`search-field`~~ ~~`stepper`~~ (CROSS retirados — el
provider declarado ES la superficie) · ~~`table`~~ (sort-trigger →
column-header por `closest` + `partInstance` ) · ~~`tag-group`~~ ·
~~`tree-view`~~ (identidad item/branch) · ~~`command`~~ (item ratón + camino
Enter; queda sólo `link-item` ) · ~~`timeline`~~ (emerge-reveal anclado).
Siguen pendientes (coinciden con §3.0): `file-upload` · `float-panel`
(drag/resize → `header` / `resize-handle` / `resize-grip` : ¿allowedTargets o
retirar?) · `grid-list` (`selection-checkbox`) · `month-grid` y `year-grid`
(`shift-navigate-step` → `cell` ) · `palabras` (13 filas, diseño fino) ·
`range-calendar` (`shift-navigate` → `day` ) · `tags-input` · `command`
(`link-item`) · `menu-dial` / `onion-menu` / `metrics` (vistas de eidos: deben
REGISTRAR sus partes) · `path-trace` / `rotate-align` / `drag-drop` (gestos
handle) · `virtual-list` / `virtual-grid` · `menubar` · `navigation-menu` ·
`css-field` / `number-field` (`commit-set` + retirar `targetOverride` de su
API pública) · `select` / `combobox` · `calendar` (day) · `pagination` (5
controles).
fix(sema,morfo,soma): una superficie no admite dos ocurrencias, y el nombre ya no miente
`data-event-*` es UNA RANURA por elemento. Tres auditorias independientes
encontraron el mismo defecto y ninguna lo cerro: fable S1 (2026-07-01, con el
README diciendo «findings executed»), sema S-17 (2026-08-05, midio el estampado
del knob muriendo a los 24ms de un hold de 240) y blocks A-36/A-65 (reproducido
en navegador, dos estampados a 1,1ms). Lo que faltaba no era diagnostico: nadie
podia distinguir una redireccion legitima de una deriva, porque la opcion que
redirigia se llamaba `fallbackTarget` y hacia lo contrario de lo que decia.
EL NOMBRE PRIMERO. `fallbackTarget` -> `targetOverride` (202 apariciones, 64
ficheros, dirigido con `git ls-files`). Siempre GANO sobre el ref registrado; el
nombre honesto es la precondicion para auditarlo. Las cronicas conservan el viejo
por diseño.
LA PROPIEDAD. `unstampEventAttrs` recibia el signal y lo DESCARTABA, asi que la
ocurrencia que terminaba antes borraba a la que tenia la ranura. Ahora comprueba
`data-event-id`. Con eso, `replace` deja de ser el comportamiento roto y pasa a
ser el suelo correcto.
A-36, SIN `regime`. Los cuatro overlays cedian el estampado del `open` al
trigger por un workaround de la era `sequence: 'pre'`; con `post` el content ya
esta montado. Retirado el override (float-panel migrado `pre`->`post`, ultimo
fuera de la doctrina que sema.md escribe). Medido en /blocks/site-header/preview
a 375px: contact-activate en el TRIGGER +18,7ms y open en el CONTENT +20,6ms,
con press-squeeze y slide-from-right-full corriendo. Antes: los dos en el
trigger, press-squeeze jamas.
`regime` DEFINIDO — y son dos valores, no cuatro. `replace` (default) y `queue`,
este solo para las 4 parejas irreducibles: los tres toggles (el provider ES el
boton) y el knob. `collapse` retirado (su caso murio con la propiedad: medido, 0
huecos en 5 emisiones a 72ms) y `lock` retirado (significaria «un cierre que no
se anuncia»), con sus 4 declaraciones muertas.
El guard de `queue` PASO EN VERDE SIENDO INCORRECTO: con timers falsos no hay
animaciones, asi que awaitExpression volvia al instante. El navegador midio el
commit encolado llegando 1,6s tarde. `queue` espera el HOLD, no la expresion.
Medir la envolvente no es medir la salida.
`renderAttrs` — los attrs que no son partes. Vaciar ACTIVE_DEV_TRACK (palabras y
chronos ya no estan excluidos) destapo 23 `data-palabras-*` que ningun morfo
declaraba y que palabras.css SI estiliza: aterrizan en el arbol que el usuario
escribio, asi que no pueden ser partes. Campo nuevo con la linea afilada —si el
consumidor puede componerlo, es una PARTE— y 3 consumidores (palabras, waveform,
aura). Declarado en types.ts Y en el esquema sium: la leccion de MorfoElement.
Ademas: 5 selectores muertos en el predicado del focus-scope de palabras
(barrido de todo el repo: aparecian solo ahi) · RTL-1 real en palabras-chrome
(inset logico + translate fisico = doble volteo; rtl:check 1->0) · el union
inline al `Direction` canonico, que destapo que el censo de direccion grepea el
NOMBRE del tipo · deuda de chronos (scope, barrel, README).
Y de la cola: S-36 (barrel 68->71 + guard), S-31 (vibrate(NaN)), S5 (la
preferencia de reduced-motion del usuario ya gana a los channels del morfo), SO2
(el warn que el JSDoc prometia), SO4 (validar el target antes del prewrite).
Todos los guards nuevos vistos fallar antes de arreglar nada.
Base: check 74 (= base) · sema+morfo+contracts 454 verdes / 6 ajenos
preexistentes · soma navegador 1258/1259 (el rojo es un timeout ajeno,
reproducido con los cambios en stash) · docs:check 0/618 · rtl:check 0/177.
Handoff: docs/process/CONTINUE-perceptual-surface.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
### 3.2 Restitución de persistentes — **eje NUEVO, ninguno de `regime` lo cubre**
Medido con el engine real:
```
afterWarn: "signal-warn-invalid" ← el aviso untilFix proyectado
duringCommit: "commit-submit" ← el commit lo pisa
afterCommit: null ← al limpiar se lleva el aviso
engineStillHoldsWarning: true ← el engine lo cree vivo
```
Una persistente **no compite por la ranura: es un estado al que la ranura debe
VOLVER**. Propuesta: tras el hold de una transitoria, el engine re-proyecta la
persistente viva de ese nodo. Afecta a `form` , `file-upload` , `textarea` ,
`media-player` , `announce` , `aura` .
### 3.3 Eje B — ordenación y emisión
- **`sequence: 'coincident'` sigue sin implementar** (idéntico a `pre` ); 21
eventos lo declaran. Implementarlo (emit sin await + handler inmediato) o
marcarlo reservado.
- **4 continuos sin throttle** contra la doctrina de `sema.md` :
`gradient-builder` , `color-picker` (área), `number-field` , `css-field`
emiten en CADA `pointermove` . Cinco compañeros sí throttlean
(`DRAG_SIGNAL_MS`). ⚠️ `rotate-align` NO es infractor: throttlea por detente.
- **Las 68 demos emiten sin `verb` ** → desde el rediseño del 2026-08-06 el verbo
elige el sonido, así que un play de `emerge-close` **suena `open`** y
`commit-delete` suena `tick` en vez de `tick.loss` . El bloque está copiado 68
veces; extraer un helper a `$demo` es refactor aparte.
- **`knob.handle-drop` es `pre` ** con handler → `data-dragging` sobrevive 240 ms
al soltar. ** `button.contact-activate` ** retrasa `onPress` 120 ms.
### 3.4 Eje C — cola de `AUDIT-sema-2026-08-05`, re-verificada abierta
docs(process): handoff nuevo para la cola de auditorias, con el protocolo de trabajo delante
El eje perceptual quedo completo hoy (F1-F6), asi que lo que sigue ya no es «su»
cola: es la cola de auditorias — sema, fable, blocks, eje B, chronos C5. Este
handoff la lleva, y pone PRIMERO lo que cambio hoy de verdad: el protocolo.
§0 · las cinco preguntas del autor antes de CADA accion, un item por mensaje, y
parar a esperar firma. Sin puerta que permita auto-autorizarse lo mecanico: lo
propuse y se corrigio en el acto. Con las dos precondiciones que hoy costaron
dos rectificaciones completas — el SPEC del COMPONENTE es doctrina y se busca
ANTES de diagnosticar (chronos), y se barre `docs/` antes de proponer retirar
nada (S-33 esta en CANON.md y en la D.3 firmada). La regla que las resume: el
codigo nunca corrige al canon; se le pone al autor la contradiccion delante.
§1 · S-33 FIRMADO y sin ejecutar, con la receta y la evidencia del probe de
`tsc`: el defecto es el `Exclude`, no «falta una rama». Derivar los tres cubos
positivamente cierra la puerta sola al flipar el const, y la tercera rama es
inerte hoy porque se teclea sobre `never` — medido, delta de conducta CERO.
§2 los 6 commits de hoy · §3 las tres decisiones abiertas con su evidencia ya
medida (S-35 · S-30/S-38 · S-19, esta ultima partida en dos porque (ii) cambia
conducta) · §4 la cola en orden · §5 la base de verificacion, con los 6 fallos
ajenos de contracts nombrados uno a uno · §6 las trampas medidas hoy, para no
re-descubrirlas: la laxitud DELIBERADA del guard D9 (endurecerlo convierte 27
emisiones reales en violaciones), el panel oculto congelando la linea de tiempo,
y los ficheros ya sucios en HEAD para prettier.
docs:check 0/624.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
> **2026-08-13 — esta cola se sigue desde
> [`CONTINUE-audit-queue.md`](./CONTINUE-audit-queue.md)**, que lleva el
> protocolo de trabajo, lo firmado y lo abierto. Cerrados desde aquí: **S-10**
> (el canal visual honra su propia bolsa, `44af38e6e`) y **S-34** (el validador
> de runtime atado al const, `eda3b977b`). **S-33 firmado y sin ejecutar.**
fix(sema,morfo,soma): una superficie no admite dos ocurrencias, y el nombre ya no miente
`data-event-*` es UNA RANURA por elemento. Tres auditorias independientes
encontraron el mismo defecto y ninguna lo cerro: fable S1 (2026-07-01, con el
README diciendo «findings executed»), sema S-17 (2026-08-05, midio el estampado
del knob muriendo a los 24ms de un hold de 240) y blocks A-36/A-65 (reproducido
en navegador, dos estampados a 1,1ms). Lo que faltaba no era diagnostico: nadie
podia distinguir una redireccion legitima de una deriva, porque la opcion que
redirigia se llamaba `fallbackTarget` y hacia lo contrario de lo que decia.
EL NOMBRE PRIMERO. `fallbackTarget` -> `targetOverride` (202 apariciones, 64
ficheros, dirigido con `git ls-files`). Siempre GANO sobre el ref registrado; el
nombre honesto es la precondicion para auditarlo. Las cronicas conservan el viejo
por diseño.
LA PROPIEDAD. `unstampEventAttrs` recibia el signal y lo DESCARTABA, asi que la
ocurrencia que terminaba antes borraba a la que tenia la ranura. Ahora comprueba
`data-event-id`. Con eso, `replace` deja de ser el comportamiento roto y pasa a
ser el suelo correcto.
A-36, SIN `regime`. Los cuatro overlays cedian el estampado del `open` al
trigger por un workaround de la era `sequence: 'pre'`; con `post` el content ya
esta montado. Retirado el override (float-panel migrado `pre`->`post`, ultimo
fuera de la doctrina que sema.md escribe). Medido en /blocks/site-header/preview
a 375px: contact-activate en el TRIGGER +18,7ms y open en el CONTENT +20,6ms,
con press-squeeze y slide-from-right-full corriendo. Antes: los dos en el
trigger, press-squeeze jamas.
`regime` DEFINIDO — y son dos valores, no cuatro. `replace` (default) y `queue`,
este solo para las 4 parejas irreducibles: los tres toggles (el provider ES el
boton) y el knob. `collapse` retirado (su caso murio con la propiedad: medido, 0
huecos en 5 emisiones a 72ms) y `lock` retirado (significaria «un cierre que no
se anuncia»), con sus 4 declaraciones muertas.
El guard de `queue` PASO EN VERDE SIENDO INCORRECTO: con timers falsos no hay
animaciones, asi que awaitExpression volvia al instante. El navegador midio el
commit encolado llegando 1,6s tarde. `queue` espera el HOLD, no la expresion.
Medir la envolvente no es medir la salida.
`renderAttrs` — los attrs que no son partes. Vaciar ACTIVE_DEV_TRACK (palabras y
chronos ya no estan excluidos) destapo 23 `data-palabras-*` que ningun morfo
declaraba y que palabras.css SI estiliza: aterrizan en el arbol que el usuario
escribio, asi que no pueden ser partes. Campo nuevo con la linea afilada —si el
consumidor puede componerlo, es una PARTE— y 3 consumidores (palabras, waveform,
aura). Declarado en types.ts Y en el esquema sium: la leccion de MorfoElement.
Ademas: 5 selectores muertos en el predicado del focus-scope de palabras
(barrido de todo el repo: aparecian solo ahi) · RTL-1 real en palabras-chrome
(inset logico + translate fisico = doble volteo; rtl:check 1->0) · el union
inline al `Direction` canonico, que destapo que el censo de direccion grepea el
NOMBRE del tipo · deuda de chronos (scope, barrel, README).
Y de la cola: S-36 (barrel 68->71 + guard), S-31 (vibrate(NaN)), S5 (la
preferencia de reduced-motion del usuario ya gana a los channels del morfo), SO2
(el warn que el JSDoc prometia), SO4 (validar el target antes del prewrite).
Todos los guards nuevos vistos fallar antes de arreglar nada.
Base: check 74 (= base) · sema+morfo+contracts 454 verdes / 6 ajenos
preexistentes · soma navegador 1258/1259 (el rojo es un timeout ajeno,
reproducido con los cambios en stash) · docs:check 0/618 · rtl:check 0/177.
Handoff: docs/process/CONTINUE-perceptual-surface.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
`S-19` (`AnnounceFn` no compila contra `uix.announce` ; ninguna raíz cablea el
canal) · ~~`S-27/S-41`~~ **ARREGLADO 2026-08-12** (el gate de `shouldPrime` lee
la preferencia: con `off` ni contexto ni listeners; test nacido en rojo) · `S-10`
fix(sema,morfo,soma): una superficie no admite dos ocurrencias, y el nombre ya no miente
`data-event-*` es UNA RANURA por elemento. Tres auditorias independientes
encontraron el mismo defecto y ninguna lo cerro: fable S1 (2026-07-01, con el
README diciendo «findings executed»), sema S-17 (2026-08-05, midio el estampado
del knob muriendo a los 24ms de un hold de 240) y blocks A-36/A-65 (reproducido
en navegador, dos estampados a 1,1ms). Lo que faltaba no era diagnostico: nadie
podia distinguir una redireccion legitima de una deriva, porque la opcion que
redirigia se llamaba `fallbackTarget` y hacia lo contrario de lo que decia.
EL NOMBRE PRIMERO. `fallbackTarget` -> `targetOverride` (202 apariciones, 64
ficheros, dirigido con `git ls-files`). Siempre GANO sobre el ref registrado; el
nombre honesto es la precondicion para auditarlo. Las cronicas conservan el viejo
por diseño.
LA PROPIEDAD. `unstampEventAttrs` recibia el signal y lo DESCARTABA, asi que la
ocurrencia que terminaba antes borraba a la que tenia la ranura. Ahora comprueba
`data-event-id`. Con eso, `replace` deja de ser el comportamiento roto y pasa a
ser el suelo correcto.
A-36, SIN `regime`. Los cuatro overlays cedian el estampado del `open` al
trigger por un workaround de la era `sequence: 'pre'`; con `post` el content ya
esta montado. Retirado el override (float-panel migrado `pre`->`post`, ultimo
fuera de la doctrina que sema.md escribe). Medido en /blocks/site-header/preview
a 375px: contact-activate en el TRIGGER +18,7ms y open en el CONTENT +20,6ms,
con press-squeeze y slide-from-right-full corriendo. Antes: los dos en el
trigger, press-squeeze jamas.
`regime` DEFINIDO — y son dos valores, no cuatro. `replace` (default) y `queue`,
este solo para las 4 parejas irreducibles: los tres toggles (el provider ES el
boton) y el knob. `collapse` retirado (su caso murio con la propiedad: medido, 0
huecos en 5 emisiones a 72ms) y `lock` retirado (significaria «un cierre que no
se anuncia»), con sus 4 declaraciones muertas.
El guard de `queue` PASO EN VERDE SIENDO INCORRECTO: con timers falsos no hay
animaciones, asi que awaitExpression volvia al instante. El navegador midio el
commit encolado llegando 1,6s tarde. `queue` espera el HOLD, no la expresion.
Medir la envolvente no es medir la salida.
`renderAttrs` — los attrs que no son partes. Vaciar ACTIVE_DEV_TRACK (palabras y
chronos ya no estan excluidos) destapo 23 `data-palabras-*` que ningun morfo
declaraba y que palabras.css SI estiliza: aterrizan en el arbol que el usuario
escribio, asi que no pueden ser partes. Campo nuevo con la linea afilada —si el
consumidor puede componerlo, es una PARTE— y 3 consumidores (palabras, waveform,
aura). Declarado en types.ts Y en el esquema sium: la leccion de MorfoElement.
Ademas: 5 selectores muertos en el predicado del focus-scope de palabras
(barrido de todo el repo: aparecian solo ahi) · RTL-1 real en palabras-chrome
(inset logico + translate fisico = doble volteo; rtl:check 1->0) · el union
inline al `Direction` canonico, que destapo que el censo de direccion grepea el
NOMBRE del tipo · deuda de chronos (scope, barrel, README).
Y de la cola: S-36 (barrel 68->71 + guard), S-31 (vibrate(NaN)), S5 (la
preferencia de reduced-motion del usuario ya gana a los channels del morfo), SO2
(el warn que el JSDoc prometia), SO4 (validar el target antes del prewrite).
Todos los guards nuevos vistos fallar antes de arreglar nada.
Base: check 74 (= base) · sema+morfo+contracts 454 verdes / 6 ajenos
preexistentes · soma navegador 1258/1259 (el rojo es un timeout ajeno,
reproducido con los cambios en stash) · docs:check 0/618 · rtl:check 0/177.
Handoff: docs/process/CONTINUE-perceptual-surface.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
(rama visual sin `??` → revienta el boot) · `S-33` (`'forbidden'` no prohíbe) ·
`S-34` (gemelo del schema copiado a mano) · `S-35` (la etiqueta esquiva la
política) · `S-30/S-38` (5 waivers, ya desbloqueados por S-31).
### 3.5 Eje D — fable, lo que sigue abierto
`M1 emission` (D.2, aceptada y nunca implementada — cerraría los waivers de
tooltip y gradient-picker) · `M4` (`enumPair` frágil con 3+ valores) · `M5`
(tipar el matcher `state` ) · `M6` (`IntentExpectedFamily` deprecado, × 2) ·
`E2` (`globalThis.matchMedia` crudo en eidos) · `E3` (catch mudo en
`#renderSchemeCss` ).
### 3.6 Eje E — canon del ledger de blocks
`A-47` (anillo de foco 1.43:1 sobre panel sólido) · `A-69` (`count-up`:
docs(process): el handoff dice lo que paso, incluido donde el diagnostico viejo mentia
Dos diagnosticos heredados eran falsos y mandaban al sitio equivocado, asi que
se corrigen en su sitio en vez de anadir una nota al final: el crash del guard
de traducciones no era una url multilinea de un catalogo (era el stripper de
comentarios rompiendo un https:// dentro de una cadena) y el guard, ademas,
nunca escaneo los call sites que se le atribuian.
Dos secciones daban ordenes OPUESTAS a §3.0 y llevaban un dia haciendolo: el
hallazgo «chronos estampa fuera del morfo» pedia una decision que C1 ya habia
tomado el mismo dia, y «la cola de la campana» listaba como pendientes nueve
componentes que las olas 2-3 habian cerrado. Marcadas resuelta y congelada, con
los cerrados tachados y remision a la cola viva.
Y se escribe lo aprendido en el eje del nombrado: las cinco clases de cadena,
las cuatro razones por las que un default NO puede subir al contrato (estado,
interpolacion, cross-element, supresion por `<label for>`), la trampa del
spread condicional, la del harness cuyo `translate` diverge de `langs.ts`, y la
regla que resume la clase — antes de mover un default, mira si el elemento es
tuyo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
`duration` no significa segundos) · ~~**`A-85` confirmado**~~ **ARREGLADO
2026-08-11, y el veredicto se INVIRTIÓ**: el censo dio 132 declaraciones en 59
morfos, y la solución no fue el `prop-truthy` de button × 132 sino una regla de
VOCABULARIO — dos clases de precedencia (`contract` = runtime gana ·
`naming` /`aria-label` = el attr explícito del consumidor gana, como la propia
cadena de nombre accesible de la plataforma). Las 132 declaraciones eran
correctas; la precedencia era el defecto. `consumerWins` en el compilador, el
label viaja en el bag (SSR incluido — antes ni salía en SSR, medido) y
`mergeProps` resuelve consumer-first. A/B del ledger re-medido en verde;
**`button` simplificado (2026-08-11)**: retirados la entrada `propRef` +
`prop-truthy` del morfo, el destructure del wrapper y la fuente del provider —
el attr del consumidor fluye por restProps y gana por política; el tipo con su
JSDoc se queda (documenta la API). A/B por stash: el insta-cierre del drawer
en `/blocks/site-header/preview` reproduce IGUAL con el código original — es
del entorno de esa instancia (el demo plano de drawer abre perfecto), no de la
simplificación. **Espejos manuales BARRIDOS (2026-08-11)** : 10 duplicados
estrictos retirados — popover close · float-panel close · editable × 3 (su
comentario ya confesaba «resolves to the same id») · palabras × 4
(bubble/slash/link/find) · y los 6 hand-merges consumer-first de wrapper
(file-upload × 5 + command-list), que re-implementaban a mano la política de
`mergeProps` ; con ellos cayeron 4 errores de tipado preexistentes (check 74→70).
⚠️ Trampa de harness cazada: `translate: (key) => key` en el harness de
editable divergía de producción (`translate = langs.ts`) — el bag resolvía
refs crudos mientras el mock alimentaba las líneas a mano; alineado.
**Normalización morfo-first de `resolvedAriaLabel` (2026-08-11)** — las ~28
cadenas resultaron ser **la danza de Button a escala** : el prop público ES
`aria-label` (con `Without<…>` sacándolo del passthrough), el wrapper lo
destructura, el provider lo resuelve contra un default y lo devuelve al bag.
Una sola superficie de consumidor, no dos. Cinco clases medidas:
| Clase | Qué declara el morfo | Arreglo |
| --- | --- | --- |
| **1** | `propRef('ariaLabel')` ; el default vive SÓLO en la cadena | el morfo pasa a declarar el default (`translationRef` con el ref VERBATIM del `LANGS` ) |
| **2** | ya el `translationRef` correcto | la cadena era duplicado puro: se retira sin tocar el morfo |
| ** +supr** | — | la supresión por labelledby pasa a `condition: prop-falsy ariaLabelledby` (exige que el provider publique la fuente) |
| **sin default** | `propRef` sin default real (radio-group, slider) | el morfo NO declara nada — precedente Button |
| **ESTADO ⛔** | — | **BLOQUEADA** : `mapRef` devuelve `map[String(raw)]` sin traducir, así que una etiqueta que cambia con el estado no puede subir (clipboard copy/copied · password-field show/hide · table expand/collapse). Ensanchar `mapRef` es decisión de contrato, no mecánica |
**Piloto `meter` cerrado y verificado** (clase 1 + supresión): morfo declara
`#?components.meter.label|Meter` con `prop-falsy ariaLabelledby` ; fuera la
cadena, la fuente, el prop de opts, el destructure del wrapper y el import
huérfano. `check` 70 = base · navegador: un meter compuesto con
`aria-label="Storage usage"` conserva el del consumidor. ⚠️ La aserción de
supresión hubo que REFORZARLA: mirar el DOM ya pasa trivialmente (los naming no
viajan por `dom.apply` ), tiene que mirar el BAG.
**Barrido EJECUTADO (2026-08-11) — 21 componentes migrados, 5 rechazados.**
Refs compilados 145 → **157** : doce defaults que vivían en resolvers de provider
son ya contrato. `check` 70 = base · soma **1269✓/1** (el timeout ajeno) ·
morfo+sema 494✓ · `translations:check` 0 · SSR de los 21 sin una sola fuga de
`#?` ni `__uix_lang_missing__` . Medido en SSR: el consumidor gana donde lo pasa
(«Project location» en breadcrumb, «Uploading assets» en progress) y el default
del contrato resuelve en castellano donde no («Opciones», «Carrusel», «Lista en
cuadrícula») — y **ahora sale en SSR** , que antes no ocurría.
⚠️ **El residuo es la parte valiosa: cuatro razones, dos que el análisis previo
NO había previsto.** Además de las state-dependent (bloqueante `mapRef` ya
conocido: table row-detail · password-field toggle · tree-grid expand ·
media-player por control · clipboard) aparecieron:
- **INTERPOLADAS** — `translationRef` nombra una clave, no admite params:
carousel `Slide N of M` e indicator · tag-group `Remove {tag}` · nav-tree
(nombre por nodo).
- ~~**CROSS-ELEMENT**~~ **ARREGLADA 2026-08-11** (ver abajo).
- ~~**SUPRIMIDA POR `<label for>` **~~ **ARREGLADA** con ella — eran la misma
clase por sus dos mitades.
#### Clase CROSS-ELEMENT — arreglada (2026-08-11)
El prop del consumidor se declaraba en una raíz que pinta un `<div>` sin rol,
pero el nombre pertenece a un control HIJO (textarea → su `<textarea>` ;
mask-field · search-field · password-field → su `<input>` ). No era «bloqueada»:
era **declarable en la parte hija** . Y había un defecto de contrato real: el
morfo declaraba la etiqueta INCONDICIONAL mientras el provider la suprimía ante
un `Field.Label` y luego la pisaba siempre — su propio comentario lo confesaba
(«Overrides the morfo's raw translationRef default»). El plan del morfo se
evaluaba y se tiraba.
Arreglo, en los 4: el default vive en la parte que posee el nombre, con la
supresión DECLARADA (`condition: prop-falsy fieldLabelled`, leyendo un prop
virtual que el provider publica — regla 2-de-3); la cadena `resolvedAriaLabel`
desaparece; y el prop de conveniencia de la raíz se reenvía con **spread
CONDICIONAL**.
⚠️ **El spread tiene que ser condicional.** Escribir la clave sin condición la
pone a `undefined` cuando el prop falta y **borra el default que el bag acaba de
aportar** — una clave presente gana a una ausente en un spread. Es la trampa de
esta clase.
Medido: SSR con el prop del consumidor reenviado («Notes», «Masked value») y con
el default del contrato en castellano donde no hay prop («Buscar»,
«Contraseña»); 0 fugas de `#?` . ⚠️ El tercer caso (dentro de `Field.Label` ) NO
está verificado en navegador: **ningún demo compone estos controles dentro de un
Field.Label**. Queda cubierto por test (search-field y mask-field asertan
`not.toHaveProperty('aria-label')` ) — y de paso mejoró: la clave ahora está
genuinamente AUSENTE en vez de presente-con-`undefined`.
**`navigation-menu` y `waveform` CERRADOS (2026-08-11)** — y resultaron ser dos
casos distintos, no uno:
- **navigation-menu**: el mismo patrón que los cuatro (raíz `<nav>` → `<ul>` ).
Mismo arreglo; la condición vive en las fuentes POR PARTE de la List (donde ya
estaba `orientation` ), no en el runtime raíz. Medido: `<ul aria-label="Principal">`
del catálogo, `<nav>` sin etiqueta.
- **waveform**: NO era la misma clase. Su default se queda en el provider —
correcto: el nombre lo lleva un `<Slider>` **compuesto** , que no es una parte
suya, y `morfo.texts` es justo la forma sancionada para un slot que resuelve
el provider. El defecto era otro y peor: su prop público era ** `ariaLabel`
camelCase** (único en el catálogo) y su tipo `Without<PrimitiveDivAttributes, {}>`
**no filtraba nada** — un `aria-label` del consumidor, que es lo natural,
aterrizaba en el `<div>` sin rol y se perdía en silencio mientras el Slider
conservaba el default. Arreglado alineándolo con el catálogo: prop
`'aria-label'` + `Without<…, { 'aria-label' }>` + reenvío al Slider.
⚠️ El cambio de tipo **cazó un consumidor real** —`media-player-time-slider`
pasaba `ariaLabel` —, que es exactamente lo que un tipo estrecho debe hacer.
Medido: raíz `<div>` SIN `aria-label` , Slider con «Forma de onda».
Lección de la clase: **antes de mover un default, mira si el elemento es tuyo.**
Si lo es, va al morfo de esa parte; si es de un componente compuesto, el
`texts` + provider es correcto y lo que hay que auditar es que el prop del
consumidor no acabe en el envoltorio sin rol.
`table` es caso aparte: su cadena la alimenta un prop público DISTINTO (`label`,
con su propio JSDoc), no el passthrough — y su `aria-label` ya gana
consumer-first hoy. Nada que migrar.
Tabla completa de exclusiones en `morfo.md` Step 4.
QUEDAN legítimos (no espejos ni cadenas): supresión por labelledby de
navigation-menu/tree-view · handles de float-panel (fuente ÚNICA).
La incoherencia de gemelos quedó RESUELTA (2026-08-11):
range-calendar declara ya `month/year-select` con los MISMOS refs
`#?common.calendar.*` que calendar, espejos del provider fuera, y un test fija
que el bag los lleva (con `translate` identidad el ref crudo aflora — prueba de
que la DECLARACIÓN llega). ⚠️ Ningún demo compone los selects de SOMA: el
«month-select» del demo de calendar es la composición de EIDOS
(Popover.Trigger+Button, nombrado por su TEXTO visible, marcador passthrough) —
no confundirlo con la parte del morfo al verificar. En `range-calendar/langs.ts`
quedan `PREV_MONTH` /`NEXT_MONTH` muertas PREEXISTENTES (no las tocó este pase).
Detalle: `morfo.md` Step 4 + ledger §A-85 RESOLUCIÓN.
fix(sema,morfo,soma): una superficie no admite dos ocurrencias, y el nombre ya no miente
`data-event-*` es UNA RANURA por elemento. Tres auditorias independientes
encontraron el mismo defecto y ninguna lo cerro: fable S1 (2026-07-01, con el
README diciendo «findings executed»), sema S-17 (2026-08-05, midio el estampado
del knob muriendo a los 24ms de un hold de 240) y blocks A-36/A-65 (reproducido
en navegador, dos estampados a 1,1ms). Lo que faltaba no era diagnostico: nadie
podia distinguir una redireccion legitima de una deriva, porque la opcion que
redirigia se llamaba `fallbackTarget` y hacia lo contrario de lo que decia.
EL NOMBRE PRIMERO. `fallbackTarget` -> `targetOverride` (202 apariciones, 64
ficheros, dirigido con `git ls-files`). Siempre GANO sobre el ref registrado; el
nombre honesto es la precondicion para auditarlo. Las cronicas conservan el viejo
por diseño.
LA PROPIEDAD. `unstampEventAttrs` recibia el signal y lo DESCARTABA, asi que la
ocurrencia que terminaba antes borraba a la que tenia la ranura. Ahora comprueba
`data-event-id`. Con eso, `replace` deja de ser el comportamiento roto y pasa a
ser el suelo correcto.
A-36, SIN `regime`. Los cuatro overlays cedian el estampado del `open` al
trigger por un workaround de la era `sequence: 'pre'`; con `post` el content ya
esta montado. Retirado el override (float-panel migrado `pre`->`post`, ultimo
fuera de la doctrina que sema.md escribe). Medido en /blocks/site-header/preview
a 375px: contact-activate en el TRIGGER +18,7ms y open en el CONTENT +20,6ms,
con press-squeeze y slide-from-right-full corriendo. Antes: los dos en el
trigger, press-squeeze jamas.
`regime` DEFINIDO — y son dos valores, no cuatro. `replace` (default) y `queue`,
este solo para las 4 parejas irreducibles: los tres toggles (el provider ES el
boton) y el knob. `collapse` retirado (su caso murio con la propiedad: medido, 0
huecos en 5 emisiones a 72ms) y `lock` retirado (significaria «un cierre que no
se anuncia»), con sus 4 declaraciones muertas.
El guard de `queue` PASO EN VERDE SIENDO INCORRECTO: con timers falsos no hay
animaciones, asi que awaitExpression volvia al instante. El navegador midio el
commit encolado llegando 1,6s tarde. `queue` espera el HOLD, no la expresion.
Medir la envolvente no es medir la salida.
`renderAttrs` — los attrs que no son partes. Vaciar ACTIVE_DEV_TRACK (palabras y
chronos ya no estan excluidos) destapo 23 `data-palabras-*` que ningun morfo
declaraba y que palabras.css SI estiliza: aterrizan en el arbol que el usuario
escribio, asi que no pueden ser partes. Campo nuevo con la linea afilada —si el
consumidor puede componerlo, es una PARTE— y 3 consumidores (palabras, waveform,
aura). Declarado en types.ts Y en el esquema sium: la leccion de MorfoElement.
Ademas: 5 selectores muertos en el predicado del focus-scope de palabras
(barrido de todo el repo: aparecian solo ahi) · RTL-1 real en palabras-chrome
(inset logico + translate fisico = doble volteo; rtl:check 1->0) · el union
inline al `Direction` canonico, que destapo que el censo de direccion grepea el
NOMBRE del tipo · deuda de chronos (scope, barrel, README).
Y de la cola: S-36 (barrel 68->71 + guard), S-31 (vibrate(NaN)), S5 (la
preferencia de reduced-motion del usuario ya gana a los channels del morfo), SO2
(el warn que el JSDoc prometia), SO4 (validar el target antes del prewrite).
Todos los guards nuevos vistos fallar antes de arreglar nada.
Base: check 74 (= base) · sema+morfo+contracts 454 verdes / 6 ajenos
preexistentes · soma navegador 1258/1259 (el rojo es un timeout ajeno,
reproducido con los cambios en stash) · docs:check 0/618 · rtl:check 0/177.
Handoff: docs/process/CONTINUE-perceptual-surface.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
---
## 4 · Decisiones del autor, pendientes
docs(morfo,chronos): la doctrina alcanza a lo que se construyo hoy
`morfo.md` gana la trampa que se llevo cinco etiquetas: **`texts` es una
DECLARACION, nunca una tabla de consulta**. `normalizeTranslationRef` toma la
clave VERBATIM y no traduce un alias a una ruta, asi que `prevMonth` frente al
`prev-month` del catalogo resuelve a una ruta que no existe y **envia el
fallback ingles sin error ni aviso**. Igual con `commonRef('action.undo')`
cuando `common.action` no existe. Y el aviso que faltaba: el guard que deberia
cazarlo, `npm run translations:check`, **estaba cascando** antes de comprobar
nada — arreglarlo y censar los 166 morfos antes de volver a fiarse.
README de soma/chronos: `SearchButton` faltaba en la tabla de partes, y ahora
documenta los dos permisos de manipulacion directa (`canMove` / `canResize` —
separados a proposito aunque hoy coincidan, porque el handler de teclado
preguntaba el equivocado) y el estado `searchOpen`.
README de eidos/chronos: seccion **Composicion** nueva (la ranura `children`,
que las partes publicas las decide `kind` en el morfo, que son ergonomicas y no
ganchos vacios, y que los overlays los pone la RAIZ). La coletilla «sin API
compound publica» queda marcada como ANULADA en su gap. Busqueda y undo/redo
pasan de hueco a hecho; la recurrencia anota que la manipulacion directa se
bloquea de forma visible y que falta PINTARLO.
`CONTINUE.md` de chronos se marca como NO vigente y apunta al handoff del eje.
El handoff gana una seccion §0 «donde estas» con los seis commits y el orden de
lo siguiente, y §4 recoge las dos decisiones de autor medidas y listas.
docs:check 0/621 · rtl:check 0/177. Los .md tocados que prettier marca ya
estaban asi en HEAD salvo el README de eidos, cuyo contenido SI pasa (el aviso
es solo CRLF del working tree, que git normaliza).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Las dos de chronos, medidas y listas para decidir (2026-08-11):**
- **La 6ª fila fantasma.** `fixedWeeks` vale `true` por defecto **sólo en
chronos**; Calendar, DatePicker, RangeCalendar y DateRangePicker lo traen
`false` . Junio de 2026 cabe en 5 filas y se inventa una 6ª **entera de julio** .
¿Alineamos el defecto con la familia, o mantenemos la altura estable
**creciendo las filas** en vez de añadir una semana? (Google Calendar hace lo
segundo.)
docs(process): la 6a fila de chronos no era un defecto — decision revocada por el SPEC
El item llevaba un mes en la cola como «la 6a fila fantasma» y estaba FIRMADO
para arreglarse. Al ir a ejecutarlo aparecio el SPEC del propio componente, que
dice lo contrario en TRES sitios coordinados: rejilla de 6 filas constante
«estabiliza la altura» (SPEC.md:270), inventario de reuso pidiendo «rejilla de
mes 6x7» (:492), y `data-outside-month` como estado declarado de `day-cell` CON
su propio token de tema (:311 y :461). Las celdas de fuera de mes son diseno,
no relleno.
La asimetria con la familia —chronos `true`, los otros cuatro `false`— se leyo
como deriva y es al reves: chronos es el unico que escribio su decision, y
coincide con el default de sus dos referentes de libreria, verificados en la
fuente y no citados de memoria: FullCalendar `fixedWeekCount` viene `true` («the
calendar will always be 6 weeks tall») y Toast UI `month.isAlways6Weeks` viene
`true`. Google/Apple/Outlook si varian las filas, pero porque son aplicaciones
que llenan el viewport; chronos no tiene contrato de altura, asi que la receta
propuesta tenia que inventarse la altura con `calc(6 * cell-min-block)` — la
altura de 6 filas. Era la misma altura con 5 filas mas gordas, y menos
informacion.
Medido antes de opinar: hoy son 115 px por fila y 724 px de rejilla en todos los
meses; con `fixedWeeks:false`, jul→dic 2026 da 609/724/609/609/724/609, o sea el
salto de 115 px que la decision queria evitar. Y crecer las filas no ensenaria
ni un evento mas: `maxLanes` es `$state(3)` fijo.
Corregidos ademas dos errores de la ficha original: el default vive en DOS
sitios (chronos.svelte:20 y engine/state.svelte.ts:64), y chronos.css:278 no es
la altura de las filas del mes sino la pista de carriles de chips dentro de una
fila. La conducta de Google queda anotada como FUNCIONALIDAD con firma propia
(contrato de altura + maxLanes derivado), no como flip de flag.
docs:check 0/623.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
~~**FIRMADA (2026-08-12): crecer las filas.**~~ **REVOCADA POR EL AUTOR
(2026-08-13) — NO SE TOCA: `fixedWeeks` se queda en `true` .** La firma se dio
sobre una premisa incompleta: **nadie puso el SPEC del componente sobre la
mesa**, y el SPEC no dice una línea sino tres coordinadas —
`SPEC.md:270` («Rejilla de 6 filas constante (**estabiliza la altura**).
Default: true.»), `SPEC.md:492` (el inventario de reuso pide «Rejilla de mes
**6× 7** ») y `SPEC.md:311` +`:461` (`data-outside-month` es un estado declarado
de `day-cell` **con su propio token de tema** ). La 6ª fila no es un fantasma:
es la rejilla especificada, y la altura estable que la decisión perseguía ya
está — medido: 115 px por fila y 724 px de rejilla en TODOS los meses.
Y la comparación competitiva, verificada en las fuentes en vez de citada:
**FullCalendar `fixedWeekCount` viene `true`** («the calendar will always be
6 weeks tall») y **Toast UI `month.isAlways6Weeks` viene `true`** — los dos
referentes de librería de la propia matriz del SPEC (§2.1) hacen lo que
chronos hace. Google/Apple/Outlook sí varían las filas, pero **porque son
aplicaciones que llenan el viewport** y la rejilla se estira hasta el alto de
la ventana (Google llega a exponer un ajuste de densidad para regularlo). Esa
condición chronos no la tiene: sus filas son `min-block-size` y la rejilla
crece con el contenido, así que no hay altura dentro de la que estirarse — por
eso la receta propuesta tenía que INVENTARSE la altura con
`calc(6 * var(--_chronos-cell-min-block))` , que es la altura de 6 filas. Era
«seguir midiendo 6 filas de alto, pero con 5 filas más gordas»: misma altura,
menos información.
⚠️ Dos errores de la ficha original, corregidos para que no se repitan: el
default vive en DOS sitios (`components/chronos.svelte:20` **y**
`engine/state.svelte.ts:64` ), y `chronos.css:278`
(`grid-auto-rows: var(--_chronos-lane-height)`) **no es la altura de las filas
del mes** — es la pista de los carriles de chips DENTRO de una fila
(`.chronos-overlay`, absoluta). Las filas del mes son hijos flex con
`min-block-size` (`chronos.css:188-193`).
**Si algún día se quiere la conducta de Google, es una FUNCIONALIDAD, no un
flip de flag**: contrato de altura (la rejilla llena su contenedor) + `maxLanes`
derivado de la altura medida — hoy es `$state(3)` fijo
(`engine/state.svelte.ts:66`), así que crecer las filas no enseña ni un evento
más. Eso necesita firma propia y una enmienda deliberada del SPEC.
docs(morfo,chronos): la doctrina alcanza a lo que se construyo hoy
`morfo.md` gana la trampa que se llevo cinco etiquetas: **`texts` es una
DECLARACION, nunca una tabla de consulta**. `normalizeTranslationRef` toma la
clave VERBATIM y no traduce un alias a una ruta, asi que `prevMonth` frente al
`prev-month` del catalogo resuelve a una ruta que no existe y **envia el
fallback ingles sin error ni aviso**. Igual con `commonRef('action.undo')`
cuando `common.action` no existe. Y el aviso que faltaba: el guard que deberia
cazarlo, `npm run translations:check`, **estaba cascando** antes de comprobar
nada — arreglarlo y censar los 166 morfos antes de volver a fiarse.
README de soma/chronos: `SearchButton` faltaba en la tabla de partes, y ahora
documenta los dos permisos de manipulacion directa (`canMove` / `canResize` —
separados a proposito aunque hoy coincidan, porque el handler de teclado
preguntaba el equivocado) y el estado `searchOpen`.
README de eidos/chronos: seccion **Composicion** nueva (la ranura `children`,
que las partes publicas las decide `kind` en el morfo, que son ergonomicas y no
ganchos vacios, y que los overlays los pone la RAIZ). La coletilla «sin API
compound publica» queda marcada como ANULADA en su gap. Busqueda y undo/redo
pasan de hueco a hecho; la recurrencia anota que la manipulacion directa se
bloquea de forma visible y que falta PINTARLO.
`CONTINUE.md` de chronos se marca como NO vigente y apunta al handoff del eje.
El handoff gana una seccion §0 «donde estas» con los seis commits y el orden de
lo siguiente, y §4 recoge las dos decisiones de autor medidas y listas.
docs:check 0/621 · rtl:check 0/177. Los .md tocados que prettier marca ya
estaban asi en HEAD salvo el README de eidos, cuyo contenido SI pasa (el aviso
es solo CRLF del working tree, que git normaliza).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **Cómo pintar un chip bloqueado.** Una ocurrencia recurrente ya no se
arrastra, pero sigue con `cursor: grab` porque `DragDrop.Draggable` no estampa
`data-disabled` . Dos vías: (a) `event-chip` declara un estado nuevo — local y
barato; (b) `Draggable` estampa `data-disabled` — beneficia a TODOS sus
consumidores pero es cambio transversal.
docs(process): la ronda de decisiones queda firmada — cinco disposiciones, dos corregidas al re-analizar
D10 SE RETIRA (lo ejecuta el eje de sonido, su handoff PRIMERO), y el
re-analisis añade un SEXTO sitio a adjudicar: sema.md §per-emit payload aun
describe pitch/gain/contour cuando soma adjunta hoy sound:'step' + curva
haptica (dragSignalOverrides del slider y del knob, con el trade-off del
2026-08-06 citado en su comentario).
fixedWeeks: alinear a false a secas era la opcion debil — las filas del mes
son de altura FIJA (grid-auto-rows, chronos.css:278) y un false pelado cambia
la semana fantasma por saltos de altura al navegar. Firmado: crecer las filas
(modelo Google), con false incluido; es arreglo de RECETA, no un flip de flag.
Chip bloqueado: via (b), y la letra pequeña va INCLUIDA — el morfo de
drag-drop YA declara data-disabled (declaracion sin emisor) y el cursor vive
en drag-drop.css; pero el chip fusiona tres morfos en un elemento clicable,
asi que la regla de archetype item se anula en su receta y aria-disabled NO
aterriza en el elemento fusionado.
mode/scope: SE RETIRAN, con el barrido de la regla 4 hecho de verdad: 0 hits
en docs/, 0 lectores del compilado, 4 ficheros de alcance. SemaCause queda
como pregunta hermana (mas muerto aun: ni atraviesa morfo).
present-rise: medir primero (lo esperable es la receta F4, pero decide la
medida). handle-drag-progress: revisar contra CANON, no renombrar — el matiz
es legal y puede ser deliberado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**FIRMADA (2026-08-12): la vía (b), con su letra pequeña medida e INCLUIDA.**
A favor, más de lo que la pregunta sabía: el morfo de drag-drop **ya declara**
`data-disabled` en el draggable (`drag-drop.ts:89`, severity optional) — hoy
es una declaración sin emisor, la clase de siempre — y el `cursor: grab` vive
en `drag-drop.css:24` , así que la corrección del cursor aterriza allí para
todos los consumidores. La letra pequeña: el chip fusiona tres morfos en UN
elemento que sigue siendo clicable (abre el editor), así que (i) la regla
`:where([data-archetype='item'][data-disabled])` de `archetypes.css:259`
(not-allowed + opacidad) hay que anularla en la receta del chip — es `:where` ,
especificidad cero — y (ii) el `aria-disabled` prop-truthy del mismo morfo NO
debe aterrizar en el elemento fusionado: estamparlo mentiría a AT sobre un
click que funciona.
docs(morfo,chronos): la doctrina alcanza a lo que se construyo hoy
`morfo.md` gana la trampa que se llevo cinco etiquetas: **`texts` es una
DECLARACION, nunca una tabla de consulta**. `normalizeTranslationRef` toma la
clave VERBATIM y no traduce un alias a una ruta, asi que `prevMonth` frente al
`prev-month` del catalogo resuelve a una ruta que no existe y **envia el
fallback ingles sin error ni aviso**. Igual con `commonRef('action.undo')`
cuando `common.action` no existe. Y el aviso que faltaba: el guard que deberia
cazarlo, `npm run translations:check`, **estaba cascando** antes de comprobar
nada — arreglarlo y censar los 166 morfos antes de volver a fiarse.
README de soma/chronos: `SearchButton` faltaba en la tabla de partes, y ahora
documenta los dos permisos de manipulacion directa (`canMove` / `canResize` —
separados a proposito aunque hoy coincidan, porque el handler de teclado
preguntaba el equivocado) y el estado `searchOpen`.
README de eidos/chronos: seccion **Composicion** nueva (la ranura `children`,
que las partes publicas las decide `kind` en el morfo, que son ergonomicas y no
ganchos vacios, y que los overlays los pone la RAIZ). La coletilla «sin API
compound publica» queda marcada como ANULADA en su gap. Busqueda y undo/redo
pasan de hueco a hecho; la recurrencia anota que la manipulacion directa se
bloquea de forma visible y que falta PINTARLO.
`CONTINUE.md` de chronos se marca como NO vigente y apunta al handoff del eje.
El handoff gana una seccion §0 «donde estas» con los seis commits y el orden de
lo siguiente, y §4 recoge las dos decisiones de autor medidas y listas.
docs:check 0/621 · rtl:check 0/177. Los .md tocados que prettier marca ya
estaban asi en HEAD salvo el README de eidos, cuyo contenido SI pasa (el aviso
es solo CRLF del working tree, que git normaliza).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
**Del eje perceptual:**
docs(process): la ronda de decisiones queda firmada — cinco disposiciones, dos corregidas al re-analizar
D10 SE RETIRA (lo ejecuta el eje de sonido, su handoff PRIMERO), y el
re-analisis añade un SEXTO sitio a adjudicar: sema.md §per-emit payload aun
describe pitch/gain/contour cuando soma adjunta hoy sound:'step' + curva
haptica (dragSignalOverrides del slider y del knob, con el trade-off del
2026-08-06 citado en su comentario).
fixedWeeks: alinear a false a secas era la opcion debil — las filas del mes
son de altura FIJA (grid-auto-rows, chronos.css:278) y un false pelado cambia
la semana fantasma por saltos de altura al navegar. Firmado: crecer las filas
(modelo Google), con false incluido; es arreglo de RECETA, no un flip de flag.
Chip bloqueado: via (b), y la letra pequeña va INCLUIDA — el morfo de
drag-drop YA declara data-disabled (declaracion sin emisor) y el cursor vive
en drag-drop.css; pero el chip fusiona tres morfos en un elemento clicable,
asi que la regla de archetype item se anula en su receta y aria-disabled NO
aterriza en el elemento fusionado.
mode/scope: SE RETIRAN, con el barrido de la regla 4 hecho de verdad: 0 hits
en docs/, 0 lectores del compilado, 4 ficheros de alcance. SemaCause queda
como pregunta hermana (mas muerto aun: ni atraviesa morfo).
present-rise: medir primero (lo esperable es la receta F4, pero decide la
medida). handle-drag-progress: revisar contra CANON, no renombrar — el matiz
es legal y puede ser deliberado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **`mode` y `scope` — FIRMADA (2026-08-12): SE RETIRAN.** El barrido de la
regla 4 del ledger está hecho: 0 hits en `docs/` , 0 lectores del compilado, y
el alcance real son 4 ficheros (`SemaMode`/`SemaScope` en `sema/types.ts` +
`morfo/types.ts:644-646` + `compile.ts:196-198` + exports) — misma clase que
`collapse` y `lock` . Pregunta hermana ABIERTA: `SemaCause` está más muerto
aún (sólo types + exports, ni atraviesa morfo) — decidir si entra en el
mismo pase.
fix(sema,morfo,soma): una superficie no admite dos ocurrencias, y el nombre ya no miente
`data-event-*` es UNA RANURA por elemento. Tres auditorias independientes
encontraron el mismo defecto y ninguna lo cerro: fable S1 (2026-07-01, con el
README diciendo «findings executed»), sema S-17 (2026-08-05, midio el estampado
del knob muriendo a los 24ms de un hold de 240) y blocks A-36/A-65 (reproducido
en navegador, dos estampados a 1,1ms). Lo que faltaba no era diagnostico: nadie
podia distinguir una redireccion legitima de una deriva, porque la opcion que
redirigia se llamaba `fallbackTarget` y hacia lo contrario de lo que decia.
EL NOMBRE PRIMERO. `fallbackTarget` -> `targetOverride` (202 apariciones, 64
ficheros, dirigido con `git ls-files`). Siempre GANO sobre el ref registrado; el
nombre honesto es la precondicion para auditarlo. Las cronicas conservan el viejo
por diseño.
LA PROPIEDAD. `unstampEventAttrs` recibia el signal y lo DESCARTABA, asi que la
ocurrencia que terminaba antes borraba a la que tenia la ranura. Ahora comprueba
`data-event-id`. Con eso, `replace` deja de ser el comportamiento roto y pasa a
ser el suelo correcto.
A-36, SIN `regime`. Los cuatro overlays cedian el estampado del `open` al
trigger por un workaround de la era `sequence: 'pre'`; con `post` el content ya
esta montado. Retirado el override (float-panel migrado `pre`->`post`, ultimo
fuera de la doctrina que sema.md escribe). Medido en /blocks/site-header/preview
a 375px: contact-activate en el TRIGGER +18,7ms y open en el CONTENT +20,6ms,
con press-squeeze y slide-from-right-full corriendo. Antes: los dos en el
trigger, press-squeeze jamas.
`regime` DEFINIDO — y son dos valores, no cuatro. `replace` (default) y `queue`,
este solo para las 4 parejas irreducibles: los tres toggles (el provider ES el
boton) y el knob. `collapse` retirado (su caso murio con la propiedad: medido, 0
huecos en 5 emisiones a 72ms) y `lock` retirado (significaria «un cierre que no
se anuncia»), con sus 4 declaraciones muertas.
El guard de `queue` PASO EN VERDE SIENDO INCORRECTO: con timers falsos no hay
animaciones, asi que awaitExpression volvia al instante. El navegador midio el
commit encolado llegando 1,6s tarde. `queue` espera el HOLD, no la expresion.
Medir la envolvente no es medir la salida.
`renderAttrs` — los attrs que no son partes. Vaciar ACTIVE_DEV_TRACK (palabras y
chronos ya no estan excluidos) destapo 23 `data-palabras-*` que ningun morfo
declaraba y que palabras.css SI estiliza: aterrizan en el arbol que el usuario
escribio, asi que no pueden ser partes. Campo nuevo con la linea afilada —si el
consumidor puede componerlo, es una PARTE— y 3 consumidores (palabras, waveform,
aura). Declarado en types.ts Y en el esquema sium: la leccion de MorfoElement.
Ademas: 5 selectores muertos en el predicado del focus-scope de palabras
(barrido de todo el repo: aparecian solo ahi) · RTL-1 real en palabras-chrome
(inset logico + translate fisico = doble volteo; rtl:check 1->0) · el union
inline al `Direction` canonico, que destapo que el censo de direccion grepea el
NOMBRE del tipo · deuda de chronos (scope, barrel, README).
Y de la cola: S-36 (barrel 68->71 + guard), S-31 (vibrate(NaN)), S5 (la
preferencia de reduced-motion del usuario ya gana a los channels del morfo), SO2
(el warn que el JSDoc prometia), SO4 (validar el target antes del prewrite).
Todos los guards nuevos vistos fallar antes de arreglar nada.
Base: check 74 (= base) · sema+morfo+contracts 454 verdes / 6 ajenos
preexistentes · soma navegador 1258/1259 (el rojo es un timeout ajeno,
reproducido con los cambios en stash) · docs:check 0/618 · rtl:check 0/177.
Handoff: docs/process/CONTINUE-perceptual-surface.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
- **`regime` es por EVENTO, pero la colisión es de la SUPERFICIE.** Para la clase
intra-morfo el evento vale; en A-36 chocaban dos morfos distintos y ninguno
podía saber del otro. Si vuelve a aparecer una colisión cruzada que no se
resuelva quitando la redirección, hay que decidir quién manda.
---
## 5 · Cómo verificar en este hilo
**Línea base ANTES de tocar** — hay 6 fallos AJENOS y preexistentes en
`contracts.test.ts` (waveform barrel · provider filenames · MOR-4 · DOM writes de
menubar · data-attrs · namespaces de aura), y 1 en el proyecto de navegador de
soma (un timeout de 5 s en `migrated providers re-declare no morfo attrs` ,
reproducido con los cambios en stash). Clasificar míos vs ajenos **por contenido
del diff**, nunca por confianza.
```
npx vitest run src/uix/sema src/uix/morfo src/uix/contracts.test.ts --project=server
npx vitest run src/uix/soma # proyecto navegador
npm run check # 74 errores = base
npm run docs:check # 0 / 618
npm run rtl:check # 0 / 177
```
**El navegador NO es opcional en este eje.** El A/B que aísla la causalidad:
`MutationObserver` ligado **al nodo** (no a un selector) sobre `data-event*` , más
`getAnimations()` . Dos trampas medidas hoy:
- **El panel oculto suspende rAF y no compone**: un bucle sobre
`requestAnimationFrame` se cuelga, `animationstart` no dispara y el
`screenshot` falla. Muestrear con temporizador y leer `getAnimations()` /
`getComputedStyle` , que sí son fiables ahí.
- **`awaitExpression` llega a su tope de 1500 ms** con el panel oculto, porque
las transiciones nunca terminan. No confundirlo con un defecto del código.
**Audio**: render offline replicando el grafo (`OfflineAudioContext`) y medir
pico / RMS. Contar osciladores responde «¿llegó algo?», nunca «¿se oye?».