14 KiB
Continue tomorrow
Fecha de corte: 2026-05-27. Rama: active-uix. Working tree con cambios
sin commitear listos para push.
TL;DR de la sesión
Sesión maratón del Token Scope Contract — del bug del toggle (ya conocido) hasta cobertura universal sin excepciones arquitectónicas:
- Diagnóstico inicial: usuario reporta "el toggle no funciona, el color del intent no cambia". Eager-resolution de CSS custom properties en el cascade del toggle.
- TSC v1 → v2 → v2.1 → v2.2: 4 iteraciones del Token Scope Contract. Cada una cerrando un gap (scope explícito, álgebra de cobertura, private tokens, multi-part + composition).
- Universal migration: 18 componentes con
data-colormigrados a TSC (15 con TSC v2.1, + 3 con extensiones v2.2 de hoy). - Bug crítico post-composition: la toggle-group composition
correctamente override
--toggle-palette-*pero el cascade colapsaba downstream porque los derived tokens (--toggle-solid-on-bgetc.) viven en scope[data-toggle](sibling, no ancestor, de[data-toggle-group-item]). Fix: inlined derivations en toggle-group.css con palette-direct refs. - Bundle JIT purge (
scripts/eidos-purge.ts): tool standalone que tree-shakes elgenerated/base.csspor componente, var() ref, data-attr y component import. Reduce ~50% el bundle final. - Docs: THEMING.md (~1700 líneas) como referencia canónica del theming. CLAUDE.md con 5 hand-offs del 2026-05-27. Eidos README actualizado.
Tareas completadas hoy (resumen)
Total: 87 tareas TaskList. Sprint TSC ocupa #50–#87. Highlights:
- TSC v1 (#50–#56): primera versión —
scope/dependsexplícitos, generator agrupando por scope. - TSC v2 (#57–#62): álgebra de scope (ScopeSet covers), cross-axis collision detection, var() auto-inferred deps, color:* rename.
- P0–P2 housekeeping (#63–#68): rename
--{c}-color-{role}-{slot}→--{c}-{role}-{slot}, JIT purge script. - CONSOL + GAP (#69–#73): consolidación de docs, universal anti-eager- resolution guard test.
- UNIV (#75–#79): private tokens (
_prefix) + migración universal de 13 componentes. - EXT (#80–#86): TSC v2.2 — multi-part
parts+ cross-recipecomposition+ 3 componentes restantes migrados (select,toggle-group,avatar) + docs. - EXT-FIX (#87): bug del toggle-group color cascade tras la composition migration.
Patrones canon nuevos en esta sesión
Token Scope Contract (TSC) — la fuente de verdad
Cada recipe token tiene un scope explícito que materializa el selector
CSS donde se emite. El generator infiere deps de los var() y valida
álgebra de cobertura. Imposible introducir el bug eager-resolution si
declaras tokens en TSC — el validator lo rechaza antes de generar CSS.
Formas:
recipes.toggle = {
'height-md': '32px', // shorthand → root
'solid-on-bg': { // single decl
value: 'var(--toggle-palette-solid)', scope: 'host'
},
'palette-solid': { // multi-decl
declarations: [
{ value: 'var(--toggle-neutral-solid)', scope: 'host' },
{ value: 'var(--toggle-affirm-solid)', scope: 'color:affirm' }
]
}
}
TSC v2.2 — parts: [...] para multi-part scope
'_accent-track': {
parts: ['trigger', 'content'],
declarations: [
{ value: 'var(--select-primary-track)', scope: 'host' },
{ value: 'var(--select-affirm-track)', scope: 'color:affirm' }
]
}
// → emits:
// [data-select-trigger], [data-select-content] { --_select-accent-track: ... }
// [data-select-trigger][data-color='affirm'], [data-select-content][data-color='affirm'] { ... }
Único consumer hoy: select (Trigger + Content). Cualquier componente
que tenga atributos cascadeados per-part (no en root) usa este patrón.
TSC v2.2 — composition: { ... } para cross-recipe override
'toggle-group': {
gap: 'var(--space-1)',
composition: {
toggle: { // foreign recipe
targetSelector: '[data-toggle-group-item]',
tokens: {
'palette-solid': {
declarations: [
{ value: 'var(--toggle-affirm-solid)', scope: 'color:affirm' },
{ value: 'var(--toggle-risk-solid)', scope: 'color:risk' }
]
}
}
}
}
}
// → emits:
// [data-toggle-group][data-color='affirm'] [data-toggle-group-item] {
// --toggle-palette-solid: var(--toggle-affirm-solid);
// }
Único consumer hoy: toggle-group. Pattern reutilizable para futuros
wrappers compositivos (button-group, nav-menu, etc.).
Private tokens (_ prefix)
Recipe key _palette-solid → CSS var --_toggle-palette-solid (con
underscore prefix). NO aparece en el public CSS contract. Solo accesible
desde la propia CSS recipe del componente. Convención: usar para slots
intermedios de derivation que no quieres exponer como theming knobs.
Inline derivation cuando el ancestor scope no aplica
Anti-pattern: leer var(--{foreign-component}-derived-token) desde
un elemento que NO es descendant del scope donde se declaró ese token.
Resultado: undefined → cascade colapsa.
Pattern correcto: inline la derivation expression localmente. Ejemplo
en toggle-group.css para [data-toggle-group-item] — replica las
expressions de recipes/base.ts > toggle.{solid,outline,ghost}-*
directamente. Duplicación documentada y aceptada como trade-off.
Trabajo acumulado SIN commitear
Modificados (eidos + soma touched durante TSC sprint):
M src/uix/eidos/components/avatar/avatar.css
M src/uix/eidos/components/badge/badge.css
M src/uix/eidos/components/button/button.css
M src/uix/eidos/components/card/card.css
M src/uix/eidos/components/checkbox/checkbox.css
M src/uix/eidos/components/editable/editable.css
M src/uix/eidos/components/file-upload/file-upload.css
M src/uix/eidos/components/radio-group/radio-group.css
M src/uix/eidos/components/select/select.css
M src/uix/eidos/components/stepper/stepper.css
M src/uix/eidos/components/switch/switch.css
M src/uix/eidos/components/tag-group/tag-group.css
M src/uix/eidos/components/tags-input/tags-input.css
M src/uix/eidos/components/toggle-group/toggle-group.css
M src/uix/eidos/components/toggle/toggle.css
M src/uix/eidos/README.md
M CLAUDE.md
M package.json (npm script para eidos:purge añadido)
Modificados (words component sprint en paralelo — no este chat):
M src/uix/eidos/components/words/*
M src/uix/soma/components/words/*
M src/uix/words/README.md
M web/routes/uix/components/words/*
Eliminado:
D src/docs/libro_semantica_completo.txt (reemplazado por .docx)
Untracked (TSC v2.2 + auxiliares):
?? scripts/eidos-purge.ts
?? scripts/probe-*.ts (5 probe scripts del debug session)
?? src/docs/Disenando_lo_que_ocurre_manuscrito_completo_revisado_v2.docx
?? src/uix/eidos/THEMING.md
?? src/uix/eidos/THEMING_AUDIT_2026-05-27.md
?? src/uix/eidos/components/words/* (slash menu, family menu, code lang picker)
?? src/uix/soma/components/words/components/words-slash-menu.svelte
?? src/uix/soma/components/words/engine/code-highlight.ts
?? src/uix/words/references/* (4 audit docs + 1 screenshot)
Variants son canon — NO theme-extensibles
Decisión arquitectónica documentada hoy en THEMING.md §19. Los
variants (solid, outline, ghost, soft, surface, etc.) son
fijos a nivel del framework — paralelos a las 8 sema families. El
theme solo cambia palette/shadows. La fuente de verdad es la const
EIDOS_VARIANTS en lib/types.ts con 5 archetypes
(control / selection / chip / marker / tabs); los unions
TS se derivan de ella via [number] indexed access.
Variants component-specific (Banner inline/overlay/persistent,
Spinner bars/dots/ring, Button 'plain') viven en cada
components/{c}/types.ts. El lint
recipe-css-contract.test.ts > variant CSS selectors per component match the declared type union valida bidireccionalmente que CSS
selectors y type unions coincidan.
Words extension system — estado y siguiente sprint
Hecho (commits 1805b081 skeleton + 088d31ec F2.3a):
- F1.1 fix evento
contact-focusmolesto (transition-only + popover scope) - F1.2 declarar 9
data-*hardcoded en morfo - F1.3 i18n keys kebab-case
- F1.4 canonizar 9 event names a forma
{family}-{verb}-{variant} - F1.5 toolbarLayout
grouped|inlinecon ResponsiveProp - F2.1 audit + design de
WordsExtensioninterface (9 hooks) - F2.2 skeleton:
extension-types.ts+extension-registry.tscon 14 tests verdes - F2.3a tipos de table movidos a
extensions/table/types.tscon re-export enengine/document.ts
F2.3 hecho (sesión de hoy, 5 checkpoint commits + 143/143 tests verdes después de cada uno):
- F2.3a ✅ — table types →
extensions/table/types.ts(commit088d31ec) - F2.3b ✅ — table factories + predicates + value-set constants →
extensions/table/factories.ts(commita0afa44d) - F2.3d ✅ — table markdown serializer (serialize + parse + buildTableFromMarkdownRows) →
extensions/table/serialize-markdown.ts(commitd6f0af48) - F2.3c ✅ — table HTML serializer (serializeTableHtml + parseTableHtml + cell helpers) →
extensions/table/serialize-html.ts(commit5797e69b) - F2.3e ✅ — table render (renderTable + renderTablePlainText) →
extensions/table/render.ts(commitd8392735)
F2.3 pendiente — los 4 sub-pasos restantes son el refactor difícil:
-
F2.3f — path.ts navigation. Las refs de table están en BRANCHES dentro de funciones grandes (
resolvePath,updateNode,inferContainerKind), NO en helpers aislados. Extracción requiere:- Diseñar callback adapters para recursión (engine → extension → engine)
- O bien: el engine consulta registry sólo para "is this a node my extension owns?" y el resto del walk queda en engine
- Reflexión: ¿cabe redibujar el dispatcher al estilo visitor pattern? Sería más limpio.
-
F2.3g — normalize.ts. Mismo patrón: branches dentro de
normalizeBlock/normalizeNode. Extracción requiere mismo enfoque que F2.3f. -
F2.3h — operations.ts (2620 LoC, ~400-500 LoC table). Las funciones
insertTable*,deleteTable*,toggle-table-*son funciones standalone — extraíbles. Pero comparten utilities (tableCellOptions,tableOptions,replaceAt) que viven en el engine. Extraer requiere:- Mover utilities a
extensions/table/utils.tso re-exportar desde engine - Mover los ~20 reducers a
extensions/table/operations.ts - El dispatcher central (
applyWordsCommand) consulta registry.getCommand(opType) y fallback al switch existente
- Mover utilities a
-
F2.3i — registry wire-up. Crear
tableExtension: WordsExtensionque cabledea TODOS los hooks (commands, render, serialize, etc.) al registry. El engine constructor registratableExtensionpor defecto para backward compatibility. Después el dispatcher reescrito consulta registry. -
F2.3j — verify final.
Recomendación de orden para próxima sesión (lo que dije y se ha confirmado al hacer F2.3a-e):
- Antes de F2.3f/g/h, hacer F2.3i con stub vacía: construir
tableExtension: WordsExtensionque registra sólo nodeTypes ['table','table-row','table-cell'], commandNames y events. El engine al boot lo registra. Después en h-i se mueve la lógica gradualmente hacia los hooks. - F2.3h primero (operations) usando el stub. Cada reducer extraído pasa de
case 'insertTable':en switch a entrada enextension.commands. El switch del engine consulta registry.getCommand() antes del fallback. - F2.3f y F2.3g al final, donde el engine ya consulta registry para todo el resto.
Estado del extension system después de hoy:
WordsExtensioninterface ✅ (F2.2)WordsExtensionRegistrycon 14 tests verdes ✅ (F2.2)extensions/table/con types + factories + 2 serializers + render ✅ (F2.3a-e)- Engine NO consulta registry aún (importa funciones directamente de extension). El cambio a "engine consulta registry para tipo X" llegará en F2.3i.
Después F2.4 (code-block, plan similar), F2.5 (docs EXTENSIONS.md), F2.6 (verify final).
Cómo retomar mañana
-
Probar toggle-group en navegador:
- El bug del cascade fue corregido inlining derivations en
toggle-group.css. Pero el fix no se probó en navegador. Si el cascade sigue roto en algún caso, revisar las inlined expressions contrarecipes/base.ts > toggle.{solid,outline,ghost}-*.
- El bug del cascade fue corregido inlining derivations en
-
Avatar regenerado: la migración TSC v2.2 produce un orden de selector ligeramente distinto al original (
[data-color][data-variant]en lugar de[data-variant][data-color]). Mismo CSS efectivo — misma especificidad. Si algún test snapshot rompe, regenerar. -
Próximo trabajo natural en TSC (NO urgent):
- Si más componentes necesitan multi-part scope o composition, ya está todo soportado — solo añadir las recipes.
- Considerar
radius:/ring-color:como AtomicScope adicionales si el avatarring-colorcascade emerge como pattern recurrente.
Comandos útiles
cd G:/dev/svelte/vicen
npm run check # svelte-check
npm run test # full vitest run
npx vitest run src/uix/eidos # eidos-only (100 tests)
npm run generate:eidos-css # regen src/uix/eidos/generated/base.css
npx tsx scripts/eidos-purge.ts <route> # JIT purge para una ruta
Referencias canónicas tras la sesión
- Theming completo:
src/uix/eidos/THEMING.md(1700+ líneas, §18 reescrito hoy como "Cobertura universal de TSC", §7 ampliado con multi-part + composition). - Hand-offs:
CLAUDE.md§"Session hand-off — 2026-05-27 #5" (TSC v2.2 + cobertura universal de theming). - Recipe authoring guide:
THEMING.md§7 (TSC) + §8 (añadir componente nuevo). - Anti-eager-resolution guard:
src/uix/eidos/recipe-css-contract.test.tstest "forbids palette-derived tokens at :root scope".