20 KiB
| title | type | audience | status | date |
|---|---|---|---|---|
| Handoff — Abrir la jaula del color (`color` = sistema completo en todos los componentes) | process | human + agent | CERRADO ✅ — Fases 0–5 + chart + fix Card/Avatar; 2 guards activos; sin huecos abiertos (qr-code excluido por diseño). Follow-ups menores al final. | 2026-07-19 (cierre definitivo — cuarta sesión) |
Handoff — Abrir la jaula del color
✅ CERRADO (2026-07-18, tercera sesión)
La iniciativa está COMPLETA. En esta sesión de cierre cayeron:
- La cola entera de standalones: skeleton · spinner (
| 'inherit'conservado) · textarea · metrics (featured icon,parts: ['icon']) · form (submit/reset,parts: ['submit','reset'], neutral = no-stamp) · field-langs (delegante) · radio-cards (delegante sobre el forward de radio-group) · image (migrado del mecanismo bespoke al forward — arregla las escalas que caían a named-color CSS) · color-picker (cross-portal RESUELTO: forward enparts: ['trigger','content'], el content estampa su propio color desde contexto) · float-panel (el "default fantasma" era info obsoleta: la recipe SÍ teníaaccent) · proof-of-human (apertura + exención fixed-tone formal compartida entre los tests de color y font-size) · card-group (ya abierto por delegación, sin cambios). - Fase 4 — tinta de contenido: text · heading · display · code · label
con RAMA EJE-PRIMERO (el eje
primary…on-solidconserva su significado de tinta vía var inline sin estampa; todo lo demás va por el forward). Text no estaba en la lista original — incluido por coherencia (mismo eje cerrado). - Fase 5 — guard estructural: test "keeps every component
*Colorprop open" enrecipe-css-contract.test.ts(escaneo de fuente + resolución transitiva de alias; exentos comentados:OnionColor = stringes MÁS ancho; chronos re-entra con su track). Docs:reference.md §25(reversión registrada) · tracker clean-room §THM-2 ·changelog.md §43· READMEs obsoletos de 13 componentes corregidos.
Huecos RESUELTOS (2026-07-19, tras decisión del usuario):
- chart — ABIERTO. Su
color(series line/area/bar/scatter/bubble, gauge, heatmap-hue, funnel/pie/bar-list/polar per-categoría, smith points) es SVG con resolución JS, así que NO usadata-color+ capa compartida: se centralizó un resolver enchart/context.ts(seriesColor/seriesSurface+ nuevoseriesContrast) que mapea rol →--color-{role}-{solid,surface,contrast}, escala →--scale-{name}-{9,a2}(steps solid/surface-alpha delPALETTE_SLOT_ STEP), y valor crudo → verbatim (surface =color-mix 15%, contrast = white). Los 3 builders inline (funnel, heatmap, calendar-heatmap) se refactorizaron para pasar por el helper. Los ~14 propscolor?: ColorRole→ComponentColorProp. Verificado en Chrome (rol/escala/crudo resuelven a color real; roles behavior- preserving).MetricsChartProps.colordelega ahí → abierto también. - qr-code — queda FUERA por decisión de diseño:
colores la tinta de los módulos del QR (contraste con el fondo para escaneabilidad), no encaja el sistema de escalas. No se abre.
Verificación adversarial + 2 fugas transitivas cerradas (2026-07-19). Un workflow de 23 agentes revisó los 16 componentes de la sesión de cierre (todos limpios) y su crítico de completitud cazó 2 fugas en componentes abiertos transitivamente (heredan un alias abierto) que nunca se tocaron ni verificaron:
- card-group-item estampaba
data-colorcrudo sin pardata-color-custom→ un valor CSS crudo caía a neutral. FIX:resolveComponentColor(ruta genérica). ⚠️ NO usa el split bespoke de Card (ver bug abajo). - s-text (
STextColor = TextColor, abierto gratis en Fase 4) seguía en el puente pre-Fase-4 → escalas/intents/crudo daban vars inexistentes. Migrado a la rama eje-primero + recipe_palette-text. - split-button revisado: OK (delega crudo en ButtonGroup→Button).
- Guard runtime NUEVO en
recipe-css-contract.test.ts: "pairs a dynamicdata-colorstamp withdata-color-custom" — el guard de TIPOS es ciego al runtime (solo leetypes.ts); éste falla si un.svelteestampadata-color={…}dinámico sin el par custom (literal estático exento; WIP excluido). Contract 30/30.
🐞→✅ Bug pre-existente descubierto + ARREGLADO: custom-color del FILL de
Card + Avatar (b93cc6c5c). Su split bespoke (--{c}-color-custom + bloque
derivador en su .css) quedó ENSOMBRECIDO por el forward compartido de THM-2
(misma especificidad, carga después → gana; para un valor crudo --palette-*
sin definir porque el shared layer lo deriva de --color-custom, no de
--{c}-color-custom → host neutral). FIX: migrados a resolveComponentColor
(genérico), borrados los bloques bespoke + 4 tokens de recipe huérfanos. De
paso: Card pisaba su propio seed (style= antes de {...rest}) → ambos
ahora COMPONEN con composeInlineStyle. Avatar RING + BADGE son canales
SEPARADOS (namespace propio, no ensombrecidos) → intactos. Lección: la
verificación de CASCADA CSS exige navegador — el workflow lo dio por bueno
leyendo solo código; solo el probe reveló que el forward lo pisa.
Los commits de la iniciativa (sesiones 3–4): 60a30c2e6 (skeleton/spinner/
textarea) · 838b01a00 (metrics/form) · 6b37beed6 (field-langs/radio-cards/
image) · c9db0a8d2 (color-picker) · 18026dfd8 (float-panel) · 04659558d
(proof-of-human + exención) · 67d7dc89c (Fase 4) · 0b2170d62 (Fase 5 guard
- docs) ·
6cdba0182(fugas transitivas + guard runtime) ·72a6569c3(chart) ·b93cc6c5c(fix Card/Avatar). Todo verificado en Chrome (probegetComputedStyle) y con el contract test 30/30.
Follow-ups (menores, opcionales — para retomar cuando toque)
Ninguno bloquea; la iniciativa está cerrada. Por prioridad:
[data-uix-docs] codepisa el<Code>de eidos en las demos — regla del shell de docs con especificidad (0,1,1) que gana sobre[data-code](0,1,0), pisando la tinta abierta dentro de las páginas de documentación. Fix: acotar a[data-uix-docs] code:not([data-code])y revisar reglas equivalentes del shell para h1–h6/label/display. (Pre-existente, no lo introdujo el color; se señaló como tarea aparte.)ringColorde Avatar es roles-only — acepta rol + valor crudo (por su seed propio--avatar-ring-color-custom) pero NO las 33 escalas. Es un canal secundario; abrirlo a escalas exigiría resolución tiposeriesColor. Gap menor, no el bug.- Límites de los 2 guards (documentar, no urgente): el de tipos no vigila
chart(usacolor?:inline en interfaces, sin alias*Color); el de runtime solo caza el patróndata-color={…}sin par custom — NO caza la clase s-text (var inline sin estampa) ni la clase Card/Avatar (cascada CSS que ensombrece). La verificación de cascada sigue siendo manual/navegador. --card-color-customen comentarios — quedan menciones históricas en comentarios (card-group-item, types) que ya no reflejan código vivo; limpieza cosmética.
⚠️ Estado del working tree (al cerrar)
El árbol tiene WIP SIN COMMITEAR de otros tracks (chat-composer, palabras):
base.ts + generated/base.css + ficheros chat-*/palabras. NO son míos —
no commitear. Todo mi trabajo del color está commiteado. Al operar sobre
base.ts: git show HEAD:base.ts + insertar solo mis bloques + swap→generate→
stage explícito→commit→restore (ver §Operativa).
Lo que sigue de este doc es el REGISTRO HISTÓRICO del handoff (patrones A–E y trampas, que siguen siendo la referencia para componentes nuevos).
Objetivo
El prop color de un componente debe aceptar todo el sistema de color del
ecosistema — por role, por intent, por paleta (33 escalas donantes) y por valor
(raw CSS) — en TODOS los componentes. Decisión de diseño del usuario
(2026-07-18): revierte la restricción de THM-2 (2026-07-12) que dejaba los
controles semánticos en subconjuntos; la jaula se abre siempre, sin
excepciones (incluida la tinta de contenido).
Doctrina de respaldo: theming/reference.md §25 (THM-2)
- §39 —
color= IDENTIDAD (role/scale/custom), desacoplada de la evaluación (que vive eninvalid+ elintentdel evento).
⚡ Empieza aquí (nueva sesión)
- Lee este doc entero + la memoria
project-open-color-cage-2026-07-18. - El motor y el patrón están LISTOS y probados. Lo que queda es ~11 componentes standalone HETEROGÉNEOS (§Cola). NO son un batch uniforme: cada uno rompe el patrón de una forma distinta y con su propia trampa.
- Verifica el estado:
npm run check(⚠️ el total oscila 76↔94, NO es baseline estable; verifica CERO errores en los componentes que toques, no el total). - Arranca por los MENOS peligrosos de la cola (skeleton/spinner/textarea), no por los portalados (color-picker/float-panel).
Estado — HECHO
Motor (Fase 0) ✅ — la costura compartida, LISTA
ComponentColorProp(src/uix/eidos/lib/types.ts) =ComponentColor | (string & {})— el tipo canónico ABIERTO al que todos apuntan.resolveComponentColor(color)(src/uix/eidos/lib/component-color.ts) — el helper del wrapper:{ dataColor, isCustom, customStyle }. Canónico →data-color; valor CSS crudo →data-color-custom+ seed--color-custom.- Derivación custom compartida (
render-css.tsrenderSharedPaletteLayer):[data-color-custom]deriva los 10 slots--palette-*desdevar(--color-custom)porcolor-mix. El forwardrenderRecipePaletteForwardemite[data-{c}][data-color], [data-{c}][data-color-custom]→ toda receta con tokens_palette-*gana roles + 33 escalas + custom con CERO CSS extra. - Guard
recipe-css-contract.test.tscon la forma del forward + invariante custom.
Fase 1/2a/2b ✅ (commit 149c0fef6, pre-continuación)
- Eidos-wrapper (14): radio-group·checkbox·stepper·toggle-group·select·badge· editable·file-upload·tag-group·tags-input·surface·avatar·card. Ref: checkbox.
- Soma-routed (button/switch/toggle):
colorpor soma/morfo, dos ejes intent+color. Ref: button. (2 bugs de framework arreglados:html-presenceconv.literal('')→ usarv.propRef; custom pisaba intent evaluativo → anular.)
Fase 3 — familias (esta sesión, 7 commits) ✅
| Commit | Componentes |
|---|---|
861427fe1 |
Field family (field base + search/password/mask/number/css/color/date/date-range/time-field) + calendar + month-grid + time-picker/time-range-picker + fix regresión runtime + 9 fixes de tipo (tests switch/toggle colorCustom) |
4adcc4c7f |
sweep custom-threading: 12 sub-partes date/time (trigger/calendar/clock/input/root) por resolveComponentColor |
889b03dae |
year-grid + range-calendar |
c5699bd01 |
familia lista/data-grid (10): table·tree-grid·tree-view·virtual-list·virtual-grid·feed·drag-drop·grid-list·clipboard·carousel |
528ddaa7f |
listbox + timeline + combobox |
1efa03439 |
link + mark (tinta de contenido) |
28aacf092 |
button-group + highlight (delegan a Button/Mark) |
Cada familia verificada en Chrome (role · escala · custom → token compartido exacto).
Los PATRONES (elige el correcto por componente)
Antes de tocar un componente, identifica su patrón:
A. Eidos-wrapper (estampa data-color directo). Ref: checkbox. Ya tiene
forward-ready recipe → solo tipo + wrapper resolveComponentColor.
B. Soma-routed (color por soma+morfo). Ref: button. Toca soma types/provider/
component + morfo + wrapper. Dos ejes intent+color.
C. Mini-recipe (cascada --_X-accent per-rol → forward). Ref: month-grid. El
patrón dominante de Fase 3:
- Recipe (
lib/recipes/base.ts): añade tokens_palette-{slot}(host = token de rol actual, normalmente primary → behavior-preserving). - CSS: quita las decls base
--_X-accent-*+ los bloques per-rol[data-X][data-color='rol']; renombra los consumidoresvar(--_X-accent-*)→var(--_X-palette-*). - types:
XColor→ComponentColorProp. - wrapper:
resolveComponentColor(estampa el trío +style). npm run generate:eidos-css+ verifica el forward engenerated/base.css.
D. Tinta de contenido (color:/background: per-rol directo). Ref: link
(1 slot text) / mark (element+text). Reemplaza la cascada color: var(--color-{rol}-text)
por color: var(--_X-palette-text) + recipe _palette-text.
E. Delegante (pasa el color a un hijo ya abierto). Ref: button-group
(contexto→Button) / highlight (→Mark). Solo tipo + wrapper.
⚠️ TRAMPAS (todas cazadas esta sesión — NO repetir)
parts:SOLO se lee en la formadeclarations: [...], no en{ value, scope }(render-css.tsexpandToDeclarationsWithPartsl.2183). Si la cascada vive en[data-X-root]o[data-X-content](no el provider bare), el recipe necesita'_palette-slot': { parts: ['root'], declarations: [{ value, scope: 'host' }] }. Verifica congrep '\[data-X...\]\[data-color=' X.cssDÓNDE se estampa data-color.- Verifica
recipe-existsFIABLE antes de añadir (grep^\t'?X'?: \{). Varios componentes YA tienen recipe (z-index/otros): drag-drop, combobox, timeline, color-picker, float-panel. FUSIONA en el existente, NO dupliques la clave (clave duplicada = el último gana, silencioso; svelte-check lo marca). - Declara SOLO los slots con consumidor real (
grep 'var(--_X-palette-slot)'). Slots sinvar()consumidor = token de recipe HUÉRFANO → falla el orphan test. Si TODO estaba muerto (virtual-list/grid) → NO pongas recipe (tipo abierto + wrapper inerte basta). - Trampa
--x: inherit: un custom-prop con valorinheritNO guarda el tokeninherit; hace que el prop herede del padre (→ inválido). Para "default = inherit" usa una regla[data-X][data-color],[…custom]{ color: var(--_X-palette-text) }y deja la base encolor: inherit(patrón demark). - Cross-portal: si el componente RE-DECLARA su accent en 2 scopes (trigger +
contenido portalado), el forward en el root NO alcanza el portal. Requiere data-color
- forward en el scope del portal (color-picker cae aquí — ver §Cola).
accentbare vsaccent-soft: renombra-softPRIMERO, luego el barevar(--_X-accent)(con)de cierre) → evita el solape de prefijo.
Cola — ~11 standalones HETEROGÉNEOS (lo que falta)
Cada uno es trabajo INDIVIDUAL con su análisis y verificación propia. Orden sugerido: los menos peligrosos primero.
Patrón D/simple (menos peligrosos — empieza aquí)
- skeleton, spinner, textarea, metrics, form — tienen cascada per-rol pero cada
uno pone una PROPIEDAD distinta (
color/background/border) directa. Analiza qué propiedad tinta cada uno y a qué slot mapea (grep 'data-color=' + var(--color-). spinner además tiene| 'inherit'en su tipo (conservar como unión aditiva). - field-langs —
FieldLangsColor = ColorRole, no estampa data-color (¿compone Field?); investigar cómo colorea. - radio-cards —
AffirmativeColorRole; compone RadioGroup (ya abierto). Probable delegante (patrón E) o mini-recipe. - image —
ImagePlaceholderColor = ColorRole | (string & {})(ya semi-abierto); el placeholder. Poco trabajo.
Patrón C con TRAMPA (compound/portalado — CUIDADO, requieren verificación)
- color-picker — cascada 1 slot (accent→border) en
[data-color-picker], PERO re-declara--_color-picker-accenten 2 scopes (trigger l.28 + contenido portalado l.99). El forward en el root no alcanza el portal → el contenido portalado necesita su propio data-color+forward. Compound (root+trigger+ channel-input estampan data-color; las partes threading raw necesitan resolveComponentColor como el sweep de combobox). Recipe YA existe (fusionar). Intentado y revertido esta sesión por el matiz cross-portal. - float-panel — cascada 1 slot (accent→border) en
[data-float-panel-content](parte →parts: ['content']). PERO su default base es--_float-panel-accent: var(--float-panel-accent)y--float-panel-accentNO existe en el recipe → el comportamiento actual es "sin acento salvo data-color". Un hostprimary-borderlo cambiaría a "siempre primary" (cambio silencioso). Verifica primero el default REAL (¿el content-wrapper siempre estampa data-color víactx?.color? ¿ctx.color default 'primary'?). Recipe YA existe (fusionar). Intentado y revertido esta sesión.
Especiales
- proof-of-human — fixed-tone. Sus literales de color/font-size en
clock.css/rotate-align.cssYA fallan 2 tests del contract (raw-color + font-size) — añadir aFIXED_TONE_COMPONENTS(recipe-css-contract.test.ts) o tokenizar. Sucolores accesorio. - chronos — en
WIP_TRACKS(excluido del contract); scheduler sin commitear. - card-group — el
.svelteraíz NO tiene propcolor(sus items componen Card, ya abierto).card-group-itemsí; revisar si necesita apertura.
Fase 4 — Tinta de contenido (code, display, heading, label)
Otro eje (primary|secondary|muted|disabled|on-solid). Abrir como unión que
conserva su eje: ComponentColorProp | 'muted' | 'disabled' | 'on-solid'; cablear
el color de texto al slot text de la paleta (patrón D, como link/mark). Nota:
link/mark YA se hicieron con este patrón — reúsalo.
Fase 5 — Guard estructural + docs
- Guard: test que falla si algún tipo
*Colores más estrecho queComponentColorProp(salvo la unión aditiva de tinta:| 'muted' | ...). Ancla:scripts/component-audit.ts:1327orecipe-css-contract.test.ts. Esto ENFORCEA "la jaula siempre abierta" y cierra la iniciativa. - Docs:
reference.md §25+ trackercontinue-cleanroom-fixes-2026-07.md §THM-2(registrar la reversión) + changelog.
Operativa (verificada esta sesión)
- Dev server:
preview_startcon la configverifyde.claude/launch.json(:5201). Reiniciar tras tocar un morfo (HMR no recargamorfo/*.tsfiable — compile cacheado por WeakMap). - ⚠️
checkNO es baseline estable: la caché incremental de svelte-check aflora intermitentemente erroresanyPRE-EXISTENTES de demos ajenos (heroscrolling/ animations/palabras/alpha/temas): oscila 76↔94. La verificación FIABLE es CERO errores en los componentes TOCADOS (grep 'ERROR "src...components/X'), no el total. - Tras tocar un recipe:
npm run generate:eidos-css+ verifica el forward emite engenerated/base.css(grep '\[data-X...\]\[data-color\],'). NO hace falta tras solo.svelte/.ts. - Contract test:
npx vitest run src/uix/eidos/recipe-css-contract.test.ts— 26/28 pasan; los 2 fallos son proof-of-human (pre-existente). >2 fallos = tu cambio (orphan/phantom/forward). - Navegador: probe
getComputedStylede--{c}-palette-{slot}sobre el elemento que estampa data-color; fuerzadata-color='risk'y compara con un<div data-color='risk'>probe (--palette-{slot}); custom constyle.setProperty('--color-custom', '#00b894')→ esperacolor-mix.
⚠️ ÍNDICE GIT COMPARTIDO
El working tree tiene WIP de OTROS tracks sin commitear. Al commitear:
git reset -q + git add de rutas EXPLÍCITAS + git diff --cached --name-only
para verificar ANTES de git commit -F - (backticks en el mensaje → usar -F, no -m).
EXCLUIR siempre:
menubar(eidos/soma/morfo),dropdown-menu,palabras,words— track palabras.soma/components/virtual-list/*+web/routes/uix/components/virtual-list/+page.svelte— el track de chat (chat-*) extiende VirtualList ("anchor end/chat mode"), AJENO al color. Yo solo tocoeidos/components/virtual-list/*.web/routes/alpha/,.claude/settings.local.json.- Cambios de comentario "Words"→"Palabras" en css ajenos (p.ej. color-picker.css).
Referencias de patrón (copiar de estos)
- Mini-recipe (C): month-grid (recipe
_palette-*+ css + types + wrapper). - Con
parts: combobox (parts: ['control','input','trigger','content']) / table (parts: ['root']). - Tinta (D): link (1 slot) / mark (element+text, regla colored-only).
- Delegante (E): button-group / highlight.
- Sweep de sub-partes que threading raw: workflow con ref
select-trigger.svelte.