You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/process/open-color-cage-2026-07.md

22 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 en parts: ['trigger','content'], el content estampa su propio color desde contexto) · float-panel (el "default fantasma" era info obsoleta: la recipe SÍ tenía accent) · 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-solid conserva 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 *Color prop open" en recipe-css-contract.test.ts (escaneo de fuente + resolución transitiva de alias; exentos comentados: OnionColor = string es 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 usa data-color + capa compartida: se centralizó un resolver en chart/context.ts (seriesColor/seriesSurface + nuevo seriesContrast) que mapea rol → --color-{role}-{solid,surface,contrast}, escala → --scale-{name}-{9,a2} (steps solid/surface-alpha del PALETTE_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 props color?: ColorRole → ComponentColorProp. Verificado en Chrome (rol/escala/crudo resuelven a color real; roles behavior- preserving). MetricsChartProps.color delega ahí → abierto también.
  • qr-code — queda FUERA por decisión de diseño: color es 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-color crudo sin par data-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 dynamic data-color stamp with data-color-custom" — el guard de TIPOS es ciego al runtime (solo lee types.ts); éste falla si un .svelte estampa data-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 (probe getComputedStyle) y con el contract test 30/30.

Follow-ups (menores, opcionales — para retomar cuando toque)

Ninguno bloquea; la iniciativa está cerrada. Por prioridad:

  1. [data-uix-docs] code pisa 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.)
  2. ringColor de 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 tipo seriesColor. Gap menor, no el bug.
  3. Límites de los 2 guards (documentar, no urgente): el de tipos no vigila chart (usa color?: inline en interfaces, sin alias *Color); el de runtime solo caza el patrón data-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.
  4. --card-color-custom en 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 en invalid + el intent del evento).

⚡ Empieza aquí (nueva sesión)

  1. Lee este doc entero + la memoria project-open-color-cage-2026-07-18.
  2. 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.
  3. 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).
  4. 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.ts renderSharedPaletteLayer): [data-color-custom] deriva los 10 slots --palette-* desde var(--color-custom) por color-mix. El forward renderRecipePaletteForward emite [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.ts con 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): color por soma/morfo, dos ejes intent+color. Ref: button. (2 bugs de framework arreglados: html-presence con v.literal('') → usar v.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:

  1. Recipe (lib/recipes/base.ts): añade tokens _palette-{slot} (host = token de rol actual, normalmente primary → behavior-preserving).
  2. CSS: quita las decls base --_X-accent-* + los bloques per-rol [data-X][data-color='rol']; renombra los consumidores var(--_X-accent-*) → var(--_X-palette-*).
  3. types: XColor → ComponentColorProp.
  4. wrapper: resolveComponentColor (estampa el trío + style).
  5. npm run generate:eidos-css + verifica el forward en generated/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)

  1. parts: SOLO se lee en la forma declarations: [...], no en { value, scope } (render-css.ts expandToDeclarationsWithParts l.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 con grep '\[data-X...\]\[data-color=' X.css DÓNDE se estampa data-color.
  2. Verifica recipe-exists FIABLE 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).
  3. Declara SOLO los slots con consumidor real (grep 'var(--_X-palette-slot)'). Slots sin var() 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).
  4. Trampa --x: inherit: un custom-prop con valor inherit NO guarda el token inherit; 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 en color: inherit (patrón de mark).
  5. 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).
  6. accent bare vs accent-soft: renombra -soft PRIMERO, luego el bare var(--_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-accent en 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-accent NO existe en el recipe → el comportamiento actual es "sin acento salvo data-color". Un host primary-border lo cambiaría a "siempre primary" (cambio silencioso). Verifica primero el default REAL (¿el content-wrapper siempre estampa data-color vía ctx?.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.css YA fallan 2 tests del contract (raw-color + font-size) — añadir a FIXED_TONE_COMPONENTS (recipe-css-contract.test.ts) o tokenizar. Su color es accesorio.
  • chronos — en WIP_TRACKS (excluido del contract); scheduler sin commitear.
  • card-group — el .svelte raíz NO tiene prop color (sus items componen Card, ya abierto). card-group-item sí; 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 *Color es más estrecho que ComponentColorProp (salvo la unión aditiva de tinta: | 'muted' | ...). Ancla: scripts/component-audit.ts:1327 o recipe-css-contract.test.ts. Esto ENFORCEA "la jaula siempre abierta" y cierra la iniciativa.
  • Docs: reference.md §25 + tracker continue-cleanroom-fixes-2026-07.md §THM-2 (registrar la reversión) + changelog.

Operativa (verificada esta sesión)

  • Dev server: preview_start con la config verify de .claude/launch.json (:5201). Reiniciar tras tocar un morfo (HMR no recarga morfo/*.ts fiable — compile cacheado por WeakMap).
  • ⚠️ check NO es baseline estable: la caché incremental de svelte-check aflora intermitentemente errores any PRE-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 en generated/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 getComputedStyle de --{c}-palette-{slot} sobre el elemento que estampa data-color; fuerza data-color='risk' y compara con un <div data-color='risk'> probe (--palette-{slot}); custom con style.setProperty('--color-custom', '#00b894') → espera color-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 toco eidos/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.

Powered by TurnKey Linux.