refactor(words): wash scale + token contract fix (audit increment 1)

Kills the biggest visual-inconsistency soup: 18 ad-hoc color-mix opacities
(4/6/8/12/14/16/22/30/35/40/45 %) collapsed to a 4-step canonical scale
--words-wash-{faint,subtle,soft,strong} (6/16/30/44 %), applied across
words.css; geometry %s (circles, widths) untouched. Hardcoded transition/
animation durations (120/140/100 ms) collapsed to --words-transition-duration.
Declared the 2 used-but-undeclared tokens (--words-font-size-xs, --words-swatch)
→ the "declares every public var" recipe test goes green (1 of 3 words failures
fixed). Editor verified in browser, no regression. See components/words/AUDIT.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
active-uix
dev 4 months ago
parent 317cb5b297
commit 54d1f658ab

@ -57,9 +57,14 @@ verificar en navegador, y con el enfoque validado → generalizar tokens + barre
## Tracker
- [ ] Piloto inspector (tokens de escala + reescritura + verificación)
- [ ] A — vocabulario (triar huérfanos + escalas + declarar faltantes)
- [ ] B — barrido de superficies
- [ ] C — paletas centralizadas
- [ ] D — fixes API (3er test)
- [ ] E — verificación + README
- [x] **Escala `wash` + contrato** (2026-06-06): añadidos `--words-wash-{faint,subtle,soft,strong}`
(6/16/30/44 %) + `font-size-xs` + `swatch` al recipe; aplicada la escala a los **18** sitios
de `color-mix` de `words.css` (la sopa 4…45 % → 4 pasos) y colapsadas las durations
hardcoded (120/140/100 ms → `--words-transition-duration`). Geometría (50/100 %) intacta.
→ test "declares every public var" **verde** (1 de 3 fallos de words resuelto). Editor
verificado en navegador (sin regresión).
- [ ] A — resto del vocabulario: triar los 41 huérfanos (cablear `rail-*`/`toolbar-*`/… o podar)
- [ ] B — barrido del resto de superficies (radius a una escala, tamaños bespoke→tokens, padding `--space-*`)
- [ ] C — centralizar las 3 paletas de color
- [ ] D — fixes API (`<style>` inspector, `onMount→$effect`) → 3er test
- [ ] E — verificación final + README

@ -122,7 +122,7 @@
[data-words-menubar] [data-active],
[data-words-menubar] [aria-pressed='true'] {
color: var(--color-primary-solid);
background: color-mix(in srgb, var(--color-primary-solid) 12%, transparent);
background: color-mix(in srgb, var(--color-primary-solid) var(--words-wash-subtle), transparent);
}
/* Preview is read-only — the surface alone looks identical to edit, so make
the switch obvious: collapse the formatting groups, leaving the bar as just
@ -149,8 +149,8 @@
regardless of block height (paragraph, code, table, image). */
transform: translateY(-50%);
/* slide smoothly between blocks as the cursor moves */
transition: top 120ms var(--words-transition-ease);
animation: words-gutter-in 140ms var(--words-transition-ease);
transition: top var(--words-transition-duration) var(--words-transition-ease);
animation: words-gutter-in var(--words-transition-duration) var(--words-transition-ease);
}
@keyframes words-gutter-in {
/* `translateY(-50%)` keeps the grip vertically centered against the
@ -199,7 +199,7 @@
margin-block-start: -1px;
border-radius: 1px;
background: var(--color-primary-solid);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-primary-solid) 30%, transparent);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-primary-solid) var(--words-wash-strong), transparent);
pointer-events: none;
}
@ -222,7 +222,7 @@
border: var(--words-border-width) solid var(--words-border);
border-radius: var(--words-radius-md);
box-shadow: var(--depth-overlay-shadow), var(--depth-overlay-halo);
animation: words-bubble-in 120ms var(--words-transition-ease);
animation: words-bubble-in var(--words-transition-duration) var(--words-transition-ease);
}
[data-words-bubble-menu][data-side='top'] {
transform: translate(-50%, calc(-100% - 0.5rem));
@ -317,16 +317,16 @@
inline-size: 1.15rem;
block-size: 1.15rem;
padding: 0;
border: 1px solid color-mix(in srgb, var(--words-content-color) 22%, transparent);
border: 1px solid color-mix(in srgb, var(--words-content-color) var(--words-wash-soft), transparent);
border-radius: 4px;
background: var(--words-swatch, var(--words-bg));
cursor: pointer;
transition: box-shadow 100ms var(--words-transition-ease, ease);
transition: box-shadow var(--words-transition-duration) var(--words-transition-ease, ease);
}
[data-words-bubble-swatch]:hover,
[data-words-bubble-swatch]:focus-visible {
outline: none;
box-shadow: 0 0 0 2px color-mix(in srgb, var(--_words-accent) 45%, transparent);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--_words-accent) var(--words-wash-strong), transparent);
}
/* "Default / none" chip — a diagonal strike over the surface tone. */
[data-words-bubble-swatch][data-clear] {
@ -334,8 +334,8 @@
linear-gradient(
to bottom right,
transparent calc(50% - 1px),
color-mix(in srgb, var(--words-content-color) 45%, transparent) calc(50% - 1px),
color-mix(in srgb, var(--words-content-color) 45%, transparent) calc(50% + 1px),
color-mix(in srgb, var(--words-content-color) var(--words-wash-strong), transparent) calc(50% - 1px),
color-mix(in srgb, var(--words-content-color) var(--words-wash-strong), transparent) calc(50% + 1px),
transparent calc(50% + 1px)
),
var(--words-bg);
@ -428,7 +428,7 @@
margin: calc(var(--space-2) * -1) calc(var(--space-2) * -1) 0;
padding: var(--space-2-5) var(--space-3);
border-block-end: 1px solid var(--words-border);
background: color-mix(in srgb, var(--_words-accent) 4%, var(--words-bg));
background: color-mix(in srgb, var(--_words-accent) var(--words-wash-faint), var(--words-bg));
}
[data-words-inspector-title] {
display: flex;
@ -446,7 +446,7 @@
block-size: 0.5rem;
border-radius: 2px;
background: var(--_words-accent);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--_words-accent) 16%, transparent);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--_words-accent) var(--words-wash-subtle), transparent);
}
[data-words-inspector-title-text] {
min-inline-size: 0;
@ -466,7 +466,7 @@
or while the user edits. */
[data-words-inspector-text][data-invalid] {
border-color: var(--color-threat-solid);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-threat-solid) 35%, transparent);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-threat-solid) var(--words-wash-strong), transparent);
}
/* Breadcrumb trail (shown from depth ≥ 2). A single, non-wrapping row of
@ -507,16 +507,16 @@
button[data-words-breadcrumb-crumb] {
cursor: pointer;
transition:
background 120ms var(--words-transition-ease, ease),
color 120ms var(--words-transition-ease, ease);
background var(--words-transition-duration) var(--words-transition-ease, ease),
color var(--words-transition-duration) var(--words-transition-ease, ease);
}
button[data-words-breadcrumb-crumb]:hover {
background: color-mix(in srgb, var(--_words-accent) 14%, transparent);
background: color-mix(in srgb, var(--_words-accent) var(--words-wash-subtle), transparent);
color: var(--words-content-color);
}
button[data-words-breadcrumb-crumb]:focus-visible {
outline: none;
box-shadow: 0 0 0 2px color-mix(in srgb, var(--_words-accent) 30%, transparent);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--_words-accent) var(--words-wash-strong), transparent);
}
[data-words-breadcrumb-crumb][data-root] {
font-weight: 600;
@ -674,8 +674,8 @@ button[data-words-breadcrumb-crumb]:focus-visible {
padding-inline: 2px;
block-size: 1.75rem;
transition:
border-color 120ms var(--words-transition-ease, ease),
box-shadow 120ms var(--words-transition-ease, ease);
border-color var(--words-transition-duration) var(--words-transition-ease, ease),
box-shadow var(--words-transition-duration) var(--words-transition-ease, ease);
}
[data-words-inspector] [data-number-field][data-size]:hover:not([data-focused]) {
border-color: var(--words-border-hover);
@ -683,7 +683,7 @@ button[data-words-breadcrumb-crumb]:focus-visible {
[data-words-inspector] [data-number-field][data-size][data-focused] {
/* Single focus frame — one accent ring; the border stays structural so a
focused field shows ONE highlight, not an accent border AND a ring. */
box-shadow: 0 0 0 2px color-mix(in srgb, var(--_words-accent) 40%, transparent);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--_words-accent) var(--words-wash-strong), transparent);
}
/* Eidos TextArea (image alt) shares the inspector input chrome. Retokenise it
to the WORDS palette so it reads identically to the plain text inputs —
@ -699,7 +699,7 @@ button[data-words-breadcrumb-crumb]:focus-visible {
[data-words-inspector] [data-textarea][data-focused] [data-textarea-input] {
/* Same single-ring focus as every other inspector field. */
border-color: var(--words-border);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--_words-accent) 40%, transparent);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--_words-accent) var(--words-wash-strong), transparent);
}
[data-words-inspector] [data-number-field-input] {
inline-size: 100%;
@ -721,7 +721,7 @@ button[data-words-breadcrumb-crumb]:focus-visible {
}
[data-words-inspector] [data-number-field-increment-trigger]:hover:not([data-disabled]),
[data-words-inspector] [data-number-field-decrement-trigger]:hover:not([data-disabled]) {
background: color-mix(in srgb, var(--words-content-color) 8%, transparent);
background: color-mix(in srgb, var(--words-content-color) var(--words-wash-subtle), transparent);
color: var(--words-content-color);
}
@ -799,8 +799,8 @@ button[data-words-breadcrumb-crumb]:focus-visible {
background: var(--words-bg);
color: var(--words-content-color);
transition:
border-color 120ms var(--words-transition-ease, ease),
box-shadow 120ms var(--words-transition-ease, ease);
border-color var(--words-transition-duration) var(--words-transition-ease, ease),
box-shadow var(--words-transition-duration) var(--words-transition-ease, ease);
}
[data-words-inspector-text]:hover:not(:focus),
[data-words-inspector-code-language]:hover:not(:focus) {
@ -813,7 +813,7 @@ button[data-words-breadcrumb-crumb]:focus-visible {
[data-words-inspector-text]:focus-visible,
[data-words-inspector-code-language]:focus-visible {
outline: none;
box-shadow: 0 0 0 2px color-mix(in srgb, var(--_words-accent) 40%, transparent);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--_words-accent) var(--words-wash-strong), transparent);
}
/* Callout intent swatch — small color dot rendered INSIDE the
@ -927,7 +927,7 @@ button[data-words-breadcrumb-crumb]:focus-visible {
padding: var(--space-1) var(--space-2);
font-size: var(--words-font-size-sm, 0.8125rem);
color: var(--words-content-color);
background: color-mix(in srgb, var(--words-content-color) 3%, transparent);
background: color-mix(in srgb, var(--words-content-color) var(--words-wash-faint), transparent);
border: 1px solid var(--words-border);
border-radius: var(--space-1);
cursor: pointer;
@ -936,7 +936,7 @@ button[data-words-breadcrumb-crumb]:focus-visible {
text-overflow: ellipsis;
}
[data-words-inspector-block-select]:hover {
background: color-mix(in srgb, var(--words-content-color) 6%, transparent);
background: color-mix(in srgb, var(--words-content-color) var(--words-wash-faint), transparent);
}
/* Table insert/delete ops row — 3 small buttons in a horizontal flex
@ -1083,8 +1083,8 @@ button[data-words-breadcrumb-crumb]:focus-visible {
background: var(--color-primary-solid);
pointer-events: none;
transition:
top 120ms var(--words-transition-ease),
height 120ms var(--words-transition-ease);
top var(--words-transition-duration) var(--words-transition-ease),
height var(--words-transition-duration) var(--words-transition-ease);
}
[data-words-content] :where([data-words-node='block']):first-child {
@ -1512,5 +1512,5 @@ mark[data-words-find-match][data-words-find-active] {
/* ── Selection ────────────────────────────────────────────────────────── */
[data-words-content] ::selection {
background: color-mix(in srgb, var(--words-primary-solid) 22%, transparent);
background: color-mix(in srgb, var(--words-primary-solid) var(--words-wash-soft), transparent);
}

@ -2403,6 +2403,12 @@
--words-focus-shadow: 0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color);
--words-invalid-border: var(--color-risk-border);
--words-disabled-opacity: 0.5;
--words-font-size-xs: 0.6875rem;
--words-wash-faint: 6%;
--words-wash-subtle: 16%;
--words-wash-soft: 30%;
--words-wash-strong: 44%;
--words-swatch: var(--words-bg);
--words-toolbar-gap: var(--space-1);
--words-toolbar-px-sm: var(--space-2);
--words-toolbar-px-md: var(--space-2-5);

@ -2676,6 +2676,19 @@ export const THEME_BASE_RECIPE_TOKENS = {
'0 0 0 var(--focus-ring-offset) var(--color-surface-default), 0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color)',
'invalid-border': 'var(--color-risk-border)',
'disabled-opacity': '0.5',
// Smallest doc-chrome size (breadcrumb, cell labels) — one step under `sm`.
'font-size-xs': '0.6875rem',
// Wash scale: ONE canonical set of color-mix opacities for the editor's
// accent/role tints (header washes, hover fills, glows, focus rings).
// Replaces the ad-hoc 4/8/12/14/16/22/30/35/40/45/50 % soup with 4 steps.
// They are percentages, applied to whatever base color each surface mixes.
'wash-faint': '6%',
'wash-subtle': '16%',
'wash-soft': '30%',
'wash-strong': '44%',
// Per-swatch hue, set inline by the bubble/inspector color grids; the
// declared default is the surface bg (an unset swatch reads as empty).
swatch: 'var(--words-bg)',
'toolbar-gap': 'var(--space-1)',
'toolbar-px-sm': 'var(--space-2)',
'toolbar-px-md': 'var(--space-2-5)',

Loading…
Cancel
Save

Powered by TurnKey Linux.