Séptimo del eje theme-reach. Este componente es un híbrido de tres familias y el
contrato lo dice ahora explícitamente: la vista de década/mes es invención suya y
pasa a contrato público — separación, ancho, cabecera, navegación, rejilla y
celdas, con los estados del rango separados por nombre en vez de amontonados en
uno solo. Más el acento.
Lo que NO se toca tiene dueño y esperando firma: la mitad de calendario lee
`--calendar-*` y su corrección es la decisión de familia calendar que la
auditoría del sistema dejó registrada; la entrada segmentada lee `--field-*` y le
toca el mandato de composición de Field. Duplicar cualquiera de las dos bajo
prefijo propio habría sido justo el alias que la purga de 2026-07 mató.
Y el mismo defecto de prefijo que sus hermanos, aquí por partida doble: 44
referencias `--_date-field-*` y 9 `--_calendar-font-size` declaradas en ESTE CSS
con el nombre de otro componente. Con un DateField o un Calendar anidados se
habrían pisado. Renombradas, con el diff de computed en cero.
Verificación: diff de computed vacío sobre 493 valores en ocho estados ·
component:audit PASS · suite eidos sin rojos nuevos · rtl:check 0 · docs:check 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
## 3. Privados de la receta — ¿de dónde sale su valor?
_La receta no declara privados propios en su CSS._
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
| --- | ---: | --- | --- | :-: |
| `--_date-range-picker-field-height` | 4 | `var(--field-control-height-md)`, `var(--field-control-height-xs)`, `var(--field-control-height-sm)`, `var(--field-control-height-lg)` | global | no |
| `--_date-range-picker-field-gap` | 4 | `var(--space-1)`, `var(--space-0-5)`, `var(--space-1-5)` | global | no |
| `--_date-range-picker-field-font-size` | 4 | `var(--field-control-font-size-md)`, `var(--field-control-font-size-xs)`, `var(--field-control-font-size-sm)`, `var(--field-control-font-size-lg)` | global | no |
| `--_date-range-picker-field-accent-border` | 8 | `var(--date-range-picker-accent-border)`, `var(--color-secondary-border)`, `var(--color-neutral-border)`, `var(--color-affirm-border)`, `var(--color-fulfill-border)`, `var(--color-risk-border)` …(+2) | global, public | no |
| `--_date-range-picker-field-accent-track` | 8 | `var(--date-range-picker-accent-track)`, `var(--color-secondary-track)`, `var(--color-neutral-track)`, `var(--color-affirm-track)`, `var(--color-fulfill-track)`, `var(--color-risk-track)` …(+2) | global, public | no |
| `--_date-range-picker-field-accent-text` | 8 | `var(--date-range-picker-accent-text)`, `var(--color-secondary-text)`, `var(--color-neutral-text)`, `var(--color-affirm-text)`, `var(--color-fulfill-text)`, `var(--color-risk-text)` …(+2) | global, public | no |
| `--_date-range-picker-calendar-font-size` | 8 | `var(--calendar-font-size-md)`, `var(--calendar-font-size-xs)`, `var(--calendar-font-size-sm)`, `var(--calendar-font-size-lg)` | global | no |
## 4. Propuesta de corrección
@ -99,7 +86,7 @@ _La receta no declara privados propios en su CSS._
- **Consume tokens públicos de `calendar`, `field`.** Un token prestado importa la semántica de su dueño: la corrección no es duplicarlo con prefijo propio, sino la decisión de familia que la auditoría de fase 1 dejó registrada (`theming-audit.md` §B, familia calendar).
- **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` (38)
### 4.1 Tokens a declarar en `lib/recipes/base.ts` (37)
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
@ -109,44 +96,43 @@ no distingue lo que debería — se marca `⚠`.
@ -154,9 +140,16 @@ no distingue lo que debería — se marca `⚠`.
- **⚠ 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)** — 3: `border`.
### 4.3 Avisos sobre los tokens propuestos (1)
### 4.3 Avisos sobre los tokens propuestos (8)
- **⚠ decisión: el valor es una expresión — el token puede llevar la expresión entera o sólo su término variable** — `--date-range-picker-gap`, `--date-range-picker-year-view-width`
- **⚠ decisión: el valor es una expresión — el token puede llevar la expresión entera o sólo su término variable** — `--date-range-picker-gap`
- **el privado `--_date-range-picker-field-gap` debe pasar a leer este público (o desaparecer)** — `--date-range-picker-gap-md`, `--date-range-picker-gap-xs`, `--date-range-picker-gap-sm`, `--date-range-picker-gap-lg`
- **el privado `--_date-range-picker-field-font-size` debe pasar a leer este público (o desaparecer)** — `--date-range-picker-font-size-md`, `--date-range-picker-font-size-xs`, `--date-range-picker-font-size-sm`, `--date-range-picker-font-size-lg`
- **el privado `--_date-range-picker-field-height` debe pasar a leer este público (o desaparecer)** — `--date-range-picker-trigger-width-md`, `--date-range-picker-trigger-width-xs`, `--date-range-picker-trigger-width-sm`, `--date-range-picker-trigger-width-lg`, `--date-range-picker-trigger-height-md`, `--date-range-picker-trigger-height-xs`, `--date-range-picker-trigger-height-sm`, `--date-range-picker-trigger-height-lg`
- **el privado `--_date-range-picker-field-accent-border` debe pasar a leer este público (o desaparecer)** — `--date-range-picker-hover-trigger-border`, `--date-range-picker-trigger-border`
- **el privado `--_date-range-picker-field-accent-track` debe pasar a leer este público (o desaparecer)** — `--date-range-picker-hover-trigger-bg`
- **el privado `--_date-range-picker-field-accent-text` debe pasar a leer este público (o desaparecer)** — `--date-range-picker-hover-trigger-fg`
- **el privado `--_date-range-picker-calendar-font-size` debe pasar a leer este público (o desaparecer)** — `--date-range-picker-calendar-font-size-md`, `--date-range-picker-calendar-font-size-xs`, `--date-range-picker-calendar-font-size-sm`, `--date-range-picker-calendar-font-size-lg`
### 4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4)
@ -210,4 +203,28 @@ hereda los defectos de ambas:
field-composition · D-TH.4 (B7). Sin esas dos firmas, aquí sólo procede el
renombrado de privados + `year-view-*`.
---
**EJECUTADO 2026-08-20 (exactamente lo que no dependía de otra firma).** Alcance
`commit-select-range`, `commit-reset`, `shift-navigate`) los emite
`range-calendar`; el picker no los duplica.
## Talla y tema (theme-reach 2026-08-20)
El componente es un híbrido de tres familias, y el contrato lo refleja:
- **Suyo** — la vista de década / mes (`overview-*`: separación, ancho, cabecera,
navegación, rejilla y celdas, con `in-range-` y `endpoint-` para los estados
del rango) y el acento (`accent-border` / `-track` / `-text`). Eso es lo que
pasa a contrato público `--date-range-picker-*`.
- **Del calendario** — la mitad de arriba lee `--calendar-*`. No se duplica: la
corrección es la decisión de familia calendar registrada en
`docs/audit/theming-audit.md` §B (formalizar `calendar` como capa compartida),
que sigue sin firmar.
- **Del campo** — la entrada segmentada lee `--field-*`. Igual: el mandato de
composición de Field (§5.3-3).
Corregido de paso: 44 referencias `--_date-field-*` y 9 `--_calendar-font-size`
que este CSS declaraba con el prefijo de OTRO componente (y que se cruzarían con
un DateField o un Calendar anidados) pasan a `--_date-range-picker-*`, con el
diff de computed en cero.
## Gaps
| ID | Disposición | Detalle |
| --- | --- | --- |
| Demo: `start segments` / `end segments` / `paged nav` | **diferir** | Mover a sección API-only del README; quitar de los controles interactivos del demo si no mejoran el preview. |
| `data-disabled-reason` en celdas para min/maxDays | **diferir** | El cell ya emite `data-disabled`; el "porqué" (out-of-bounds vs range-length) puede añadirse vía `data-disabled-reason` para que el screen reader explique. No bloqueante. |
| Tests browser-level del flujo completo | **implementar** | Falta cobertura visual (Playwright) del flujo abrir → seleccionar start → seleccionar end → commit/dismiss. |
| El botón `Clear` es MUDO | **implementar** | El morfo declara `commit-reset`, pero `clear()` en el provider sólo hace `value.current = undefined` — nunca llama a `runtime.trigger('commit-reset')`. Afecta por igual a date-picker, time-picker, time-range-picker y color-picker: o se cablea el trigger en los cinco, o el evento sale del morfo. Medido 2026-08-11. |
| Demo: `start segments` / `end segments` / `paged nav` | **diferir**| Mover a sección API-only del README; quitar de los controles interactivos del demo si no mejoran el preview. |
| `data-disabled-reason` en celdas para min/maxDays | **diferir** | El cell ya emite `data-disabled`; el "porqué" (out-of-bounds vs range-length) puede añadirse vía `data-disabled-reason` para que el screen reader explique. No bloqueante. |
| Tests browser-level del flujo completo | **implementar** | Falta cobertura visual (Playwright) del flujo abrir → seleccionar start → seleccionar end → commit/dismiss. |
| El botón `Clear` es MUDO | **implementar** | El morfo declara `commit-reset`, pero `clear()` en el provider sólo hace `value.current = undefined` — nunca llama a `runtime.trigger('commit-reset')`. Afecta por igual a date-picker, time-picker, time-range-picker y color-picker: o se cablea el trigger en los cinco, o el evento sale del morfo. Medido 2026-08-11. |