Actualiza el handoff y el changelog §43 al estado FINAL de la iniciativa
(cerrada, cuarta sesión):
- open-color-cage-2026-07.md: añade las 2 fugas transitivas cerradas
(card-group-item · s-text) + el guard runtime, el fix del bug de Card/Avatar
(`b93cc6c5c`), la lista completa de commits (sesiones 3–4), y una sección
«Follow-ups» con lo menor que queda (override docs-chrome sobre `<Code>`,
ringColor roles-only, límites de los 2 guards, comentarios históricos) +
nota del estado del working tree (WIP de chat/palabras sin commitear, ajeno).
- changelog.md §43: el bug de Card/Avatar pasa de «flageado, NO arreglado» a
ARREGLADO con el resumen del fix (migración a resolveComponentColor + compose
del seed + canales ring/badge separados intactos).
Sin cambios de código. La iniciativa queda CERRADA sin huecos abiertos
(qr-code excluido por diseño). Contract 30/30.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BUG (descubierto en la verificación adversarial de la iniciativa): un color
CSS crudo (p.ej. `color="#3b82f6"`) NO pintaba el FILL de Card ni Avatar —
caía a neutral. Card/Avatar (Fase 2a) usaban un split bespoke pre-helper
(`isCanonicalColor` + seed `--{c}-color-custom` + bloque derivador
`[data-{c}][data-color-custom]{--_{c}-palette-*: …}` en su .css). Ese bloque
quedó ENSOMBRECIDO por el forward compartido de THM-2 en `generated/base.css`
(misma especificidad (0,2,0), carga después → gana); para un valor crudo
`--palette-*` está sin definir (el shared layer lo deriva de `--color-custom`,
NO de `--{c}-color-custom`) → host neutral.
Fix (los alinea con TODO el resto de la iniciativa):
- card.svelte / avatar.svelte: el split bespoke → `resolveComponentColor`
(genérico: canónico→`data-color`; crudo→`data-color-custom` + seed
`--color-custom`, que el forward YA consume). Ambos ahora COMPONEN el seed
con el `style` del consumidor (`composeInlineStyle`) para que `...rest` no lo
pise — Card lo pisaba (style antes de rest), por eso el custom se perdía
incluso con el seed correcto.
- card.css / avatar.css: borrados los bloques bespoke `[data-{c}][data-color-
custom]{…}` (muertos + ensombrecidos). Las variant slices leen
`--_{c}-palette-*`, que el forward llena para rol/escala/custom uniformemente.
- Avatar RING y BADGE son canales de color SEPARADOS (`--avatar-ring-color-
custom` / `--avatar-badge-color-custom`): NO se tocan — usan su propio
namespace de tokens, el forward no los ensombrece y funcionan (verificado).
- base.ts: quitados los 4 tokens de recipe muertos (`card`/`avatar` ·
`color-custom` + `color-custom-contrast`); ring/badge se conservan.
Verificado en Chrome: Card real (picker del demo) + Avatar sintético pintan el
custom en soft/solid/outline/ghost, componiendo el style del consumidor;
roles/escalas siguen exactos (behavior-preserving); ring + badge intactos.
Orphan test resuelto (los `-contrast` ya no huérfanos). Los 2 fallos residuales
del contract son WIP de chat-composer (otro track, sin commitear), ajenos.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cierra la UI de F4 (las tres piezas elegidas por el usuario: listas · merge/split
· count/TOC · resize). Alcance v1 acordado: solo COLUMNAS (sin filas ni snap).
palabras-table-resize.svelte (NUEVO): overlay de tiradores en el borde superior
de cada tabla top-level (uno por borde interno de columna), materializados solo
al pasar el puntero por la tabla (mirror de palabras-column-inserter: medición
frame-relative, remeasure en api.html + scroll/resize). Arrastrar un tirador
redimensiona la columna a su IZQUIERDA:
- feedback EN VIVO por inline `width` en la celda de la 1ª fila durante el drag
(el navegador dimensiona la columna por la celda más ancha),
- commit a `cell.width` en UN paso de historia al soltar (nada durante el drag),
- doble-clic = auto (limpia el width).
`cell.width` es una LONGITUD CSS en string (el campo `length` del panel commitea
p.ej. "314px") — un número crudo renderiza `width:314` inválido; el commit usa
`${w}px`. Restringido a tablas TOP-LEVEL (path del commit `[table,0,col]`).
Filas + snap a fracciones (1/2·1/3·2/3) diferidos a v1.1. Langs `resize-column`
diferido (entanglement; ts() por fallback).
Verificado Playwright headless: hover tabla → 2 tiradores (3 columnas); arrastrar
+80px → col0 234→314 (las otras se ajustan 183/196), commit width:314px; doble-
clic → auto restaurado (234/221/238); 0 errores consola. Screenshot MIRADO
(tirador de acento en el borde hover). check 0 errores palabras.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cierra F4.7. Los derivados wordCount/charCount/tableOfContents ya vivían en el
snippet del provider; faltaba su affordance. Decisión del usuario (2026-07-19):
barra de estado al pie con el recuento (readout pasivo) + botón «Índice» que
abre un popover de encabezados; clic hace scroll al heading.
- palabras-status.svelte (NUEVO): «N palabras · M caracteres» + `<Popover>` con
`tableOfContents` (sangrado por `level` vía data-level); jumpTo hace
scrollIntoView del `[data-palabras-id]` del encabezado y cierra el popover.
El trigger se deshabilita sin encabezados.
- palabras.svelte: montada al pie del shell (los 3 modos — el recuento es un
readout del documento, no una propiedad; el usuario lo quitó del panel).
- palabras-chrome.css: sección [data-palabras-status] + outline (tokens).
El índice va en affordance PROPIA (no el panel de propiedades — decisión del
usuario 2026-07-18: es navegación, no una propiedad de página).
Langs es/en (status.words/chars/outline/untitled) DIFERIDOS (fichero de langs
entangled con el merge pendiente; los `ts()` renderizan por fallback).
Verificado: check 0 errores palabras; Playwright headless: «45 palabras · 253
caracteres», popover con 2 encabezados (niveles 1/2), clic en «Estrategias» →
popover cierra + h2 en vista; 0 errores consola. Screenshot MIRADO.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Chart era el último hueco de la iniciativa. Su `color` NO tinta labels/ejes:
tinta la MARCA de datos (stroke de línea, fill de área/barra/burbuja, hue de
heatmap, arco de gauge, porción de pie/funnel/bar-list/polar, punto de smith).
Es SVG con resolución JS, así que NO usa el `data-color` + capa de paleta
compartida (eso es para cascadas CSS en HTML) — el mecanismo es propio.
Resolver central en `chart/context.ts`:
- `seriesColor` / `seriesSurface` reescritos + `seriesContrast` nuevo, todos
`number | ComponentColorProp`. Un índice cicla `SERIES_ROLES` (multi-serie,
sin cambios). Un valor explícito pasa por `resolveChartColor`:
· rol/intent → `var(--color-{role}-{solid,surface,contrast})`
· una de las 33 escalas → `var(--scale-{name}-{9,a2})` (steps solid /
surface-alpha del `PALETTE_SLOT_STEP`); contrast → white (no hay token)
· valor CSS crudo → verbatim (surface = `color-mix 15%`, contrast = white;
sin contraste garantizado, el trade-off de cualquier custom).
- `ChartContext.color` widenado a `number | ComponentColorProp`.
Todos los props `color?: ColorRole` (~14: ChartSeriesProps, Bubble, Sparkline,
ChartCategory, BarList, Funnel, Calendar/Heatmap, Smith×2, Gauge) →
`ComponentColorProp`. Los 3 builders que armaban el fill inline (funnel:
solid+contrast, heatmap:56, calendar-heatmap:118) refactorizados para pasar
por el helper — funnel pierde su `roleOf` local.
qr-code queda FUERA por decisión de diseño (color = tinta de módulos del QR,
contraste con el fondo, no encaja escalas).
Verificado en Chrome: las 3 ramas del resolver resuelven a color real
(--scale-teal-9→oklch, --scale-teal-a2→rgba translúcido, #3b82f6→verbatim,
mix translúcido ok); marcas existentes = primary-solid (roles behavior-
preserving). check chart limpio; contract 30/30. Docs: changelog §43 +
handoff actualizados (hueco chart cerrado, qr-code excluido por diseño).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Expone los comandos mergeTableCells/splitTableCell del motor en el chrome,
gateados a celda activa de tabla top-level.
Desviación declarada respecto a "solo UI": los comandos resolvían la celda vía
`state.selection`, que el chrome PIERDE al abrirse fuera del editor (el menú
Editar / el panel). Ajuste mínimo del motor, consistente con mover/eliminar
(que ya operan por `loc.path`): mergeTableCells/splitTableCell aceptan un `at`
explícito `[block,row,cell]` (fallback a la selección). Test nuevo con
`selection: null`.
UI (eidos):
- palabras-block-actions.ts: `tableCellActions(loc, activeEl)` deriva inCell +
`at` (desde loc) + canMergeRight/Down/Split (geometría del <td> renderizado) +
dispatch `mergeActiveCells`/`splitActiveCell`.
- Menú Editar (barra): 3 items gateados (Combinar derecha `cols:2` / abajo
`rows:2` / Dividir).
- Panel: fila de acciones de celda PROPIA — el `blockFoot` (mover/insertar) se
oculta para celdas structural, así que las celdas tienen su pie.
Restringido a tablas top-level (el resolver del motor direcciona
[block,row,cell]). Langs es/en de las 3 acciones DIFERIDOS (fichero de langs
entangled con el merge pendiente; los `ts()` renderizan por fallback).
Verificado: motor 37 tests (2 nuevos del `at`); check 0 errores palabras/table;
Playwright headless en AMBAS superficies: barra Editar → Combinar derecha →
celda colspan=2 (B1 absorbida) → Dividir la restaura; pie del panel acoplado
igual. 0 errores de consola.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Los campos start/numbering del panel de lista (motor + escritura ya cableados
desde 5411becb5) aparecían para viñetas y tareas, donde no tienen sentido.
Añade kinds:['ordered'] a ambos — mismo filtro que ya gobierna marker/box.
Verificado en navegador: el modelo start:3/numbering:'i' proyecta a
<ol start="3" type="i"> y el navegador pinta lower-roman (iii, iv).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verificacion adversarial multi-agente (17 componentes de la sesion de cierre,
23 agentes) cazo 2 fugas que la verificacion individual en Chrome no cubrio —
componentes abiertos TRANSITIVAMENTE (heredan un alias abierto) y nunca
tocados ni verificados:
- **card-group-item**: estampaba `data-color={color}` CRUDO en ambas ramas
(button + div) sin `data-color-custom` ni seed → un valor CSS crudo (que el
tipo abierto `CardColor=ComponentColorProp` permite) caia a neutral y se
descartaba en silencio. Ahora pasa por `resolveComponentColor` (ruta
GENERICA `--color-custom`, que el forward compartido `[data-card][data-color-
custom]` consume). NOTA: NO usa el split bespoke de Card (`--card-color-
custom`) a proposito — esa ruta esta ENSOMBRECIDA por el forward de THM-2 y
no pinta (bug pre-existente de Card/Avatar, flageado aparte). Verificado en
Chrome: teal=escala exacta, #3b82f6=palette-solid exacto, neutral=sin estampa.
- **s-text**: `STextColor = TextColor` se abrio gratis al ensanchar TextColor
en Fase 4, pero su .svelte seguia en el puente pre-Fase-4
(`var(--color-content-${colorKey})` para TODO valor) → escalas/intents/crudo
producian vars inexistentes. Migrado a la rama eje-primero de Fase 4 (espejo
de Text): ink axis inline sin estampa; resto por el forward. Recipe
`_palette-text` + regla colored-only + stamp. Verificado en Chrome
(risk/teal exactos, custom aplicado, default sin estampa).
- **split-button**: revisado, SIN cambios — delega `{color}` crudo en
ButtonGroup→Button (ya verificado); no re-estampa.
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), asi que un stamp dinamico sin el par custom pasa
inadvertido (justo la clase de card-group-item). Este falla si algun .svelte
estampa `data-color={…}` dinamico sin `data-color-custom` en el mismo fichero;
literal estatico (`data-color="neutral"` del badge +N de avatar-group) exento;
WIP excluido. 30/30 verde.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
El fix crítico de la auditoría (insertLineBreak enruta code/table a \n a
CUALQUIER profundidad, no solo top-level) iba sin test. Añade 5 casos en
paragraph-ops.test.ts:
- code top-level → \n, sin nodo break (fija la rama).
- code anidado en callout → \n, sin break (DISCRIMINA).
- code anidado en columna → \n, sin break (DISCRIMINA).
- celda de tabla → \n vía Shift+Enter, no solo Enter (fija la rama).
- tabla anidada en callout → \n en la celda (DISCRIMINA).
Discriminación demostrada revirtiendo el fix al check solo-top-level: caen
EXACTAMENTE los 3 casos anidados y los 2 top-level siguen verdes. Con el fix
puesto, 30/30 verde; check con 0 errores en palabras.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Los 3 tests recomendados por la auditoría (autorados + verificados
adversarialmente: cada uno falla si se revierte el invariante que fija).
- normalize.test.ts (nuevo): fija el flanqueo de texto de los break. 5 casos
vía normalizeDocument con factories reales: break al inicio / al final / entre
dos breaks / junto a un link → siempre queda un text inline a ambos lados;
break ya flanqueado → sin empties espurios. Caen si se borra el paso de
normalize.ts:299-317.
- palabras-provider.svelte.test.ts (+2): (a) slashMenuItems filtra por la
allowlist insertableBlocks (['heading','paragraph'] excluye code-block; []
vacía; undefined = todo) — cae si se quita el filtro del motor. (b) paridad
canonicalSlashType (soma) === canonicalBlockType (eidos) sobre el set real de
ids del registry — cae si las dos copias byte-idénticas del cruce de capa
divergen.
- palabras-provider.svelte.ts: canonicalSlashType pasa a exportarse (única
fuente del cambio) para poder compararla contra la de eidos.
Verificado: 58 tests verdes (5 + 53); check con 0 errores en palabras.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Guard: test nuevo "keeps every component `*Color` prop open (THM-2 — the
cage stays open)" en recipe-css-contract.test.ts — escanea los alias
`export type XColor = …` de components/*/types.ts y FALLA si alguno se
estrecha por debajo de ComponentColorProp. Uniones aditivas ('muted',
'inherit', 'absent'…) pasan; alias resuelven transitivamente
(DatePickerColor = CalendarColor); WIP_TRACKS (chronos) re-entra con su
track; exento comentado: OnionColor = string (MAS ancho que la jaula,
normalizacion pendiente). Verde con el inventario real: 60+ alias abiertos.
Docs (todo lo aplazado del track):
- reference.md §25: reversion de los subconjuntos THM-2 registrada como
doctrina (color = sistema completo en TODOS; identidad ≠ evaluacion;
enforcement = el guard).
- Tracker clean-room §THM-2: nota de reversion (los narrows que THM-2 dejo
"por diseño" quedan abiertos, Avatar.Badge incluido).
- changelog.md §43: cronica de la iniciativa completa (mecanica, fases,
huecos señalados).
- Handoff open-color-cage-2026-07.md → CERRADO con el resumen de la tercera
sesion (cola completa, F4, F5, commits) y los 2 huecos pendientes de
decision: chart (11 props ColorRole) y qr-code (color inline semi-abierto,
invisible para el guard). El cuerpo queda como referencia de patrones A–E.
- 13 READMEs con afirmaciones ya falsas corregidos (roles-only, subsets,
"Locking to ColorRole", tablas de props): badge, css-field, field-langs,
float-panel, link, listbox, mark, metrics (solo el icon; Chart delega en
chart y queda como esta), password-field, proof-of-human (incl. la fila de
decision "descartar" → "hecho (reversion)"), range-calendar, textarea,
timeline. banner NO se toca (su ColorRole es del eje intent, otra cosa).
Contract test: 29/29 verde.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Auditoría multi-agente (hallazgos verificados adversarialmente). Arreglos:
- CRÍTICO: insertLineBreak detectaba code/table solo en el bloque TOP-LEVEL, así
que un code block ANIDADO (dentro de una columna o callout) recibía un nodo
break (ilegal en code text-only — validate lo rechaza) en vez de un \n literal.
Ahora camina el container path: el code block es la hoja, la table un ancestro
de la celda.
- El parser de NOTACIÓN (parsePalabrasScheme) no conocía marks/blocks, así que un
esquema en forma de notación no podía gobernar (marks=/blocks= caían en el
scale-shortcut como NaN y se descartaban). Añadido el parseo (coma-lista; vacío
= ninguno), restaurando la paridad notación<->objeto (ESQUEMA §9).
- Bubble: el Select de familia se renderizaba VACÍO cuando el esquema declara
fonts:[] (dropdown muerto). showFamily ahora exige allFonts.length > 0.
- Doc: marks NO gobierna color/background (el color va por editable + la paleta)
— corregido en types.ts + ESQUEMA §6b, que lo documentaban por error.
Tests nuevos: parseo de notación marks/blocks + lock-down (marks:[]/blocks:[] =
nada permitido, que sobrevive al merge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Los 5 primitivos tipograficos tenian OTRO eje de color: la tinta de contenido
(primary|secondary|muted|disabled|on-solid, donde primary = --color-content-
primary, NO el rol), interpolada inline por el wrapper (--_x-color) sin
data-color ni cascada CSS. Abrir el tipo a pelo rompia: color='teal' →
var(--color-content-teal) inexistente; 'muted' via resolveComponentColor →
--color-custom: muted invalido.
Apertura con RAMA EJE-PRIMERO (identica en los 5):
- tipo: `ComponentColorProp | 'muted' | 'disabled' | 'on-solid'` (label sin
on-solid — conserva su union exacta de 4). primary/secondary ya estan en
ComponentColorProp pero la rama los captura ANTES y les conserva el
significado de TINTA ("conserva su eje", handoff §Fase 4).
- wrapper: valor ∈ eje → var inline como siempre, SIN estampa; resto →
resolveComponentColor (canonico → data-color; raw → data-color-custom +
seed). Los dos caminos son mutuamente excluyentes.
- css: regla colored-only `--_x-color: var(--_x-palette-text)` — no pelea con
el eje porque el eje nunca estampa.
- recipe: `_palette-text` nueva por componente (host = fallback-only).
Text NO estaba en la lista del handoff (code/display/heading/label) pero tiene
la MISMA union cerrada y es el primitivo modelo de la familia — incluido y
declarado (dejarlo fuera era una incoherencia de la propia fase).
Verificado en Chrome: heading con las DOS ramas y prop real (chip muted →
inline var sin estampa; risk/teal/custom = probe exacto); label idem (la demo
monta color='primary' real → inline, y risk exacto sin el inline); text/
display risk/custom exactos; code el token fluye exacto — su pisoton visible
es una colision PRE-EXISTENTE del chrome de docs ([data-uix-docs] code, ya
pisaba el eje viejo; señalada como tarea aparte). Contract test 28/28 —
primera vez todo verde.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Apertura (era el ultimo standalone con cascada):
- El accent del reto (2 slots: solid + element-soft) pasa de 7 bloques
per-rol al forward compartido en la parte `root` (display:contents →
cascada a todos los retos, verificado que el skin hereda). Recipe NUEVA
(su comentario "needs no recipes/base.ts entry" quedaba obsoleto).
- `ProofOfHumanColor = ComponentColorProp` (era ColorRole, README decia
"8 roles-only"). Root + los 3 retos (clock/path-trace/rotate-align)
estampan via resolveComponentColor — sus stamps son CSS-muertos hoy, pero
con el tipo abierto habrian estampado raw strings como data-color.
Exencion fixed-tone (los "2 fallos pre-existentes" del contract):
- FIXED_TONE_COMPONENTS se hoista y comparte entre el test de color Y el de
font-size, añadiendo proof-of-human: la paloma mensajera + carta son
paleta identitaria del objeto dibujado (anotada linea a linea) y los
font-size 13px/11px son unidades de usuario SVG del viewBox, no escala
tipografica (anotacion añadida al .ra-card que no la tenia). Nota: esto
tambien exime a natural-time-picker/color-picker del test de font-size
(hoy limpios; su doctrina fixed-tone ya esta documentada).
card-group: revisado, SIN cambios — card-group-item ya usa `CardColor`
(= ComponentColorProp desde Fase 1): abierto por delegacion.
Verificado en Chrome: default primary = probe exacto (2 slots), risk/jade
exactos, skin hereda por display:contents, custom → seed + mix element.
Contract: mis 2 pre-existentes ELIMINADOS; unico fallo restante =
chat-message `--radius-xs` (WIP vivo de otra sesion, ajeno).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
El esquema (fijado por el desarrollador, NO editable por el usuario) ahora gobierna qué
marcas y qué tipos de bloque puede usar el usuario, no solo el panel. Allowlists top-level
`marks` + `blocks` en PalabrasScheme (doctrina «sin declarar = todo permitido», como
`editable`) + lectores `schemeAllowsMark` / `schemeAllowsBlock`.
- Bubble: filtra las 5 marcas booleanas + gatea sub/sup/tamaño/familia por `marks` (los
valores paramétricos siguen saliendo de fonts/sizes/palette).
- Slash: filtrado en el MOTOR vía nuevo opt `insertableBlocks` del provider (para que el
índice de teclado no derive) — filterSlashCommandItems(query, allowed) con mapeo canónico
id→tipo.
- Menús eidos (menubar / panel / column-inserter): filtran vía insertableBlockTypes(scheme).
ESQUEMA.md §6b documentado. Verificado: 64 tests (gobernanza + provider con el opt), check
0 errores en palabras, repro Playwright default (bubble 5 marcas + Aa, slash 14 items, 0
errores de consola).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El accent (regla del header, focus ring, grip de resize) pasa de 8 bloques
per-rol al forward compartido en la parte `content` (el panel portala y
estampa su propio `data-color` via ctx). El default real NO era el "fantasma"
que temia el handoff: la recipe SI tiene `accent: neutral-border` y el root
siempre estampa `color='neutral'` por defecto → estampa-siempre + regla
generica es fiel (host del forward = neutral-border, el valor del default).
El fallback sin-ctx (`var(--float-panel-accent)`) queda intacto.
Tipos: `FloatPanelColor = ComponentColorProp` (era ColorRole); el Content
resuelve via resolveComponentColor con seed `--color-custom` para raw values.
Verificado en Chrome: default neutral = probe exacto; threat/iris = token
compartido; sin estampa → fallback neutral-border; custom → color-mix del
seed. Contract test sin fallos nuevos.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
La trampa: el accent vivia en DOS scopes — cascada per-rol en el root y una
re-declaracion fija `primary-border` en el contenido portalado. El forward del
root nunca alcanzaba el portal: el prop `color` solo tintaba el ring del
trigger; los rings del portal (area-thumb, slider-thumb, eye-dropper) eran
SIEMPRE primary.
Solucion (espejo de select): el forward apunta a las DOS partes que estampan —
recipe `_palette-border` con `parts: ['trigger', 'content']` (fusionado en la
recipe existente). El trigger consume el token directo en su ring; el content
re-deriva su `--_color-picker-accent` del token EN SU scope (sus descendientes
lo heredan como antes). Root: fuera la decl base + la cascada de 7 roles; el
provider solo estampa el marcador canonico y el color RAW viaja por contexto
(Trigger/Content/ChannelInput lo resuelven cada uno via resolveComponentColor,
con seed `--color-custom` para valores raw). El marker div del ChannelInput
enruta ademas el forward de color-field, como un ColorField standalone.
Tipos: `ColorPickerColor = ComponentColorProp` (era ColorRole).
Verificado en Chrome con el popover abierto: content portalado estampa su
propio data-color; default primary = probe exacto en trigger y content;
risk/teal en el scope del portal = token compartido; custom → color-mix del
seed; el area-thumb hereda el accent. Contract test: mis cambios no añaden
fallos (los 2 de chat-composer son WIP de otra sesion con recipe fuera del
tree; 2 de proof-of-human pre-existentes).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tres aperturas de bajo peso:
- field-langs: delegante puro (patron E) — pasa `color` al `<Field>` compuesto
(abierto en Fase 3). Solo tipo: `FieldLangsColor = ComponentColorProp`.
- radio-cards: comparte elemento provider con radio-group, cuyo forward ya
enruta la paleta que las reglas de card consumen. Tipo abierto (era
AffirmativeColorRole) + ruta custom en el wrapper (seed `--color-custom`).
- image: migra el mecanismo bespoke pre-helper (`data-placeholder-color` +
seed propio) al forward compartido, paridad con avatar (su gemelo declarado).
Arregla de paso las escalas: `placeholderColor='teal'` caia a la rama custom
y pintaba el NAMED COLOR css `teal` (hue equivocado) y `'iris'` era invalido;
ahora resuelven a la escala canonica. `'neutral'` (default) no estampa y
conserva el fallback neutral-track. Fuera el token de recipe
`placeholder-color-custom` (huerfano tras la migracion).
Verificado en Chrome (probe): radio-cards risk/mint/custom en el forward y el
item checked consume borde+fondo (el gris inicial era la transition congelada
del pane suspendido, caso conocido, verificado con transition:none); image
default=neutral-track exacto, risk/teal=escala, custom=color-mix; field-langs
estampa via Field. check: 0 errores en tocados; total 77 = 76 baseline + 1 de
web/routes/uix/demos/chat (WIP de otra sesion). Contract test 26/28.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
El fix anterior (`value={customColor || '#000000'}`) NO servía: el warning
"specified value '' does not conform to #rrggbb" NO viene del valor, sino de que
Svelte pone `""` en el input MOMENTÁNEAMENTE durante la HIDRATACIÓN cuando hay un
`value=`/`bind:value` reactivo (el navegador lo rechaza y resetea el input a
#000000). Diagnóstico ya documentado en `web/routes/temas/color/+page.svelte`.
Fix correcto: action `colorValue` (nueva en `lib/harness.svelte.ts`) que setea el
valor imperativamente (`node.value = hex || '#000000'`), sin atributo `value`
gestionado por Svelte → no hay `""` en hidratación. Los 21 demos con picker custom
pasan de `value={...}` a `use:colorValue={customColor}`.
Verificado tras reiniciar el dev server (el HMR de Vite servía código viejo): el
input conserva #8e4ec6, sin reset a #000000, sin warning. check: 76 (0 en tocados).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dos aperturas por partes (`parts:` en recipe, forma `declarations:`):
- metrics-icon: los 5 slots del featured icon (track/solid/text/contrast/
border) pasan de 8 bloques per-rol al forward compartido en la parte `icon`.
Fuera el guard roles-only del wrapper (caia a neutral con cualquier valor
no-rol); `plain` sigue sin estampar. Default primary estampado = identico.
El flash de valor NO se toca: gradua por `data-event-intent` (sema), no por
el prop `color`. `MetricsChartProps.color` queda cerrado: delega en la
familia chart, que NO esta en la cola del handoff (hueco señalado aparte).
- form submit/reset: 4 slots (solid/solid-hover/border/contrast) en las DOS
partes via el mismo forward. `'neutral'` (default de reset) NO estampa: la
base action look (element bg + text ink) ES el neutral — estamparlo habria
cambiado reset a solid grey. Submit default primary = identico.
Verificado en Chrome (probe getComputedStyle): metrics-icon risk 5/5 slots +
crimson + custom (contrast blanco derivado); form threat 4/4 + iris + custom;
reset sin estampa con look base intacto. check: 0 errores en tocados (total
76 = baseline); contract test 26/28 (2 pre-existentes proof-of-human).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Renombra el motor headless soma `words` a `palabras` y FUSIONA sus dos contratos morfo
(motor `words` + chrome `palabras`) en uno solo `palabras`: carpeta soma/components/
words→palabras, ~2500 refs (`Words*`→`Palabras*`, `data-words-*`→`data-palabras-*`,
`WORDS_`→`PALABRAS_`, `$soma/components/words`→palabras), morfo unificado (12 eventos +
partes de chrome, scope soma/sema/eidos), vars CSS `--_?words-`→`--palabras-`. Barrel
soma actualizado.
Además dos cambios de comportamiento:
- Panel Documento: quitada la sección Paleta (es del esquema, no editable por el usuario;
los colores se aplican desde los controles de color, no como ajuste de documento).
- Shift+Enter: nuevo nodo inline atómico `PalabrasBreak` que renderiza <br> (antes metía
un \n crudo que colapsaba, invisible). Costuras: render/split (fix crash)/offsets
(ancho 1)/validate/normalize (break flanqueado por texto)/serialize (html·md·txt)/parse
(<br>↔break + guarda anti-<p><br></p> de office). Code/table siguen con \n literal. El
slash-menu ya no traga Shift+Enter.
Verificado: motor 490 tests verdes (+ nuevos de break) · check 0 errores en palabras ·
repro Playwright real (salto en medio Y al final pintan 2 líneas, Backspace limpio, DOM
100% data-palabras-*, 0 data-words-*, sin errores de consola) · eidos-lint invalid:0.
Nota: el merge de langs y las exenciones de auditoria (ACTIVE_DEV_TRACK/WIP_TRACKS) quedan
sin commitear por enredo con el trabajo de chat en curso en los barrels.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`0886bfe53` aplico el fallback `|| '#000000'` uniforme a los 21 demos, pero
card era el unico con `bind:value` (two-way) y `bind:` no admite expresiones →
error de compilacion de Svelte al visitar /uix/components/card. Se pasa al
patron one-way de los otros 20 (`value={...}` + `oninput`), conservando el
boton "apply" propio de card. Verificado: la pagina compila y renderiza.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tres standalones al forward de paleta compartido (`resolveComponentColor` +
recipe `_palette-*`):
- skeleton: tinta el wash (slot track, 50% alpha) + el highlight del shimmer.
Colored-only: el default `'neutral'` NO estampa data-color y conserva su
mezcla base cross-mode (oklab + 16% solid). Delta declarado: affirm/fulfill/
risk/threat/loss ganan highlight tintado en el shimmer (antes solo primary/
secondary lo definian; la regla generica uniformiza la cobertura).
- spinner: patron C puro — la base consume `--_spinner-palette-{text,track}`
(host = primary, behavior-preserving); fuera los 7 bloques per-rol.
`'inherit'` se conserva como union aditiva con su regla literal
(currentColor); no puede viajar por el seed custom (trampa `--x: inherit`).
- textarea: tinta el borde de focus (slot solid). Colored-only: el default
`'primary'` no estampa y conserva `--focus-ring-color` (que NO es
primary-solid: es un mix al 52% — estampar habria cambiado el default).
Verificado en Chrome (probe getComputedStyle): default intacto en los tres;
risk/teal/amber/jade = token compartido exacto; custom hex → color-mix del
seed. check: 0 errores en los componentes tocados; contract test 26/28 (2
pre-existentes de proof-of-human).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
El input de color custom de los demos usaba `value={customColor}` y `customColor`
arranca vacío (''), que NO es un valor válido para `<input type="color">` (exige
`#rrggbb`) → warning en consola: "The specified value "" does not conform to the
required format".
Fix uniforme en los 21 demos con picker custom: `value={customColor || '#000000'}`.
PalettePicker.svelte ya estaba bien (input sin `value`, no controlado). Verificado
en el demo de toggle: el warning desaparece.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reescribe docs/process/open-color-cage-2026-07.md tras los 7 commits de esta
sesión: familias hechas (field/calendar/time/list-grid/lists + link/mark/
button-group/highlight), los 5 PATRONES de migración (A eidos-wrapper · B
soma-routed · C mini-recipe month-grid · D tinta de contenido · E delegante), las
6 TRAMPAS cazadas (forma declarations[] para `parts`, recipe-exists fiable/no
duplicar clave, declarar solo slots consumidos, `--x:inherit`, cross-portal,
prefijo accent/accent-soft), y la COLA de ~11 standalones heterogéneos con su
patrón + trampa cada uno (incl. color-picker/float-panel intentados y revertidos
por matiz cross-portal/default). Operativa corregida: el total de `check` NO es
baseline estable (76↔94 por caché svelte-check) → verificar CERO en tocados;
índice git compartido + exclusiones (chat-track en soma/virtual-list, palabras,
alpha).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ambos delegan el color a un componente ya abierto, así que es solo tipo + wrapper
(sin recipe/css):
- button-group: pasa `color` a los <Button> hijos por contexto (cada Button lo
resuelve, incl. custom). Tipo 'primary'|'secondary'|'neutral' → ComponentColorProp;
el `data-color` del <Group> (presentacional, sin consumidor) pasa por
`resolveComponentColor` para no estampar un hex crudo inválido.
- highlight: envuelve coincidencias en `<mark data-mark>` (ya forward-ready). Tipo
→ ComponentColorProp; wrapper estampa el trío en el shell `[data-highlight]` +
cada `<mark>`, con el seed `--color-custom` en el shell que los marks heredan.
check: 0 errores en componentes tocados (el total oscila 76↔94 por la caché de
svelte-check aflorando errores `any` de demos ajenos).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Migra los dos content-ink al forward de paleta compartido:
- link: `color` tinta el texto. Base pasa a `color: var(--_link-palette-text)`
(host = primary-text); se quita la cascada per-rol de 8 bloques. hover/active
siguen en primary (comportamiento pre-existente). Recipe nuevo `_palette-text`.
- mark (resaltado): `element` tinta el fondo (default = fulfill, el resalte
clásico), `text` tinta la tinta SOLO cuando hay color (la base sigue
`color: inherit`; no se puede poner `inherit` como host de custom-prop — trampa
del `--x: inherit`). Regla añadida `[data-mark][data-color],[…custom]{color:…}`.
Ambos wrappers pasan por `resolveComponentColor` (custom hex → data-color-custom
+ seed). Verificado en Chrome: link risk→texto risk, custom→color-mix; mark
fulfill por defecto, risk→fondo risk, custom→color-mix.
Nota: `check` marca 94 (no 76) por la caché incremental de svelte-check aflorando
errores `any` pre-existentes en demos ajenos (heroscrolling/animations/palabras/
alpha/temas); CERO errores en componentes tocados (link/mark limpios).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Migra los 3 al forward de paleta compartido, cada uno con su matiz:
- listbox: 2 slots (solid = indicador activo, element = fila seleccionada), raíz
bare `[data-listbox]`. El `accent-text` era código muerto (sin consumidor) →
eliminado. Recipe nuevo.
- timeline: 5 slots (solid/track/border/contrast/text) que alimentan
`--_timeline-hue-*` del item; data-intent por item sigue mandando sobre el
acento del root. Tipo ProgressiveColorRole → ComponentColorProp. Recipe
existente ampliado.
- combobox: la cascada vivía en CUATRO partes (control/input/trigger/content),
así que el forward las cubre vía `parts: [...]` (forma declarations[]). 3 slots
(track/border/text). Los 5 wrappers (root + 4 partes) pasan por
`resolveComponentColor` para que el custom hex no se estampe crudo. Recipe
existente (content-z) ampliado.
Verificado en navegador: combobox data-color=risk → palette-border del control =
token risk compartido; custom #00b894 → color-mix. table (lote previo) idem.
check: 76 ajenos, 0 propios. Contract 26/28 (2 = proof-of-human pre-existente).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Migra table · tree-grid · tree-view · virtual-list · virtual-grid · feed ·
drag-drop · grid-list · clipboard · carousel al forward de paleta compartido.
Patrón 2-slots: `--_{c}-accent` (indicador sort/activo) -> `--_{c}-palette-solid`,
`--_{c}-accent-soft` (tinte de fila seleccionada) -> `--_{c}-palette-element`.
Recipe nuevo con host = primary; el generador emite base default + forward. Se
quita la cascada per-rol de cada css; tipo -> ComponentColorProp; wrapper vía
`resolveComponentColor`.
Detalles no uniformes resueltos:
- Los 7 que estampan `data-color` en `[data-{c}-root]` usan `parts: ['root']`
(forma declarations[], la única que el generador lee para parts) -> forward
`[data-{c}-root][data-color]`. Los 3 bare (grid-list/clipboard/carousel) van
al selector base.
- drag-drop ya tenía recipe (preview-z) -> fusionado, sin duplicar clave.
- tree-grid/tree-view solo tintan la fila (element); su `accent` (solid) no
tenía consumidor -> declaran solo `_palette-element`.
- virtual-list/virtual-grid tenían la cascada accent MUERTA (sin consumidor):
contenedores virtualizados sin visual por color. Tipo abierto (guard) +
wrapper inerte, pero SIN recipe (evita token de paleta huérfano).
Verificado en navegador (demo table): data-color=risk -> palette-solid/element
= tokens risk compartidos exactos; custom #00b894 -> solid=#00b894, element=
color-mix(...). check: 76 ajenos, 0 propios. Contract 26/28 (2 = proof-of-human).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mismo patrón mini-recipe que month-grid: recipe con 5 tokens `_palette-*`
(host = primary) -> el generador emite base default + forward
`[data-{c}][data-color], [data-{c}][data-color-custom]`; se quita la cascada
per-rol de la css y se renombran los consumidores accent -> palette; tipo ->
ComponentColorProp; wrapper vía `resolveComponentColor`.
- year-grid: espejo exacto de month-grid (5 slots, sin cross-refs).
- range-calendar: el END endpoint + `--calendar-range-end-*` bind a
`--_range-calendar-palette-*`; el swap de START a secondary para affirm/fulfill
(distinción de extremos) se conserva como regla per-rol (no es paleta).
Verificado en navegador (demo range-calendar): data-color=risk -> palette-solid
= range-end-solid = token risk compartido; affirm -> start = secondary; custom
#00b894 -> range-end = #00b894. check: 76 ajenos, 0 propios. Contract 26/28 (2 =
proof-of-human pre-existente).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
palabras: borrar el recolor muerto de la Paleta (ya es solo display)
Tras hacer la Paleta un grid de solo display (`ca883e819`), el código de recolor quedó
sin uso: `writePaletteColor` / `resetPalette` / `paletteModified` + la fila del ↺ reset +
el `onPick`/`presets` del swatch. Borrados. `onPick` de `PalabrasColorRow` pasa a OPCIONAL
(el modo swatchOnly no recolorea). Conservados `paletteValueOf` (lo lee el swatch),
`commitDocScheme`/`baseScheme` (los usan Medida/Márgenes/Texto).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
Al ensanchar `*PickerColor` a `ComponentColorProp`, los sub-wrappers que
enhebraban `data-color={color|resolvedColor|visual.getColor()}` CRUDO podían
recibir un valor custom (hex) y estamparlo tal cual en `data-color` (inválido;
roles/escalas ya pintaban por la capa compartida, solo el custom hex no).
Se pasan por el helper `resolveComponentColor` (patrón de `select-trigger.svelte`):
canonical -> `data-color`; valor crudo -> `data-color-custom` + el seed
`--color-custom` que la capa compartida rampa. 12 sub-partes:
- date-picker: root, trigger, calendar
- date-range-picker: root, trigger, calendar, input
- time-picker: trigger, clock
- time-range-picker: trigger, clock
- time-range-field: input
Verificado en navegador (demo date-range-field, custom #00b894): ambos endpoints
quedan data-color=null, data-color-custom='' y --_field-palette-text =
color-mix(...#00b894...) exacto. El soma range-field hace `{...restProps}`
wholesale sobre el [data-field] de cada endpoint, así que data-color-custom + el
seed llegan solos. check: 76 errores ajenos, 0 propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix del feedback: al hacer la Paleta un grid (commit anterior) mantuve el ColorPicker en
cada swatch, así que clicar un color ABRÍA el picker — el usuario NO quería eso («grid de
colores con tooltip» = una LEYENDA de los colores del documento, para verlos, no un
editor). Y lo peor: hice el grid sin verificar la interacción. `PalabrasColorRow`
`swatchOnly` pasa a renderizar un `<span>` de display (color de fondo + nombre en `title`
tooltip), SIN ColorPicker ni click.
⚠️ Esto QUITA el recolor por-swatch que la sección Paleta tenía (era el editor de la
paleta del documento). DECLARADO, no silencioso: si se quiere poder recolorear la paleta,
se añade aparte con una affordance clara (p. ej. un botón «editar»).
Verificado Chrome real: 9 swatches de color con tooltips en 2 filas; clicar uno NO abre
ningún ColorPicker (pickerOpen:false, anyPortalArea:false). check sin errores en palabras.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Continúa la apertura de la jaula del color hacia el sistema completo
(role · intent · 33 escalas · valor custom), tipando a `ComponentColorProp`
y enrutando por el forward `--palette-*` compartido:
- Field family: field base gana el forward `_palette-text`; search-field
y 8 hojas (password/mask/number/css/color/date/date-range/time-field)
adoptan `data-field` y heredan el forward. Wrappers vía
`resolveComponentColor`; demos con picker de roles/intents/escalas/custom.
- Calendar base: recipe `_palette-{solid,solid-hover,track,border,text}`;
calendar.css migra su cascada per-rol al forward compartido.
- month-grid: patrón mini-recipe (5 tokens `_palette-*`, sin recipe previo);
el generador emite base default + forward `[data-month-grid][data-color]`.
- time-picker / time-range-picker: tipo + wrapper.
Fix de regresión de runtime: la migración de la field-family dejó huérfana
la cascada `--_time-field-accent-*`, y time-range-picker re-exportaba el
Input crudo de soma, así que sus endpoints no recibían el color del picker.
Nuevo `time-range-picker-input.svelte` enhebra `data-color`/`data-color-custom`
a cada endpoint (verificado en navegador: color del picker -> ambos extremos).
time-picker (single) ya se salvaba por el overlay `data-field` en su raíz.
Fix de 9 errores de tipo: los factories de opts de los tests de provider
de switch/toggle no pasaban `colorCustom` (lo hicimos required en Fase 2b).
Regenera generated/base.css. check: 76 errores ajenos, 0 propios.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dos ajustes pedidos por el usuario:
- **Paleta (panel Documento)**: era una LISTA (una fila-con-label por color) → alta y con
scroll. Ahora un GRID compacto de swatches — `PalabrasColorRow` gana modo `swatchOnly`
(solo el swatch, el nombre del color como tooltip nativo `title`); cada swatch sigue
abriendo el picker completo. Grid `auto-fill minmax(2.25rem)` (9 colores → 2 filas,
~80px, sin scroll).
- **Bubble, tipografía de rango**: el campo de Tamaño mostraba el placeholder (vacío sin
override) y los campos iban apretados. Ahora muestra el tamaño EFECTIVO (el mark del
rango, o si no el default del bloque vía `schemeSizeFor` — «los píxeles actuales», p. ej.
32 en un H1) y los campos son más anchos (Tamaño 7rem, Familia 12rem).
Verificado Chrome real: Paleta = 9 swatches en 2 filas con tooltips (sin scroll); el bubble
muestra «32» al seleccionar en el H1. check sin errores nuevos en palabras.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Primer trozo de touch/móvil (FD.1: «drawer inferior de ancho completo» en pantalla
estrecha). En `palabras-panel`, cuando `mode==='floating'` y el viewport es estrecho
(`!dom.isAtLeast('md')`, < 768px, REACTIVO vía el breakpoint tracker de ActiveDom), el
panel deja de renderizar el FloatPanel (una ventana flotante inservible en un teléfono) y
usa un Drawer inferior: `variant="overlay"` + `Drawer.Overlay` (backdrop frosted) +
`direction="bottom"` + `open` controlado + `dismissible`. El SHELL usa las partes de Drawer
en ese caso (como docked). CSS `[data-palabras-mobile]`: edge-to-edge (inset 0, ancho
completo), a ras del fondo, esquinas redondeadas arriba, cap 85vh.
Verificado Chrome real: a 390px → sheet edge-to-edge a ras (x:0 w:390 bottomAnchored) con
backdrop visible; **tap-fuera CIERRA**; a 1400px SIN regresión (sigue el FloatPanel anclado
a la derecha del bloque). check sin errores nuevos en palabras. Pendiente de F4.10 (necesita
DISPOSITIVO real para verificar): gestos táctiles (long-press del grip, arrastre de reorden,
drag-down para cerrar el sheet) + targets ≥44px.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Acceso al panel de propiedades SIN ratón (INTERACCION §4.5, atajo ratificado). En
`palabras-activate` el keydown (que ya lleva la escalera Esc) gana Ctrl/⌘+.: resuelve el
bloque del CARET desde el DOM (activeBlockId puede sentarse a nivel documento), lo activa
y pide abrir el panel vía `onOpenPanel` (el shell lo abre en modo flotante; acoplado ya
está visible, menubar usa la barra).
Verificado Chrome real (recorrido sin ratón): caret en un párrafo → Ctrl+. → el panel abre
anclado a ESE bloque («T Párrafo», migas Documento › Párrafo) Y el foco ENTRA en el panel
(focus-trap del FloatPanel, del framework); Esc → el panel cierra Y el foco VUELVE al
editor (focus-return). check sin errores nuevos en palabras. Pendiente de F3.6: announce
(aria-live) de acciones de bloque (mover/eliminar/insertar).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completa el bubble (INTERACCION.md §4.3, «tipografía de rango tras un overflow»). Botón
«Aa» → conmuta a una fila de tipografía (como el editor de enlace) con Subíndice /
Superíndice + Tamaño (NumberField px) + Familia (Select de las fuentes del esquema). Se
aplican como MARCAS del motor sobre la selección vía `applyCommand({type:'toggleMark',
mark})`: booleanas `sub`/`sup`, paramétricas `{type:'fontSize',value}` /
`{type:'fontFamily',value}` (mismo valor apaga, valor nuevo reemplaza). El Select
portalizado lleva `data-words-external-tool` (patrón de PalabrasTypography) → la selección
sobrevive al dropdown. `scheme` cableado al bubble para las fuentes.
Verificado Chrome real: «Aa» → fila con los 4 controles; sub → `data-words-marks="sub"`;
tamaño 24 → `style="font-size:24px"` (apilado con sub); familia → `font-family` (3 fuentes
en el dropdown); **selección CONSERVADA** tras la Familia. check sin errores nuevos en palabras.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cierra F3.3: el motor serializa a los 3 formatos (`exportContent`) pero faltaba la
affordance. Pie de acciones «Exportar» al fondo del panel de Documento (decisión del
usuario 2026-07-18: es una ACCIÓN de documento, no una propiedad; presente en los 3
modos): JSON · HTML · Markdown, cada uno descarga `documento.{fmt}`. No hay callback
`onSave`, así que la affordance descarga directo (globales `URL`/`Blob` + anchor en el
`ActiveDom.document`, iframe-safe; `md`→`markdown`).
Verificado Chrome real: pie con los 3 botones bajo las secciones del panel; JSON →
documento.json (JSON válido), HTML → documento.html (<h1>…<p>…), Markdown → documento.md
(# … + prosa). check sin errores nuevos en palabras.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Motor (Fase 0): tipo abierto `ComponentColorProp`, helper `resolveComponentColor`,
derivación custom compartida `[data-color-custom]` (color-mix desde `--color-custom`)
+ forward extendido a `[data-{c}][data-color-custom]`; guard recipe-css-contract
actualizado.
Eidos-wrapper (14): radio-group, checkbox, stepper, toggle-group, select, badge,
editable, file-upload, tag-group, tags-input, surface, avatar, card. Patrón:
tipo -> ComponentColorProp, wrapper -> resolveComponentColor (estampa
data-color/data-color-custom/style), demo con picker + input custom. Incluye el
fix del bug de escalas de card (una escala caía al path custom y pintaba el CSS
named color en vez de la escala de paleta).
Soma-routed (button/switch/toggle): patrón morfo+soma limpio — el provider omite
data-color y emite data-color-custom para el valor crudo, con el intent evaluativo
ganando y suprimiendo el custom. Ensancha los tipos de color de la capa soma.
Arreglados 2 bugs del runtime del framework:
- html-presence con `v.literal('')` (valor vacío = falsy) nunca emitía el attr ->
`v.propRef('colorCustom')` (como data-disabled).
- el custom pisaba al intent evaluativo -> el prop `colorCustom` se anula con
intent no-neutral.
Bundle: incluye el rail segmentado de radio-group (variant=segmented, indicador
deslizante compartido soma-mide/eidos-pinta: measured-indicator + sliding-indicator)
de la misma sesión, intercalado en los ficheros de radio-group.
Handoff para continuar: docs/process/open-color-cage-2026-07.md (Fase 3/4/5 pendientes).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bitácora de la sesión en continuar-palabras.md: las 3 superficies de edición en línea
(bubble/slash/find — commits 99a7e1c23 / 43c1e1c6a / 8c2a4d511), el pulido del menubar
(inspector ancho-adaptativo sin scroll, sliders de Ajustes, Deshacer/Rehacer, panel
Documento = solo propiedades), el HALLAZGO de que el motor ya exporta los contratos
(BubbleMenu/SlashMenu/FindReplace/CommandButton) → eran cablear, y dónde retomar
(saveas · bubble F2.4 · menciones @ · a11y). Marca la sección v4 menú-nativo como
histórica (revertida).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tercer hueco P0 de edición en línea. El motor traía toda la maquinaria de búsqueda
(`Words.FindReplace` snippet: findQuery/findMatches/currentMatchIndex + case/word/regex/
selection + next/prev/replace/replaceAll) pero SIN chrome desde F1. `palabras-find.svelte`
lo compone: panel fijo arriba-derecha con fila de búsqueda (input + contador n/total + ↑↓
+ toggles Aa/palabra/regex + ✕) y fila de reemplazo (input + Reemplazar/Todo), con
primitivos del framework. Apertura + posición son del chrome (el motor no las da):
Ctrl/⌘F abre cuando el editor tiene foco, Esc/✕ cierra; el resaltado de coincidencias en
el documento es del motor (decoraciones de F1). Cableado en TODOS los modos.
Verificado Chrome real: Ctrl+F → panel arriba-derecha con input enfocado; «de» → 7
coincidencias resaltadas en amarillo + contador; Enter → 1/7 (siguiente); Reemplazar todo
«downtime»→«PARADA» (verificado en el texto). check sin errores nuevos en palabras.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Segundo hueco P0 de edición en línea (INTERACCION.md §4.2). El editor solo insertaba
por grip / menú Insertar — sin «/». `palabras-slash.svelte` compone el CONTENIDO del
snippet de `Words.SlashMenu` (el motor detecta «/query», filtra los items, maneja el
teclado y posiciona bajo el caret): lista de 14 tipos con icono + label + descripción,
item activo resaltado; clic/hover vía `commit`/`setActiveIndex`. Los labels del motor
(inglés) se re-etiquetan a castellano vía `ts()` (fallbacks; bundle EN = F3.5-B),
cayendo al label del motor para ids no mapeados (plugins). Icono por tipo normalizado
del id (heading-* → heading, *-list → list, code-block → code). Cableado en TODOS los modos.
Verificado Chrome real: «/» → menú bajo el caret con 14 items en castellano; ArrowDown
mueve el activo (teclado del motor); Enter inserta (4→5 bloques, turn-into a heading);
clic inserta; se cierra al confirmar. check sin errores nuevos en palabras.
Nota: el posicionador soma no hace flip hacia arriba — con el caret muy al fondo el menú
(cap 20rem, con scroll interno) roza el borde; refinamiento pendiente.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El editor no tenía superficie de formato EN LÍNEA sobre la selección (murió con el
chrome words y no se recableó) — el mayor hueco P0. INTERACCION.md §4.3 lo especifica.
`palabras-bubble.svelte` compone el CONTENIDO del snippet de `Words.BubbleMenu` (el
motor ya resuelve posición sobre la selección estable + el invariante «no robar el
rango»): marcas B/I/U/S/código vía `Words.CommandButton` (restaura el rango, refleja
`data-active`) + enlace inline (Enter aplica `insertLink`, Escape cancela). Cableado en
TODOS los modos. Turn-into («/») y color (panel) NO van aquí (§4.4); tipografía de rango
(F2.4) + «→propiedades» detrás de overflow = siguiente.
Verificado en Chrome real: seleccionar → bubble sobre la línea (side=top, on-screen) con
5 marcas + enlace; Negrita aplica la marca (`data-words-marks=bold`) y activa el botón;
enlace → input enfocado → Enter envuelve la selección en `<a href>`. Selección conservada.
check sin errores nuevos en palabras. Refs langs con fallback castellano (bundle EN = F3.5-B).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El Índice (lista de encabezados) + el recuento estaban plegados dentro del panel
de PROPIEDADES de Documento (venían del viejo menú «Ver»). Un índice es navegación
del documento, no una propiedad de página — no cuadra ahí (decisión del usuario:
quitar ambos). El panel queda estricto: Documento · Texto · Paleta. Borrado el
bloque doc-nav, las funciones huérfanas (idPathOf / jumpToHeading) y su CSS
(doc-nav / doc-nav-title / toc-item / toc-level / doc-count).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
En menubar la entrada «Documento» de la barra ya nombra el panel y el acordeón lo
repite como su primera sección, así que la cabecera propia del panel (título
«Documento» + chip de esquema) era chrome redundante heredado de la ventana
flotante. Se elimina SOLO en menubar (guard mode !== 'menubar'): el panel abre
directo en las secciones, como los inspectores de elemento. Flotante/acoplado
conservan la cabecera completa (título + chip del esquema que gobierna).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El menú Editar no ofrecía historial. Añadidos Deshacer (api.undo/canUndo) y
Rehacer (api.redo/canRedo) arriba del menú, con separador. Como son acciones
GLOBALES del documento (no necesitan selección), el trigger «Editar» pasa a estar
SIEMPRE habilitado, y son los items element-específicos (mover/copiar/pegar/
eliminar) los que se deshabilitan sin elemento activo. Verificado en Chrome real:
insertar (4->5 bloques) -> Deshacer revierte (->4) -> Rehacer se habilita.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El auto-fit empaquetaba los 8 sliders en 4 columnas de 120px: las pistas
solapaban la etiqueta vecina y «Escala de grises» partía en dos líneas. Paso a
2 columnas fijas de ~256px (el ancho legible de un slider), y el «Restablecer»
compacto a la derecha en vez de una barra a todo el ancho abajo. Sin scroll
(450px, cabe a 800 y 900).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- --pcols (1/2/3) según la carga de la sección: escasa 17rem/1-col, media
26rem/2-col, densa 35rem/3-col. La superficie hace fit-content sobre ese ancho
explícito (sin sizing circular) y es el único límite de scroll (último recurso).
- Mega-controles (Ajustes 8 sliders, Tipografía) se esparcen con auto-fit:
Ajustes 534->341px, Tipografía 352->289px. Cero scroll en 5 secciones x 10 tipos.
- Quita el «+ Insert block» redundante del panel de Documento en menubar
(el menú Insertar de la barra ya lo cubre).
- Imagen: fold por colisión de nombre + su propia sección Ajustes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>