uix(clipboard): temable — 0 % → 63 %, todo en la píldora de «Copied!»

6 claves fusionadas en el bloque `clipboard` de `recipes/base.ts` (donde ya
vivían los dos forwards de paleta). Censo 0 % → 63 %.

**Todo lo tokenizable es el INDICADOR.** El host va `display: contents` y el
trigger reusa la receta de `Button` por el patrón de consumidor, así que la
receta sólo posee la píldora que aparece un instante tras copiar.

- Los dos forwards de paleta siguen privados (THM-2): el fondo y la tinta del
  chip los leen a pelo, sin sitio donde meter un público sin duplicar la regla
  — son el 37 % que no alcanza.
- `font-family` queda fuera por la misma razón que en code-block, kbd y
  announce: lo posee la capa tipográfica.
- El padding es un shorthand de dos valores → dos knobs lógicos.
- **Los `--motion-*` no son knobs de tema**: son los ganchos con que el
  indicador afina el preset `scale-fade` del canal de motion (escala 0.85,
  rápida). Pertenecen al canal, no al contrato, y la política de
  `prefers-reduced-motion` la emite el generador.

Artefactos y gates:
- Sonda antes/después = 0 diffs en 1.392 valores × 8 estados.
- R-5.4: 0/6, los seis adjudicados con su medición — el indicador sólo existe
  un instante tras copiar (0 nodos en reposo, contados) y forzándolo los seis
  mueven su propiedad (gap 4→1234px, padding 2/8→1235/1236px, radio
  9999→1237px, tipo 12→99px, peso 500→123).
- censo --only 63 % · component:audit PASS · suite eidos sin rojos nuevos ·
  rtl:check 0 · docs:check 0 · check por fichero limpio.
- README con «Talla y tema» y tab `Tokens` en la demo (6 claves).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent d568577eb7
commit 151e5159b8

@ -10,9 +10,9 @@
- **Medido**: 2026-08-21 · **162 recetas** con CSS + **8 componentes sin receta** = 170 fichas, el árbol entero de `eidos/components/`
- **La pregunta**: ¿cuánto de la apariencia de cada componente puede cambiar un tema **sin tocar el sistema ni la receta**?
- **Alcance global**: **50%** — 2318 de 4676 knobs pasan por un token público del componente
- **Reparto**: público 2318 · privado 627 · global 1164 · literal 567 · sistema transversal 509 _(fuera del ratio)_
- **Sin token público propio**: 33 · **alcance < 20 %**: 26 · **alcance 100 %**: 10 · **con eje `size`**: 59
- **Alcance global**: **50%** — 2323 de 4676 knobs pasan por un token público del componente
- **Reparto**: público 2323 · privado 627 · global 1160 · literal 566 · sistema transversal 509 _(fuera del ratio)_
- **Sin token público propio**: 32 · **alcance < 20 %**: 25 · **alcance 100 %**: 10 · **con eje `size`**: 59
## Cómo se lee
@ -91,12 +91,12 @@ theming §6.7 lo pone delante (`hover-bg`). Las fichas proponen la forma
| 19 | [timeline](./timeline.md) | 24 | 51% | 49 | 45 |
| 20 | [avatar](./avatar.md) | 23 | 36% | 36 | 0 |
## Sin ningún token público propio (33)
## Sin ningún token público propio (32)
Ni una clave pública en `lib/recipes/base.ts` — alguno tiene privados forward
(`_palette-*`), que no son contrato: un tema no puede nombrarlos.
[`alert-dialog`](./alert-dialog.md) · [`aspect-ratio`](./aspect-ratio.md) · [`auto-grid`](./auto-grid.md) · [`avatar`](./avatar.md) · [`cascade`](./cascade.md) · [`clipboard`](./clipboard.md) · [`css-field`](./css-field.md) · [`date-picker`](./date-picker.md) · [`display`](./display.md) · [`field-langs`](./field-langs.md) · [`heading`](./heading.md) · [`highlight`](./highlight.md) · [`knob`](./knob.md) · [`mark`](./mark.md) · [`mask-field`](./mask-field.md) · [`menu-dial`](./menu-dial.md) · [`mockup`](./mockup.md) · [`month-grid`](./month-grid.md) · [`motion`](./motion.md) · [`number-field`](./number-field.md) · [`palabras`](./palabras.md) · [`range-calendar`](./range-calendar.md) · [`scroll-frames`](./scroll-frames.md) · [`skin-media-player`](./skin-media-player.md) · [`stack`](./stack.md) · [`text`](./text.md) · [`text-blur`](./text-blur.md) · [`text-circular`](./text-circular.md) · [`text-focus`](./text-focus.md) · [`text-gradient`](./text-gradient.md) · [`text-scramble`](./text-scramble.md) · [`wrap`](./wrap.md) · [`year-grid`](./year-grid.md)
[`alert-dialog`](./alert-dialog.md) · [`aspect-ratio`](./aspect-ratio.md) · [`auto-grid`](./auto-grid.md) · [`avatar`](./avatar.md) · [`cascade`](./cascade.md) · [`css-field`](./css-field.md) · [`date-picker`](./date-picker.md) · [`display`](./display.md) · [`field-langs`](./field-langs.md) · [`heading`](./heading.md) · [`highlight`](./highlight.md) · [`knob`](./knob.md) · [`mark`](./mark.md) · [`mask-field`](./mask-field.md) · [`menu-dial`](./menu-dial.md) · [`mockup`](./mockup.md) · [`month-grid`](./month-grid.md) · [`motion`](./motion.md) · [`number-field`](./number-field.md) · [`palabras`](./palabras.md) · [`range-calendar`](./range-calendar.md) · [`scroll-frames`](./scroll-frames.md) · [`skin-media-player`](./skin-media-player.md) · [`stack`](./stack.md) · [`text`](./text.md) · [`text-blur`](./text-blur.md) · [`text-circular`](./text-circular.md) · [`text-focus`](./text-focus.md) · [`text-gradient`](./text-gradient.md) · [`text-scramble`](./text-scramble.md) · [`wrap`](./wrap.md) · [`year-grid`](./year-grid.md)
## Tabla completa (162 recetas, por alcance ascendente)
@ -110,7 +110,6 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [field-langs](./field-langs.md) | 0% | 41 | 0 | 0 | 31 | 8 | 2 | 0 | – |
| [skin-media-player](./skin-media-player.md) | 0% | 11 | 0 | 0 | 2 | 7 | 2 | 0 | y |
| [scroll-frames](./scroll-frames.md) | 0% | 9 | 0 | 1 | 4 | 4 | 0 | 0 | – |
| [clipboard](./clipboard.md) | 0% | 8 | 0 | 2 | 5 | 1 | 0 | 0 | – |
| [menu-dial](./menu-dial.md) | 0% | 8 | 0 | 3 | 1 | 4 | 0 | 0 | y |
| [mark](./mark.md) | 0% | 5 | 0 | 2 | 1 | 2 | 0 | 0 | – |
| [aspect-ratio](./aspect-ratio.md) | 0% | 3 | 0 | 0 | 1 | 2 | 0 | 0 | – |
@ -189,6 +188,7 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [emoji-picker](./emoji-picker.md) | 61% | 32 | 17 | 0 | 8 | 3 | 4 | 17 | – |
| [pin-input](./pin-input.md) | 61% | 19 | 11 | 0 | 6 | 1 | 1 | 20 | y |
| [editable](./editable.md) | 63% | 44 | 25 | 12 | 1 | 2 | 4 | 56 | y |
| [clipboard](./clipboard.md) | 63% | 8 | 5 | 2 | 1 | 0 | 0 | 6 | – |
| [text-focus](./text-focus.md) | 63% | 8 | 5 | 0 | 1 | 2 | 0 | 0 | – |
| [spin-field](./spin-field.md) | 64% | 27 | 16 | 0 | 8 | 1 | 2 | 20 | – |
| [file-upload](./file-upload.md) | 64% | 61 | 38 | 16 | 1 | 4 | 2 | 98 | y |

@ -5,22 +5,18 @@
> Vista de conjunto: [README](./README.md) · método y protocolo:
> [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7.
- **Medido**: 2026-08-20 · **Alcance**: **0%** — 0 de 8 knobs por token público
- **Knobs de apariencia**: 8 — público 0 · privado 2 · global 5 · literal 1 · sistema 0 _(fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 0 pública(s) · 2 privada(s) forward — `_palette-solid`, `_palette-element`
- **Medido**: 2026-08-21 · **Alcance**: **63%** — 5 de 8 knobs por token público
- **Knobs de apariencia**: 8 — público 5 · privado 2 · global 1 · literal 0 · sistema 0 _(fuera del ratio)_
- **Contrato hoy** (`lib/recipes/base.ts`): 6 pública(s) — `indicator-gap`, `indicator-padding-block`, `indicator-padding-inline`, `indicator-radius`, `indicator-font-size`, `indicator-font-weight` · 2 privada(s) forward — `_palette-solid`, `_palette-element`
- **Eje `size`**: no · **ficheros**: `clipboard.css`
## 1. Knobs fuera de alcance
### 1.1 Directo a primitivo global (5)
### 1.1 Directo a primitivo global (1)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `clipboard.css:21` | `[data-clipboard-indicator]` | `gap` | `var(--space-1)` |
| 2 | `clipboard.css:23` | `[data-clipboard-indicator]` | `border-radius` | `var(--radius-full)` |
| 3 | `clipboard.css:26` | `[data-clipboard-indicator]` | `font-size` | `var(--font-size-xs)` |
| 4 | `clipboard.css:27` | `[data-clipboard-indicator]` | `font-family` | `var(--style-label-font-family)` |
| 5 | `clipboard.css:28` | `[data-clipboard-indicator]` | `font-weight` | `var(--font-weight-medium, 500)` |
| 1 | `clipboard.css:27` | `[data-clipboard-indicator]` | `font-family` | `var(--style-label-font-family)` |
### 1.2 A través de un privado (2)
@ -29,11 +25,9 @@
| 1 | `clipboard.css:24` | `[data-clipboard-indicator]` | `background` | `var(--_clipboard-palette-element)` |
| 2 | `clipboard.css:25` | `[data-clipboard-indicator]` | `color` | `var(--_clipboard-palette-solid)` |
### 1.3 Literales (1)
### 1.3 Literales (0)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
| 1 | `clipboard.css:22` | `[data-clipboard-indicator]` | `padding` | `0.125rem 0.5rem` |
_Ninguno._
## 2. Sistema transversal (0) — informativo, fuera del ratio
@ -49,7 +43,7 @@ Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inl
## 4. Propuesta de corrección
### 4.1 Tokens a declarar en `lib/recipes/base.ts` (5)
### 4.1 Tokens a declarar en `lib/recipes/base.ts` (1)
Valor **verbatim** del CSS de hoy: el default no se mueve, sólo cambia quién
puede moverlo. Nombres derivados de recipe-contract §1 (ejes lógicos, talla
@ -59,15 +53,10 @@ no distingue lo que debería — se marca `⚠`.
| token (`--clipboard-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
| `indicator-gap` | `root` | `var(--space-1)` | 1 |
| `indicator-radius` | `root` | `var(--radius-full)` | 1 |
| `indicator-font-size` | `root` | `var(--font-size-xs)` | 1 |
| `indicator-font-family` | `root` | `var(--style-label-font-family)` | 1 |
| `indicator-font-weight` | `root` | `var(--font-weight-medium, 500)` | 1 |
### 4.2 Sin nombre mecánico (3)
### 4.2 Sin nombre mecánico (2)
- **⚠ decisión: `padding` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `padding`.
- **⚠ decisión: el privado que alimenta este knob no se declara en el CSS (viene de `base.ts` o de un estilo inline) — hay que resolverlo antes de nombrarlo** — 2: `background`, `color`.
### 4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4)
@ -82,6 +71,19 @@ no distingue lo que debería — se marca `⚠`.
<!-- veredicto:start -->
_(pendiente — lo escribe el autor; se conserva al regenerar)_
**Medido 2026-08-22.** El host va `display: contents` y el trigger reusa la
receta de `Button` por el patrón de consumidor, así que **todo lo tokenizable
es el INDICADOR** — la píldora «Copied!» que aparece un instante tras copiar.
1. **Los dos forwards de paleta se quedan privados** (THM-2): el fondo y la
tinta del chip los leen a pelo, sin sitio donde meter un público sin
duplicar la regla. Son el 25 % que no alcanza.
2. **`font-family` NO se acuña**: lee `--style-label-font-family`, la capa
tipográfica (mismo criterio que `code-block`, `kbd` y `announce`).
3. **El padding es un shorthand de dos valores** → dos knobs lógicos.
4. **Los `--motion-*` no son knobs de tema**: son los ganchos del canal de
motion con que el indicador afina el preset `scale-fade` (escala 0.85,
rápida). Pertenecen al canal, no al contrato de la receta, y la política de
`prefers-reduced-motion` la emite el generador.
<!-- veredicto:end -->

@ -231,6 +231,20 @@ export const SENTINEL_EXCEPTIONS: Record<string, Record<string, string>> = {
'title-padding-block-end':
'static heading not mounted in the demo; forced → reaches (8 → 1234px)'
},
clipboard: {
'indicator-gap':
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (4 → 1234px)',
'indicator-padding-block':
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (2 → 1235px)',
'indicator-padding-inline':
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (8 → 1236px)',
'indicator-radius':
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (9999 → 1237px)',
'indicator-font-size':
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (12 → 99px)',
'indicator-font-weight':
'the “Copied!” indicator only exists for a moment after a copy (0 nodes at rest, counted); forced → reaches (500 → 123)'
},
code: {
radius:
'the DEMO HARNESS wins: `[data-uix-docs] code` (uix.css, 0-1-1) beats the recipe base rule `[data-code]` (0-1-0) and sets border-radius/padding/font on the inline code — confirmed with CDP matched-rules. The variant rules (0,2,0) DO win, which is why only this base-rule token reads dead. Same harness rule that hides the <pre> of code-block (next-features §13)',

@ -66,6 +66,31 @@ Referencias: [Ark UI Clipboard](https://ark-ui.com/docs/components/clipboard) ·
servicios): permisos, fallback de ejecución y soporte viven en el art,
no en el componente.
## Talla y tema
**Guard R-5.4** (`npm run theming:sentinel -- clipboard <url>`): 0/6 en vivo,
los seis adjudicados con su medición en
`scripts/theming-sentinel-exceptions.ts` — el indicador «Copied!» sólo existe
un instante tras copiar (0 nodos en reposo, contados), y forzándolo los seis
mueven su propiedad. Corrido 2026-08-22.
Contrato en `lib/recipes/base.ts > clipboard` (6 claves, fusionadas con los dos
forwards de paleta). Alcance del censo: **63 %** (era 0 %).
**Todo lo tokenizable es el INDICADOR.** El host va `display: contents` y el
trigger reusa la receta de `Button` por el patrón de consumidor, así que la
receta sólo posee la píldora que aparece tras copiar.
Los dos forwards de paleta siguen privados (THM-2): el fondo y la tinta del
chip los leen a pelo, sin sitio donde meter un público sin duplicar la regla —
son el 37 % que no alcanza. `font-family` queda fuera por la misma razón que en
`code-block`, `kbd` y `announce`: lo posee la capa tipográfica.
**Los `--motion-*` no son knobs de tema**: son los ganchos con que el indicador
afina el preset `scale-fade` del canal de motion (escala 0.85, rápida).
Pertenecen al canal, no al contrato de la receta, y la política de
`prefers-reduced-motion` la emite el generador.
## Gaps
| Gap | Disposición | Detalle |

@ -18,14 +18,14 @@
[data-clipboard-indicator] {
display: inline-flex;
align-items: center;
gap: var(--space-1);
padding: 0.125rem 0.5rem;
border-radius: var(--radius-full);
gap: var(--clipboard-indicator-gap);
padding: var(--clipboard-indicator-padding-block) var(--clipboard-indicator-padding-inline);
border-radius: var(--clipboard-indicator-radius);
background: var(--_clipboard-palette-element);
color: var(--_clipboard-palette-solid);
font-size: var(--font-size-xs);
font-size: var(--clipboard-indicator-font-size);
font-family: var(--style-label-font-family);
font-weight: var(--font-weight-medium, 500);
font-weight: var(--clipboard-indicator-font-weight);
/* Mount pop = the `scale-fade` preset (motionAttrs in the wrapper). The
hooks keep the original punchier pop (scale 0.85, fast) — the local
`clipboard-pop` keyframe this replaced lived outside the motion channel. */

@ -2070,6 +2070,12 @@
--gradient-picker-selected-preset-border: var(--color-primary-border);
--gradient-picker-selected-preset-outline-offset: 2px;
--gradient-picker-actions-gap: var(--space-2);
--clipboard-indicator-gap: var(--space-1);
--clipboard-indicator-padding-block: 0.125rem;
--clipboard-indicator-padding-inline: 0.5rem;
--clipboard-indicator-radius: var(--radius-full);
--clipboard-indicator-font-size: var(--size-xs-font-size);
--clipboard-indicator-font-weight: var(--font-weight-medium, 500);
--carousel-indicator-size-xs: 0.375rem;
--carousel-indicator-size-sm: 0.4375rem;
--carousel-indicator-size-md: 0.5rem;

@ -2924,6 +2924,15 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
'actions-gap': 'var(--space-2)'
},
clipboard: {
// Host is `display: contents` and the trigger reuses Button's recipe, so
// everything here is the INDICATOR — the brief "Copied!" pill.
// `font-family` is absent: it reads the `--style-label-*` layer.
'indicator-gap': 'var(--space-1)',
'indicator-padding-block': '0.125rem',
'indicator-padding-inline': '0.5rem',
'indicator-radius': 'var(--radius-full)',
'indicator-font-size': 'var(--size-xs-font-size)',
'indicator-font-weight': 'var(--font-weight-medium, 500)',
'_palette-solid': { value: 'var(--color-primary-solid)', scope: 'host' },
'_palette-element': { value: 'var(--color-primary-element)', scope: 'host' }
},

@ -8,10 +8,11 @@
import { compileMorfo } from '$uix/morfo';
import { clipboardMorfo } from '@/uix/morfo/components/clipboard';
import { getActiveUix } from '$active-uix';
import TokensPanel from '../../lib/TokensPanel.svelte';
const uix = getActiveUix();
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens' | 'a11y';
let tab = $state<Tab>('live');
// ── User-controlled value ────────────────────────────────────────────
@ -230,6 +231,9 @@
<span data-uix-tab-count>{events.length}</span>
</button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'tokens'} onclick={() => (tab = 'tokens')}>
Tokens <span data-uix-tab-count>6</span>
</button>
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
</div>
@ -482,6 +486,10 @@
</section>
{/if}
{#if tab === 'tokens'}
<TokensPanel component="clipboard" stage={stageRef ?? undefined} />
{/if}
{#if tab === 'a11y'}
<section data-uix-section>
<h2 data-uix-section-title>Accessibility</h2>

Loading…
Cancel
Save

Powered by TurnKey Linux.