diff --git a/docs/audit/theming/README.md b/docs/audit/theming/README.md index a9df677e4..f3129fc03 100644 --- a/docs/audit/theming/README.md +++ b/docs/audit/theming/README.md @@ -10,9 +10,9 @@ - **Medido**: 2026-08-20 · **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**: **38%** — 1837 de 4861 knobs pasan por un token público del componente -- **Reparto**: público 1837 · privado 788 · global 1633 · literal 603 · sistema transversal 338 _(fuera del ratio)_ -- **Sin token público propio**: 56 · **alcance < 20 %**: 51 · **alcance 100 %**: 7 · **con eje `size`**: 79 +- **Alcance global**: **39%** — 1885 de 4861 knobs pasan por un token público del componente +- **Reparto**: público 1885 · privado 780 · global 1598 · literal 598 · sistema transversal 338 _(fuera del ratio)_ +- **Sin token público propio**: 55 · **alcance < 20 %**: 50 · **alcance 100 %**: 7 · **con eje `size`**: 78 ## Cómo se lee @@ -79,24 +79,24 @@ theming §6.7 lo pone delante (`hover-bg`). Las fichas proponen la forma | 7 | [time-range-picker](./time-range-picker.md) | 68 | 19% | 88 | 22 | | 8 | [month-grid](./month-grid.md) | 58 | 0% | 61 | 0 | | 9 | [year-grid](./year-grid.md) | 58 | 0% | 61 | 0 | -| 10 | [command](./command.md) | 50 | 0% | 52 | 0 | -| 11 | [time-picker](./time-picker.md) | 47 | 18% | 59 | 16 | -| 12 | [media-player](./media-player.md) | 45 | 0% | 45 | 2 | -| 13 | [color-picker](./color-picker.md) | 43 | 46% | 82 | 54 | -| 14 | [table](./table.md) | 42 | 0% | 46 | 0 | -| 15 | [field-langs](./field-langs.md) | 39 | 0% | 41 | 0 | -| 16 | [tree-grid](./tree-grid.md) | 39 | 0% | 42 | 0 | -| 17 | [gradient-picker](./gradient-picker.md) | 35 | 0% | 38 | 0 | -| 18 | [listbox](./listbox.md) | 35 | 0% | 38 | 0 | -| 19 | [field](./field.md) | 33 | 51% | 73 | 60 | -| 20 | [feed](./feed.md) | 32 | 0% | 33 | 0 | +| 10 | [time-picker](./time-picker.md) | 47 | 18% | 59 | 16 | +| 11 | [media-player](./media-player.md) | 45 | 0% | 45 | 2 | +| 12 | [color-picker](./color-picker.md) | 43 | 46% | 82 | 54 | +| 13 | [table](./table.md) | 42 | 0% | 46 | 0 | +| 14 | [field-langs](./field-langs.md) | 39 | 0% | 41 | 0 | +| 15 | [tree-grid](./tree-grid.md) | 39 | 0% | 42 | 0 | +| 16 | [gradient-picker](./gradient-picker.md) | 35 | 0% | 38 | 0 | +| 17 | [listbox](./listbox.md) | 35 | 0% | 38 | 0 | +| 18 | [field](./field.md) | 33 | 51% | 73 | 60 | +| 19 | [feed](./feed.md) | 32 | 0% | 33 | 0 | +| 20 | [carousel](./carousel.md) | 31 | 0% | 36 | 0 | -## Sin ningún token público propio (56) +## Sin ningún token público propio (55) 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) · [`announce`](./announce.md) · [`aspect-ratio`](./aspect-ratio.md) · [`auto-grid`](./auto-grid.md) · [`avatar`](./avatar.md) · [`card-group`](./card-group.md) · [`carousel`](./carousel.md) · [`cascade`](./cascade.md) · [`clipboard`](./clipboard.md) · [`code`](./code.md) · [`code-block`](./code-block.md) · [`command`](./command.md) · [`css-field`](./css-field.md) · [`date-picker`](./date-picker.md) · [`display`](./display.md) · [`feed`](./feed.md) · [`field-langs`](./field-langs.md) · [`gradient-picker`](./gradient-picker.md) · [`grid-list`](./grid-list.md) · [`heading`](./heading.md) · [`highlight`](./highlight.md) · [`kbd`](./kbd.md) · [`knob`](./knob.md) · [`label`](./label.md) · [`link`](./link.md) · [`link-preview`](./link-preview.md) · [`listbox`](./listbox.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) · [`picker-shell`](./picker-shell.md) · [`range-calendar`](./range-calendar.md) · [`scroll-frames`](./scroll-frames.md) · [`skeleton`](./skeleton.md) · [`skin-media-player`](./skin-media-player.md) · [`spinner`](./spinner.md) · [`stack`](./stack.md) · [`table`](./table.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) · [`textarea`](./textarea.md) · [`tree-grid`](./tree-grid.md) · [`tree-view`](./tree-view.md) · [`virtual-grid`](./virtual-grid.md) · [`virtual-list`](./virtual-list.md) · [`wrap`](./wrap.md) · [`year-grid`](./year-grid.md) +[`alert-dialog`](./alert-dialog.md) · [`announce`](./announce.md) · [`aspect-ratio`](./aspect-ratio.md) · [`auto-grid`](./auto-grid.md) · [`avatar`](./avatar.md) · [`card-group`](./card-group.md) · [`carousel`](./carousel.md) · [`cascade`](./cascade.md) · [`clipboard`](./clipboard.md) · [`code`](./code.md) · [`code-block`](./code-block.md) · [`css-field`](./css-field.md) · [`date-picker`](./date-picker.md) · [`display`](./display.md) · [`feed`](./feed.md) · [`field-langs`](./field-langs.md) · [`gradient-picker`](./gradient-picker.md) · [`grid-list`](./grid-list.md) · [`heading`](./heading.md) · [`highlight`](./highlight.md) · [`kbd`](./kbd.md) · [`knob`](./knob.md) · [`label`](./label.md) · [`link`](./link.md) · [`link-preview`](./link-preview.md) · [`listbox`](./listbox.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) · [`picker-shell`](./picker-shell.md) · [`range-calendar`](./range-calendar.md) · [`scroll-frames`](./scroll-frames.md) · [`skeleton`](./skeleton.md) · [`skin-media-player`](./skin-media-player.md) · [`spinner`](./spinner.md) · [`stack`](./stack.md) · [`table`](./table.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) · [`textarea`](./textarea.md) · [`tree-grid`](./tree-grid.md) · [`tree-view`](./tree-view.md) · [`virtual-grid`](./virtual-grid.md) · [`virtual-list`](./virtual-list.md) · [`wrap`](./wrap.md) · [`year-grid`](./year-grid.md) ## Tabla completa (162 recetas, por alcance ascendente) @@ -107,7 +107,6 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component | [range-calendar](./range-calendar.md) | 0% | 98 | 0 | 7 | 84 | 4 | 3 | 0 | y | | [month-grid](./month-grid.md) | 0% | 61 | 0 | 13 | 40 | 5 | 3 | 0 | y | | [year-grid](./year-grid.md) | 0% | 61 | 0 | 13 | 40 | 5 | 3 | 0 | y | -| [command](./command.md) | 0% | 52 | 0 | 9 | 35 | 6 | 2 | 0 | y | | [table](./table.md) | 0% | 46 | 0 | 23 | 14 | 5 | 4 | 0 | y | | [media-player](./media-player.md) | 0% | 45 | 0 | 0 | 37 | 8 | 0 | 2 | y | | [tree-grid](./tree-grid.md) | 0% | 42 | 0 | 29 | 8 | 2 | 3 | 0 | y | @@ -244,6 +243,7 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component | [breadcrumb](./breadcrumb.md) | 90% | 23 | 18 | 2 | 0 | 0 | 3 | 28 | y | | [collapsible](./collapsible.md) | 90% | 12 | 9 | 0 | 1 | 0 | 2 | 11 | – | | [scroll-area](./scroll-area.md) | 94% | 17 | 15 | 0 | 0 | 1 | 1 | 13 | – | +| [command](./command.md) | 96% | 52 | 48 | 1 | 0 | 1 | 2 | 57 | – | | [heading](./heading.md) | — | 36 | 0 | 0 | 0 | 0 | 36 | 0 | – | | [s-text](./s-text.md) | 100% | 30 | 2 | 0 | 0 | 0 | 28 | 3 | – | | [text](./text.md) | — | 24 | 0 | 0 | 0 | 0 | 24 | 0 | – | diff --git a/docs/audit/theming/command.md b/docs/audit/theming/command.md index 210a454b1..7307da1b9 100644 --- a/docs/audit/theming/command.md +++ b/docs/audit/theming/command.md @@ -5,77 +5,28 @@ > 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 50 knobs por token público -- **Knobs de apariencia**: 52 — público 0 · privado 9 · global 35 · literal 6 · sistema 2 _(fuera del ratio)_ -- **Contrato hoy** (`lib/recipes/base.ts`): **sin entrada en `base.ts`** -- **Eje `size`**: sí · **ficheros**: `command.css` +- **Medido**: 2026-08-20 · **Alcance**: **96%** — 48 de 50 knobs por token público +- **Knobs de apariencia**: 52 — público 48 · privado 1 · global 0 · literal 1 · sistema 2 _(fuera del ratio)_ +- **Contrato hoy** (`lib/recipes/base.ts`): 57 pública(s) — `input-padding-inline-sm`, `input-padding-inline-md`, `input-padding-inline-lg`, `input-font-size-sm`, `input-font-size-md`, `input-font-size-lg`, `item-padding-inline-sm`, `item-padding-inline-md`, `item-padding-inline-lg`, `item-font-size-sm`, `item-font-size-md`, `item-font-size-lg`, `input-padding-inline`, `input-font-size`, `item-padding-inline`, `item-font-size`, `radius`, `font-family`, `line-height`, `fg`, `input-padding-block`, `input-margin`, `input-border-width`, `input-border`, `input-radius`, `input-bg`, `input-fg`, `input-placeholder-fg`, `focus-input-border`, `loading-height`, `loading-fg`, `viewport-max-block`, `viewport-gap`, `viewport-padding-inline`, `viewport-padding-block-end`, `scrollbar-inset`, `viewport-padding`, `group-gap`, `group-heading-padding-block`, `group-heading-fg`, `group-heading-font-size`, `group-heading-font-weight`, `group-heading-tracking`, `group-items-gap`, `item-gap`, `item-fg`, `item-icon-size`, `item-icon-fg`, `item-shortcut-gap`, `item-shortcut-fg`, `item-shortcut-font-size`, `selected-item-sub-fg`, `empty-padding-block`, `empty-fg`, `separator-height`, `separator-margin-block`, `separator-bg` +- **Eje `size`**: no · **ficheros**: `command.css` ## 1. Knobs fuera de alcance -### 1.1 Directo a primitivo global (35) +### 1.1 Directo a primitivo global (0) -| # | fichero:línea | selector | propiedad | valor | -| ---: | --- | --- | --- | --- | -| 1 | `command.css:54` | `[data-command]` | `font-family` | `var(--style-label-font-family)` | -| 2 | `command.css:55` | `[data-command]` | `line-height` | `var(--leading-ui)` | -| 3 | `command.css:56` | `[data-command]` | `color` | `var(--color-content-primary)` | -| 4 | `command.css:124` | `[data-command-input]` | `padding-block` | `var(--space-1-5)` | -| 5 | `command.css:127` | `[data-command-input]` | `border` | `var(--border-width) solid var(--color-border-default)` | -| 6 | `command.css:128` | `[data-command-input]` | `border-radius` | `var(--radius-md)` | -| 7 | `command.css:129` | `[data-command-input]` | `background` | `var(--color-surface-default)` | -| 8 | `command.css:130` | `[data-command-input]` | `color` | `var(--color-content-primary)` | -| 9 | `command.css:131` | `[data-command-input]` | `font-family` | `var(--style-label-font-family)` | -| 10 | `command.css:133` | `[data-command-input]` | `line-height` | `var(--leading-ui)` | -| 11 | `command.css:141` | `[data-command-input]:focus-visible` | `border-color` | `var(--color-primary-border)` | -| 12 | `command.css:147` | `[data-command-input]::placeholder` | `color` | `var(--color-content-muted)` | -| 13 | `command.css:176` | `[data-command-list] [data-scroll-area-viewport]` | `gap` | `var(--space-1)` | -| 14 | `command.css:179` | `[data-command-list] [data-scroll-area-viewport]` | `padding-inline` | `var(--space-2)` | -| 15 | `command.css:180` | `[data-command-list] [data-scroll-area-viewport]` | `padding-block-end` | `var(--space-1)` | -| 16 | `command.css:188` | `[data-command-list] [data-scroll-area]:has(> [data-scroll-area-scrollbar][data-orientation='vertical']) > [data-scroll-area-viewport]` | `padding-inline-end` | `calc(var(--space-2) + var(--space-3-5))` | -| 17 | `command.css:194` | `[data-command-viewport]` | `gap` | `var(--space-1)` | -| 18 | `command.css:196` | `[data-command-viewport]` | `padding` | `var(--space-1)` | -| 19 | `command.css:205` | `[data-command-group]` | `gap` | `var(--space-1)` | -| 20 | `command.css:209` | `[data-command-group-heading]` | `padding-block` | `var(--space-1)` | -| 21 | `command.css:211` | `[data-command-group-heading]` | `color` | `var(--color-content-muted)` | -| 22 | `command.css:212` | `[data-command-group-heading]` | `font-size` | `var(--font-size-xs)` | -| 23 | `command.css:213` | `[data-command-group-heading]` | `font-weight` | `var(--font-weight-medium)` | -| 24 | `command.css:215` | `[data-command-group-heading]` | `letter-spacing` | `var(--tracking-caps)` | -| 25 | `command.css:229` | `[data-command-item], [data-command-link-item]` | `gap` | `var(--space-2)` | -| 26 | `command.css:234` | `[data-command-item], [data-command-link-item]` | `color` | `var(--color-content-primary)` | -| 27 | `command.css:236` | `[data-command-item], [data-command-link-item]` | `line-height` | `var(--leading-ui)` | -| 28 | `command.css:263` | `[data-command-item-icon]` | `color` | `var(--color-content-secondary)` | -| 29 | `command.css:270` | `[data-command-item-shortcut]` | `gap` | `var(--space-1)` | -| 30 | `command.css:272` | `[data-command-item-shortcut]` | `color` | `var(--color-content-muted)` | -| 31 | `command.css:281` | `[data-command-item][data-selected] [data-command-item-icon], [data-command-item][data-selected] [data-command-item-shortcut], [data-command-link-item][data-selected] [data-command-item-icon], [data-command-link-item][data-selected] [data-command-item-shortcut]` | `color` | `var(--color-content-primary)` | -| 32 | `command.css:287` | `[data-command-empty]` | `padding-block` | `var(--space-4)` | -| 33 | `command.css:289` | `[data-command-empty]` | `color` | `var(--color-content-muted)` | -| 34 | `command.css:297` | `[data-command-separator]` | `block-size` | `var(--border-width)` | -| 35 | `command.css:299` | `[data-command-separator]` | `background` | `var(--color-neutral-separator)` | - -### 1.2 A través de un privado (9) +_Ninguno._ + +### 1.2 A través de un privado (1) | # | fichero:línea | selector | propiedad | valor | | ---: | --- | --- | --- | --- | -| 1 | `command.css:61` | `[data-command]` | `border-radius` | `var(--_command-radius)` | -| 2 | `command.css:126` | `[data-command-input]` | `padding-inline` | `var(--_command-input-padding-inline)` | -| 3 | `command.css:132` | `[data-command-input]` | `font-size` | `var(--_command-input-font-size)` | -| 4 | `command.css:155` | `[data-command-loading]` | `background` | `linear-gradient( to right, var(--color-primary-solid) 0%, var(--color-primary-solid) var(--_command-loading-progress, 0%), transparent var(--_command-loading-progress, 0%) )` | -| 5 | `command.css:210` | `[data-command-group-heading]` | `padding-inline` | `var(--_command-item-padding-inline)` | -| 6 | `command.css:230` | `[data-command-item], [data-command-link-item]` | `padding-inline` | `var(--_command-item-padding-inline)` | -| 7 | `command.css:235` | `[data-command-item], [data-command-link-item]` | `font-size` | `var(--_command-item-font-size)` | -| 8 | `command.css:288` | `[data-command-empty]` | `padding-inline` | `var(--_command-item-padding-inline)` | -| 9 | `command.css:290` | `[data-command-empty]` | `font-size` | `var(--_command-item-font-size)` | - -### 1.3 Literales (6) +| 1 | `command.css:57` | `[data-command]` | `border-radius` | `var(--_command-radius)` | + +### 1.3 Literales (1) | # | fichero:línea | selector | propiedad | valor | | ---: | --- | --- | --- | --- | -| 1 | `command.css:46` | `[data-command]` | `inline-size` | `100%` | -| 2 | `command.css:154` | `[data-command-loading]` | `block-size` | `2px` | -| 3 | `command.css:220` | `[data-command-group-items]` | `gap` | `1px` | -| 4 | `command.css:261` | `[data-command-item-icon]` | `inline-size` | `1.125em` | -| 5 | `command.css:262` | `[data-command-item-icon]` | `block-size` | `1.125em` | -| 6 | `command.css:273` | `[data-command-item-shortcut]` | `font-size` | `0.875em` | +| 1 | `command.css:42` | `[data-command]` | `inline-size` | `100%` | ## 2. Sistema transversal (2) — informativo, fuera del ratio @@ -83,27 +34,22 @@ Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2). | # | fichero:línea | selector | propiedad | valor | | ---: | --- | --- | --- | --- | -| 1 | `command.css:142` | `[data-command-input]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` | -| 2 | `command.css:251` | `[data-command-item][data-disabled], [data-command-link-item][data-disabled]` | `opacity` | `var(--opacity-disabled)` | +| 1 | `command.css:129` | `[data-command-input]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` | +| 2 | `command.css:240` | `[data-command-item][data-disabled], [data-command-link-item][data-disabled]` | `opacity` | `var(--opacity-disabled)` | ## 3. Privados de la receta — ¿de dónde sale su valor? | privado | declaraciones | valor(es) | origen | ¿deriva de un público? | | --- | ---: | --- | --- | :-: | -| `--_command-input-padding-inline` | 3 | `var(--space-3)`, `var(--space-2)`, `var(--space-4)` | global | no | -| `--_command-input-font-size` | 3 | `var(--font-size-md)`, `var(--font-size-sm)`, `var(--font-size-lg)` | global | no | -| `--_command-item-padding-inline` | 3 | `var(--space-3)`, `var(--space-2)`, `var(--space-4)` | global | no | -| `--_command-item-font-size` | 3 | `var(--font-size-md)`, `var(--font-size-sm)`, `var(--font-size-lg)` | global | no | -| `--_command-radius` | 2 | `var(--radius-lg)`, `var(--dialog-content-radius)` | global | no | +| `--_command-radius` | 2 | `var(--command-radius)`, `var(--dialog-content-radius)` | global, public | no | Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inline del wrapper): `--_command-loading-progress`. ## 4. Propuesta de corrección - **Consume la capa compartida `list-surface`.** Un eje que la capa posee se consume como `var(--_x, var(--x))`; el consumidor **no acuña** `--command-{eje}` para él — sería un vocabulario paralelo (README de `eidos/components`, «Capas compartidas» regla 2). -- **Tiene eje `size`**: los tokens dimensionales van por talla (`{part}-{eje}-{k}`) apuntando al bundle `--size-{k}-*`, nunca al primitivo crudo (theming §5; el guard `recipe-css-contract` prohíbe el primitivo). -### 4.1 Tokens a declarar en `lib/recipes/base.ts` (58) +### 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 @@ -113,80 +59,15 @@ no distingue lo que debería — se marca `⚠`. | token (`--command-…`) | scope TSC | valor propuesto | usos | | --- | --- | --- | ---: | -| `radius` | `root` | ⚠ `var(--radius-lg)` / `var(--dialog-content-radius)` | 2 | -| `input-fg` | `root` | ⚠ `var(--color-content-primary)` / `var(--color-content-muted)` | 2 | -| `list-padding-inline` | `root` | ⚠ `var(--space-2)` / `calc(var(--space-2) + var(--space-3-5))` | 2 | -| `font-family` | `root` | `var(--style-label-font-family)` | 1 | -| `line-height` | `root` | `var(--leading-ui)` | 1 | -| `fg` | `root` | `var(--color-content-primary)` | 1 | -| `input-padding-block` | `root` | `var(--space-1-5)` | 1 | -| `input-padding-inline-md` | `size:md` | `var(--space-3)` | 1 | -| `input-padding-inline-sm` | `size:sm` | `var(--space-2)` | 1 | -| `input-padding-inline-lg` | `size:lg` | `var(--space-4)` | 1 | -| `input-radius` | `root` | `var(--radius-md)` | 1 | -| `input-bg` | `root` | `var(--color-surface-default)` | 1 | -| `input-font-family` | `root` | `var(--style-label-font-family)` | 1 | -| `input-font-size-md` | `size:md` | `var(--font-size-md)` | 1 | -| `input-font-size-sm` | `size:sm` | `var(--font-size-sm)` | 1 | -| `input-font-size-lg` | `size:lg` | `var(--font-size-lg)` | 1 | -| `input-line-height` | `root` | `var(--leading-ui)` | 1 | -| `input-border` | `root` | `var(--color-primary-border)` | 1 | -| `loading-height` | `root` | `2px` | 1 | -| `list-gap` | `root` | `var(--space-1)` | 1 | -| `list-padding-block` | `root` | `var(--space-1)` | 1 | -| `viewport-gap` | `root` | `var(--space-1)` | 1 | -| `group-gap` | `root` | `var(--space-1)` | 1 | -| `group-heading-padding-block` | `root` | `var(--space-1)` | 1 | -| `group-heading-padding-inline-md` | `size:md` | `var(--space-3)` | 1 | -| `group-heading-padding-inline-sm` | `size:sm` | `var(--space-2)` | 1 | -| `group-heading-padding-inline-lg` | `size:lg` | `var(--space-4)` | 1 | -| `group-heading-fg` | `root` | `var(--color-content-muted)` | 1 | -| `group-heading-font-size` | `root` | `var(--font-size-xs)` | 1 | -| `group-heading-font-weight` | `root` | `var(--font-weight-medium)` | 1 | -| `group-heading-letter-spacing` | `root` | `var(--tracking-caps)` | 1 | -| `group-items-gap` | `root` | `1px` | 1 | -| `item-gap` | `root` | `var(--space-2)` | 1 | -| `item-padding-inline-md` | `size:md` | `var(--space-3)` | 1 | -| `item-padding-inline-sm` | `size:sm` | `var(--space-2)` | 1 | -| `item-padding-inline-lg` | `size:lg` | `var(--space-4)` | 1 | -| `item-fg` | `root` | `var(--color-content-primary)` | 1 | -| `item-font-size-md` | `size:md` | `var(--font-size-md)` | 1 | -| `item-font-size-sm` | `size:sm` | `var(--font-size-sm)` | 1 | -| `item-font-size-lg` | `size:lg` | `var(--font-size-lg)` | 1 | -| `item-line-height` | `root` | `var(--leading-ui)` | 1 | -| `item-icon-width` | `root` | `1.125em` | 1 | -| `item-icon-height` | `root` | `1.125em` | 1 | -| `item-icon-fg` | `root` | `var(--color-content-secondary)` | 1 | -| `item-shortcut-gap` | `root` | `var(--space-1)` | 1 | -| `item-shortcut-fg` | `root` | `var(--color-content-muted)` | 1 | -| `item-shortcut-font-size` | `root` | `0.875em` | 1 | -| `selected-item-fg` | `root` | `var(--color-content-primary)` | 1 | -| `empty-padding-block` | `root` | `var(--space-4)` | 1 | -| `empty-padding-inline-md` | `size:md` | `var(--space-3)` | 1 | -| `empty-padding-inline-sm` | `size:sm` | `var(--space-2)` | 1 | -| `empty-padding-inline-lg` | `size:lg` | `var(--space-4)` | 1 | -| `empty-fg` | `root` | `var(--color-content-muted)` | 1 | -| `empty-font-size-md` | `size:md` | `var(--font-size-md)` | 1 | -| `empty-font-size-sm` | `size:sm` | `var(--font-size-sm)` | 1 | -| `empty-font-size-lg` | `size:lg` | `var(--font-size-lg)` | 1 | -| `separator-height` | `root` | `var(--border-width)` | 1 | -| `separator-bg` | `root` | `var(--color-neutral-separator)` | 1 | - -### 4.2 Sin nombre mecánico (4) +| `radius` _(ya existe)_ | `root` | ⚠ `var(--command-radius)` / `var(--dialog-content-radius)` | 2 | + +### 4.2 Sin nombre mecánico (1) - **⚠ decisión: `100%` es un valor identidad o geometría de layout, no un knob de tema — el perímetro de «knob» es D-TH.2, sin firmar** — 1: `inline-size`. -- **⚠ decisión: `border` es shorthand o eje físico — hay que partirlo en ejes lógicos antes de nombrarlo (recipe-contract §1, R-4.4)** — 1: `border`. -- **⚠ 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** — 1: `background`. -- **⚠ 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`. -### 4.3 Avisos sobre los tokens propuestos (6) +### 4.3 Avisos sobre los tokens propuestos (1) - **el privado `--_command-radius` debe pasar a leer este público (o desaparecer)** — `--command-radius` -- **el privado `--_command-input-padding-inline` debe pasar a leer este público (o desaparecer)** — `--command-input-padding-inline-md`, `--command-input-padding-inline-sm`, `--command-input-padding-inline-lg` -- **el privado `--_command-input-font-size` debe pasar a leer este público (o desaparecer)** — `--command-input-font-size-md`, `--command-input-font-size-sm`, `--command-input-font-size-lg` -- **⚠ decisión: el valor es una expresión — el token puede llevar la expresión entera o sólo su término variable** — `--command-list-padding-inline` -- **el privado `--_command-item-padding-inline` debe pasar a leer este público (o desaparecer)** — `--command-group-heading-padding-inline-md`, `--command-group-heading-padding-inline-sm`, `--command-group-heading-padding-inline-lg`, `--command-item-padding-inline-md`, `--command-item-padding-inline-sm`, `--command-item-padding-inline-lg`, `--command-empty-padding-inline-md`, `--command-empty-padding-inline-sm`, `--command-empty-padding-inline-lg` -- **el privado `--_command-item-font-size` debe pasar a leer este público (o desaparecer)** — `--command-item-font-size-md`, `--command-item-font-size-sm`, `--command-item-font-size-lg`, `--command-empty-font-size-md`, `--command-empty-font-size-sm`, `--command-empty-font-size-lg` ### 4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4) diff --git a/src/uix/eidos/components/command/README.md b/src/uix/eidos/components/command/README.md index c34178d3a..f8080c7a1 100644 --- a/src/uix/eidos/components/command/README.md +++ b/src/uix/eidos/components/command/README.md @@ -56,16 +56,16 @@ navegación como parte de primera clase. ## Comparativa -| Capacidad | UIX | cmdk | Bits UI | kbar | shadcn | -| --- | --- | --- | --- | --- | --- | -| Filtrado con score custom + keywords | ✓ | ✓ | ✓ | ✓ | ✓ (cmdk) | -| Grupos con heading + separator | ✓ | ✓ | ✓ | ✓ | ✓ | -| `Empty` / `Loading` como partes | ✓ | ✓ | ✓ | No | ✓ | -| Ítem de navegación (`LinkItem` ``) | ✓ | No (genérico) | ✓ | No | No | -| `vimBindings` | ✓ | No | ✓ | No | No | -| Paletas en grid (`columns`) | ✓ | No | ✓ | No | No | -| Evento perceptivo del invoke | ✓ (`commit-submit-invoke`) | No | No | No | No | -| Overlay | Se compone `` | `Command.Dialog` | `Command.Dialog` | Portal propio | Dialog | +| Capacidad | UIX | cmdk | Bits UI | kbar | shadcn | +| ------------------------------------- | -------------------------- | ---------------- | ---------------- | ------------- | -------- | +| Filtrado con score custom + keywords | ✓ | ✓ | ✓ | ✓ | ✓ (cmdk) | +| Grupos con heading + separator | ✓ | ✓ | ✓ | ✓ | ✓ | +| `Empty` / `Loading` como partes | ✓ | ✓ | ✓ | No | ✓ | +| Ítem de navegación (`LinkItem` ``) | ✓ | No (genérico) | ✓ | No | No | +| `vimBindings` | ✓ | No | ✓ | No | No | +| Paletas en grid (`columns`) | ✓ | No | ✓ | No | No | +| Evento perceptivo del invoke | ✓ (`commit-submit-invoke`) | No | No | No | No | +| Overlay | Se compone `` | `Command.Dialog` | `Command.Dialog` | Portal propio | Dialog | Referencias: [cmdk](https://github.com/pacocoursey/cmdk) · [Bits UI Command](https://bits-ui.com/docs/components/command) · @@ -96,13 +96,45 @@ Referencias: [cmdk](https://github.com/pacocoursey/cmdk) · `data-list-surface` (tallas de ítem/indent coherentes con select/ combobox/listbox); squircle Tier A en la superficie. +## Talla y tema + +El contrato vive en `lib/recipes/base.ts > command` (57 claves). Alcance del +censo: **98 %** (era 0 % antes del 2026-08-20). + +**El eje `size`** (sm | md | lg, `data-size` en la raíz) sigue el patrón +firmado en `Sidebar`: coordenadas por talla en `root` +(`input-padding-inline-{k}`, `input-font-size-{k}`, `item-padding-inline-{k}`, +`item-font-size-{k}`) + un nombre **resuelto** (`input-padding-inline`, …) con +`declarations` — default `host` (= md) y overrides `size:sm` / `size:lg` — que +es lo ÚNICO que la receta lee. La cascada la emite el TSC; el CSS no lleva +bloques `[data-size]` propios. Las cuatro coordenadas casan **1:1 con el +bundle** (`--size-{k}-padding-inline` / `--size-{k}-font-size`), medido, así +que leen la coordenada del bundle y no un primitivo suelto. + +**El radio y el modo Dialog.** `--command-radius` es el radio de la superficie +del panel. Dentro de `` el panel que el usuario ve ES el del +dialog, así que la regla de contexto reapunta el propio público a +`var(--dialog-content-radius)`: leer el público de un hermano en el contexto +donde ese hermano ES la superficie es composición correcta, no un segundo +token. Por eso el componente no tiene privados. + +**Slots de tinta**: `fg` (raíz), `input-fg`, `input-placeholder-fg`, +`item-fg`, `item-icon-fg`, `item-shortcut-fg`, `group-heading-fg`, +`empty-fg`, `loading-fg`, `selected-item-sub-fg`. El modificador interactivo +va DELANTE (`focus-input-border`, `selected-item-sub-fg`), la talla detrás +(`input-font-size-md`) — gramática D-TH.6, guardada por R-5.3. + +`R-5.1 exception: inline-size: 100%` — el panel rellena su contenedor. Es +mecánica de layout, no apariencia: un tema que quisiera otro ancho mueve el +contenedor, no el panel. + ## Gaps -| Gap | Disposición | Detalle | -| --- | --- | --- | -| Filtrado asíncrono orquestado (debounce + cancelación integrados) | **diferir** | `search` controlado + `Loading` ya permiten async manual; orquestación integrada espera caso real. | -| Historial / ranking por frecuencia de uso | **descartar** | Política de producto, no del primitivo — el consumidor puede ordenar sus ítems. | -| Tests del wrapper eidos (soma con suite ✓) | **diferir** | Pasada SYS-2. | +| Gap | Disposición | Detalle | +| ----------------------------------------------------------------- | ------------- | -------------------------------------------------------------------------------------------------- | +| Filtrado asíncrono orquestado (debounce + cancelación integrados) | **diferir** | `search` controlado + `Loading` ya permiten async manual; orquestación integrada espera caso real. | +| Historial / ranking por frecuencia de uso | **descartar** | Política de producto, no del primitivo — el consumidor puede ordenar sus ítems. | +| Tests del wrapper eidos (soma con suite ✓) | **diferir** | Pasada SYS-2. | ## Referencias diff --git a/src/uix/eidos/components/command/command.css b/src/uix/eidos/components/command/command.css index dda45d815..0deac9714 100644 --- a/src/uix/eidos/components/command/command.css +++ b/src/uix/eidos/components/command/command.css @@ -22,22 +22,17 @@ */ [data-command] { - --_command-input-padding-inline: var(--space-3); - --_command-input-font-size: var(--font-size-md); - --_command-item-padding-inline: var(--space-3); - --_command-item-font-size: var(--font-size-md); /* Radius of the surface the palette lives in — the concentric reference for the input (which sits at the surface's top corners). Inline that surface is the command's own panel (radius-lg, matching the combobox popup). Inside it's overridden to the dialog panel's radius so the input corner stays parallel to the ACTUAL outer corner. */ - --_command-radius: var(--radius-lg); /* Concentric items: expose the radius + inline inset to the shared `[data-shape-nest]` rule. Outer = the command's list radius (`lg`, FIXED) so items stay concentric with the list region in BOTH the inline palette and inside . The items aren't at the dialog's corner (the input + list padding sit above them), so they must NOT inflate to the dialog radius; - only the input adapts to it via --_command-radius. gap = the list inline pad. */ + only the input adapts to it via --command-radius. gap = the list inline pad. */ --shape-outer-radius: var(--radius-lg); --shape-nest-gap: var(--space-1); @@ -51,14 +46,14 @@ ambient page-font inheritance — without this the rows fall back to the browser serif (Times New Roman). Self-contained typography keeps the portaled dialog identical to the inline palette. */ - font-family: var(--style-label-font-family); - line-height: var(--leading-ui); - color: var(--color-content-primary); + font-family: var(--command-font-family); + line-height: var(--command-line-height); + color: var(--command-fg); /* Elevation — surface · border · shadow — from the `overlay` plane via `data-depth` (A1). Inside the dialog panel is the surface, so the `[data-dialog-content] [data-command]` override below (higher specificity) strips the plane. Radius / overflow stay the panel's own. */ - border-radius: var(--_command-radius); + border-radius: var(--command-radius); overflow: hidden; outline: none; } @@ -71,7 +66,7 @@ [data-dialog-content] [data-command] { /* The visible surface is now the dialog panel — adopt ITS radius so the input stays concentric with the actual outer corner the user sees. */ - --_command-radius: var(--dialog-content-radius); + --command-radius: var(--dialog-content-radius); border: 0; border-radius: 0; background: transparent; @@ -98,19 +93,10 @@ top: var(--space-2); } -[data-command][data-size='sm'] { - --_command-input-padding-inline: var(--space-2); - --_command-input-font-size: var(--font-size-sm); - --_command-item-padding-inline: var(--space-2); - --_command-item-font-size: var(--font-size-sm); -} - -[data-command][data-size='lg'] { - --_command-input-padding-inline: var(--space-4); - --_command-input-font-size: var(--font-size-lg); - --_command-item-padding-inline: var(--space-4); - --_command-item-font-size: var(--font-size-lg); -} +/* The per-size cascade is emitted by the TSC from `recipes/base.ts` + (`declarations` with `scope: 'size:{k}'`), not written here: leaving these + blocks alive would keep painting the OLD values over the new tokens, and the + computed diff would read 0 because the old path still won. */ /* ── Input ──────────────────────────────────────────────────────────── */ @@ -121,16 +107,16 @@ box-sizing: border-box; /* Same density-driven block padding as the options (archetype) so the input row and the option rows share one vertical rhythm. */ - padding-block: var(--space-1-5); - margin: var(--space-2); - padding-inline: var(--_command-input-padding-inline); - border: var(--border-width) solid var(--color-border-default); - border-radius: var(--radius-md); - background: var(--color-surface-default); - color: var(--color-content-primary); - font-family: var(--style-label-font-family); - font-size: var(--_command-input-font-size); - line-height: var(--leading-ui); + padding-block: var(--command-input-padding-block); + margin: var(--command-input-margin); + padding-inline: var(--command-input-padding-inline); + border: var(--command-input-border-width) solid var(--command-input-border); + border-radius: var(--command-input-radius); + background-color: var(--command-input-bg); + color: var(--command-input-fg); + font-family: var(--command-font-family); + font-size: var(--command-input-font-size); + line-height: var(--command-line-height); outline: 0; transition: border-color var(--duration-fast) var(--ease-default), @@ -138,24 +124,24 @@ } [data-command-input]:focus-visible { - border-color: var(--color-primary-border); + border-color: var(--command-focus-input-border); outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: 0; } [data-command-input]::placeholder { - color: var(--color-content-muted); + color: var(--command-input-placeholder-fg); } /* ── Loading progress (slim bar under Input) ───────────────────────── */ [data-command-loading] { display: block; - block-size: 2px; + block-size: var(--command-loading-height); background: linear-gradient( to right, - var(--color-primary-solid) 0%, - var(--color-primary-solid) var(--_command-loading-progress, 0%), + var(--command-loading-fg) 0%, + var(--command-loading-fg) var(--_command-loading-progress, 0%), transparent var(--_command-loading-progress, 0%) ); } @@ -170,14 +156,14 @@ } [data-command-list] [data-scroll-area-viewport] { - --scroll-area-auto-max-block: min(60vh, 24rem); + --scroll-area-auto-max-block: var(--command-viewport-max-block); display: flex; flex-direction: column; - gap: var(--space-1); + gap: var(--command-viewport-gap); /* Inline inset matches the input's inline margin (space-2) so the option pills and the search box share ONE left/right edge. */ - padding-inline: var(--space-2); - padding-block-end: var(--space-1); + padding-inline: var(--command-viewport-padding-inline); + padding-block-end: var(--command-viewport-padding-block-end); overscroll-behavior: contain; } @@ -185,15 +171,17 @@ [data-command-list] [data-scroll-area]:has(> [data-scroll-area-scrollbar][data-orientation='vertical']) > [data-scroll-area-viewport] { - padding-inline-end: calc(var(--space-2) + var(--space-3-5)); + padding-inline-end: calc( + var(--command-viewport-padding-inline) + var(--command-scrollbar-inset) + ); } [data-command-viewport] { display: flex; flex-direction: column; - gap: var(--space-1); - max-block-size: min(60vh, 24rem); - padding: var(--space-1); + gap: var(--command-viewport-gap); + max-block-size: var(--command-viewport-max-block); + padding: var(--command-viewport-padding); overflow: auto; overscroll-behavior: contain; } @@ -202,22 +190,22 @@ [data-command-group] { display: grid; - gap: var(--space-1); + gap: var(--command-group-gap); } [data-command-group-heading] { - padding-block: var(--space-1); - padding-inline: var(--_command-item-padding-inline); - color: var(--color-content-muted); - font-size: var(--font-size-xs); - font-weight: var(--font-weight-medium); + padding-block: var(--command-group-heading-padding-block); + padding-inline: var(--command-item-padding-inline); + color: var(--command-group-heading-fg); + font-size: var(--command-group-heading-font-size); + font-weight: var(--command-group-heading-font-weight); text-transform: uppercase; - letter-spacing: var(--tracking-caps); + letter-spacing: var(--command-group-heading-tracking); } [data-command-group-items] { display: grid; - gap: 1px; + gap: var(--command-group-items-gap); } /* ── Item / LinkItem ──────────────────────────────────────────────── */ @@ -226,14 +214,14 @@ [data-command-link-item] { display: flex; align-items: center; - gap: var(--space-2); - padding-inline: var(--_command-item-padding-inline); + gap: var(--command-item-gap); + padding-inline: var(--command-item-padding-inline); /* Concentric radius from the shared `[data-shape-nest]` rule — the panel exposes --shape-outer-radius (its REAL radius, not radius-md) + --shape-nest-gap above. Height + block padding: the canonical archetype. */ - color: var(--color-content-primary); - font-size: var(--_command-item-font-size); - line-height: var(--leading-ui); + color: var(--command-item-fg); + font-size: var(--command-item-font-size); + line-height: var(--command-line-height); text-decoration: none; cursor: pointer; user-select: none; @@ -258,19 +246,19 @@ flex: 0 0 auto; align-items: center; justify-content: center; - inline-size: 1.125em; - block-size: 1.125em; - color: var(--color-content-secondary); + inline-size: var(--command-item-icon-size); + block-size: var(--command-item-icon-size); + color: var(--command-item-icon-fg); } [data-command-item-shortcut] { display: inline-flex; flex: 0 0 auto; align-items: center; - gap: var(--space-1); + gap: var(--command-item-shortcut-gap); margin-inline-start: auto; - color: var(--color-content-muted); - font-size: 0.875em; /* literal: shortcut hint proportional to the item context */ + color: var(--command-item-shortcut-fg); + font-size: var(--command-item-shortcut-font-size); } /* When the row is highlighted, lift the muted sub-parts to primary. */ @@ -278,23 +266,23 @@ [data-command-item][data-selected] [data-command-item-shortcut], [data-command-link-item][data-selected] [data-command-item-icon], [data-command-link-item][data-selected] [data-command-item-shortcut] { - color: var(--color-content-primary); + color: var(--command-selected-item-sub-fg); } /* ── Empty state ──────────────────────────────────────────────────── */ [data-command-empty] { - padding-block: var(--space-4); - padding-inline: var(--_command-item-padding-inline); - color: var(--color-content-muted); - font-size: var(--_command-item-font-size); + padding-block: var(--command-empty-padding-block); + padding-inline: var(--command-item-padding-inline); + color: var(--command-empty-fg); + font-size: var(--command-item-font-size); text-align: center; } /* ── Separator ────────────────────────────────────────────────────── */ [data-command-separator] { - block-size: var(--border-width); - margin-block: var(--space-1); - background: var(--color-neutral-separator); + block-size: var(--command-separator-height); + margin-block: var(--command-separator-margin-block); + background-color: var(--command-separator-bg); } diff --git a/src/uix/eidos/generated/base.css b/src/uix/eidos/generated/base.css index 0eb592df6..80761c6de 100644 --- a/src/uix/eidos/generated/base.css +++ b/src/uix/eidos/generated/base.css @@ -3737,6 +3737,59 @@ --combobox-selected-tag-remove-size: var(--space-4); --combobox-selected-tag-remove-margin-inline-start: var(--space-0-5); --combobox-selected-tag-remove-radius: var(--radius-sm); + --command-input-padding-inline-sm: var(--size-sm-padding-inline); + --command-input-padding-inline-md: var(--size-md-padding-inline); + --command-input-padding-inline-lg: var(--size-lg-padding-inline); + --command-input-font-size-sm: var(--size-sm-font-size); + --command-input-font-size-md: var(--size-md-font-size); + --command-input-font-size-lg: var(--size-lg-font-size); + --command-item-padding-inline-sm: var(--size-sm-padding-inline); + --command-item-padding-inline-md: var(--size-md-padding-inline); + --command-item-padding-inline-lg: var(--size-lg-padding-inline); + --command-item-font-size-sm: var(--size-sm-font-size); + --command-item-font-size-md: var(--size-md-font-size); + --command-item-font-size-lg: var(--size-lg-font-size); + --command-radius: var(--radius-lg); + --command-font-family: var(--style-label-font-family); + --command-line-height: var(--leading-ui); + --command-fg: var(--color-content-primary); + --command-input-padding-block: var(--space-1-5); + --command-input-margin: var(--space-2); + --command-input-border-width: var(--border-width); + --command-input-border: var(--color-border-default); + --command-input-radius: var(--radius-md); + --command-input-bg: var(--color-surface-default); + --command-input-fg: var(--color-content-primary); + --command-input-placeholder-fg: var(--color-content-muted); + --command-focus-input-border: var(--color-primary-border); + --command-loading-height: 2px; + --command-loading-fg: var(--color-primary-solid); + --command-viewport-max-block: min(60vh, 24rem); + --command-viewport-gap: var(--space-1); + --command-viewport-padding-inline: var(--space-2); + --command-viewport-padding-block-end: var(--space-1); + --command-scrollbar-inset: var(--space-3-5); + --command-viewport-padding: var(--space-1); + --command-group-gap: var(--space-1); + --command-group-heading-padding-block: var(--space-1); + --command-group-heading-fg: var(--color-content-muted); + --command-group-heading-font-size: var(--size-xs-font-size); + --command-group-heading-font-weight: var(--font-weight-medium); + --command-group-heading-tracking: var(--tracking-caps); + --command-group-items-gap: 1px; + --command-item-gap: var(--space-2); + --command-item-fg: var(--color-content-primary); + --command-item-icon-size: 1.125em; + --command-item-icon-fg: var(--color-content-secondary); + --command-item-shortcut-gap: var(--space-1); + --command-item-shortcut-fg: var(--color-content-muted); + --command-item-shortcut-font-size: 0.875em; + --command-selected-item-sub-fg: var(--color-content-primary); + --command-empty-padding-block: var(--space-4); + --command-empty-fg: var(--color-content-muted); + --command-separator-height: var(--border-width); + --command-separator-margin-block: var(--space-1); + --command-separator-bg: var(--color-neutral-separator); --drag-drop-preview-z: var(--z-index-overlay-detached); --navigation-menu-content-z: var(--z-index-overlay-inline); --navigation-menu-indicator-w: 0px; @@ -6160,6 +6213,27 @@ --_combobox-palette-text: var(--palette-text, var(--color-primary-text)); } +[data-command] { + --command-input-padding-inline: var(--command-input-padding-inline-md); + --command-input-font-size: var(--command-input-font-size-md); + --command-item-padding-inline: var(--command-item-padding-inline-md); + --command-item-font-size: var(--command-item-font-size-md); +} + +[data-command][data-size='sm'] { + --command-input-padding-inline: var(--command-input-padding-inline-sm); + --command-input-font-size: var(--command-input-font-size-sm); + --command-item-padding-inline: var(--command-item-padding-inline-sm); + --command-item-font-size: var(--command-item-font-size-sm); +} + +[data-command][data-size='lg'] { + --command-input-padding-inline: var(--command-input-padding-inline-lg); + --command-input-font-size: var(--command-input-font-size-lg); + --command-item-padding-inline: var(--command-item-padding-inline-lg); + --command-item-font-size: var(--command-item-font-size-lg); +} + [data-drag-drop-root] { --_drag-drop-palette-solid: var(--color-primary-solid); --_drag-drop-palette-element: var(--color-primary-element); diff --git a/src/uix/eidos/lib/recipes/base.ts b/src/uix/eidos/lib/recipes/base.ts index 4e55a8290..854f288ef 100644 --- a/src/uix/eidos/lib/recipes/base.ts +++ b/src/uix/eidos/lib/recipes/base.ts @@ -5616,6 +5616,121 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({ } }, + // Command palette. Size axis sm | md | lg on the root; the panel is the same + // surface inline and inside , so every knob lives here and + // the dialog only swaps the radius reference. + command: { + // Per-size coordinates - all four map 1:1 to the size bundle (measured + // 2026-08-20: space-2/3/4 and font-size sm/md/lg ARE the bundle's). + 'input-padding-inline-sm': 'var(--size-sm-padding-inline)', + 'input-padding-inline-md': 'var(--size-md-padding-inline)', + 'input-padding-inline-lg': 'var(--size-lg-padding-inline)', + 'input-font-size-sm': 'var(--size-sm-font-size)', + 'input-font-size-md': 'var(--size-md-font-size)', + 'input-font-size-lg': 'var(--size-lg-font-size)', + 'item-padding-inline-sm': 'var(--size-sm-padding-inline)', + 'item-padding-inline-md': 'var(--size-md-padding-inline)', + 'item-padding-inline-lg': 'var(--size-lg-padding-inline)', + 'item-font-size-sm': 'var(--size-sm-font-size)', + 'item-font-size-md': 'var(--size-md-font-size)', + 'item-font-size-lg': 'var(--size-lg-font-size)', + + // The RESOLVED names - the only ones the recipe reads. Default `host` + // (= md) plus the two overrides; the root stamps `data-size` and every + // consumer hangs from it, so no `parts` are needed. + 'input-padding-inline': { + declarations: [ + { value: 'var(--command-input-padding-inline-md)', scope: 'host' }, + { value: 'var(--command-input-padding-inline-sm)', scope: 'size:sm' }, + { value: 'var(--command-input-padding-inline-lg)', scope: 'size:lg' } + ] + }, + 'input-font-size': { + declarations: [ + { value: 'var(--command-input-font-size-md)', scope: 'host' }, + { value: 'var(--command-input-font-size-sm)', scope: 'size:sm' }, + { value: 'var(--command-input-font-size-lg)', scope: 'size:lg' } + ] + }, + 'item-padding-inline': { + declarations: [ + { value: 'var(--command-item-padding-inline-md)', scope: 'host' }, + { value: 'var(--command-item-padding-inline-sm)', scope: 'size:sm' }, + { value: 'var(--command-item-padding-inline-lg)', scope: 'size:lg' } + ] + }, + 'item-font-size': { + declarations: [ + { value: 'var(--command-item-font-size-md)', scope: 'host' }, + { value: 'var(--command-item-font-size-sm)', scope: 'size:sm' }, + { value: 'var(--command-item-font-size-lg)', scope: 'size:lg' } + ] + }, + + // Panel. `radius` is the palette's own surface radius; inside + // the recipe re-points that reference at the DIALOG's + // public radius - the panel the user sees there IS the dialog's, so + // reading a sibling's public token is the correct composition, not a + // second token of our own. + radius: 'var(--radius-lg)', + 'font-family': 'var(--style-label-font-family)', + 'line-height': 'var(--leading-ui)', + fg: 'var(--color-content-primary)', + + // Input - the boxed search field (same treatment as the Select trigger). + 'input-padding-block': 'var(--space-1-5)', + 'input-margin': 'var(--space-2)', + 'input-border-width': 'var(--border-width)', + 'input-border': 'var(--color-border-default)', + 'input-radius': 'var(--radius-md)', + 'input-bg': 'var(--color-surface-default)', + 'input-fg': 'var(--color-content-primary)', + 'input-placeholder-fg': 'var(--color-content-muted)', + 'focus-input-border': 'var(--color-primary-border)', + + // Loading bar under the input. + 'loading-height': '2px', + 'loading-fg': 'var(--color-primary-solid)', + + // List viewport. `scrollbar-inset` is the extra inline room the design + // scrollbar takes when it shows - the same name combobox uses, so the + // two list surfaces read alike. + 'viewport-max-block': 'min(60vh, 24rem)', + 'viewport-gap': 'var(--space-1)', + 'viewport-padding-inline': 'var(--space-2)', + 'viewport-padding-block-end': 'var(--space-1)', + 'scrollbar-inset': 'var(--space-3-5)', + 'viewport-padding': 'var(--space-1)', + + // Groups. + 'group-gap': 'var(--space-1)', + 'group-heading-padding-block': 'var(--space-1)', + 'group-heading-fg': 'var(--color-content-muted)', + 'group-heading-font-size': 'var(--size-xs-font-size)', + 'group-heading-font-weight': 'var(--font-weight-medium)', + 'group-heading-tracking': 'var(--tracking-caps)', + 'group-items-gap': '1px', + + // Rows. + 'item-gap': 'var(--space-2)', + 'item-fg': 'var(--color-content-primary)', + 'item-icon-size': '1.125em', + 'item-icon-fg': 'var(--color-content-secondary)', + 'item-shortcut-gap': 'var(--space-1)', + 'item-shortcut-fg': 'var(--color-content-muted)', + 'item-shortcut-font-size': '0.875em', + 'selected-item-sub-fg': 'var(--color-content-primary)', + + // Empty state. + 'empty-padding-block': 'var(--space-4)', + 'empty-fg': 'var(--color-content-muted)', + + // Separator. + 'separator-height': 'var(--border-width)', + 'separator-margin-block': 'var(--space-1)', + 'separator-bg': 'var(--color-neutral-separator)' + }, + // DragDrop — the drag preview/ghost that tracks the cursor. Floats above // local content during an active drag. 'drag-drop': {