From 509d1144ff414cc51f37a681d98af2e0c46e38d2 Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 23 Aug 2026 23:22:53 +0200 Subject: [PATCH] =?UTF-8?q?uix(calendar):=20temable=20=E2=80=94=2084=20%?= =?UTF-8?q?=20=E2=86=92=2089=20%,=20y=20una=20entrada=20de=20FAMILIA=20no?= =?UTF-8?q?=20se=20juzga=20en=20la=20ruta=20de=20UN=20componente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CERO claves acuñadas: el censo no tenía nada que coser. Su columna `global` ya era 0 desde la capa calendar-surface (2026-08-21), así que lo único fuera de alcance eran cuatro literales de IDENTIDAD. Todo el trabajo fue MEDIR, y ahí estaba el hallazgo. Qué entra: - Cuatro literales firmados con su razón (recipe-contract §3), fuera del ratio: el inline-size: max-content del panel y de la tabla (su anchura ES day-size × 7 más el espaciado — precedente progress), el min-inline-size: max-content del panel de mes y el inline-size: 100% de la cabecera. La propuesta §4 pedía acuñar width / month-panel-width / grid-width para ellos: sería dar a un tema la anchura de una caja cuya anchura es su contenido. - Ninguna línea de CSS nueva. Las 74 claves del contrato quedan como estaban. Censo: 84 % → 89 % (71 knobs, 57 públicos, 4 literales → 67 knobs, 57 públicos, 0 literales, 4 excepciones). Los 7 que quedan fuera son los forwards de paleta THM-2: no se acuñan. CERRADO en su 89 %. Global 68 %, no baja. Centinela R-5.4: 45/74 → 68/74, 6 adjudicadas. Verde (exit 0). Lo que enseñó: 1. Una entrada de receta de FAMILIA no se puede juzgar en la ruta de UN componente. Esta entrada es el vocabulario de las cuatro superficies de rejilla de fechas (lo dice lib/calendar-surface.css): range-calendar, month-grid y year-grid consumen --calendar-* y no acuñan nada — de ahí su 0 % HONESTO. Sus partes llevan data-month-grid-* / data-range-calendar-*, invisibles al filtro data-calendar: 23 de sus 29 «tokens muertos» eran eso. Con urls a las tres rutas + extraNodes + barrido de variant, 45/74 → 68/74. Los tres consumidores NO se han tocado y siguen en su 0 % honesto. 2. Y la propia demo tapa tres claves más: su cabecera monta SELECTORES de mes y año donde iría el heading, así que heading-{fg,font-size,font-weight} no tienen nodo aquí; los tiene month-grid. 3. HALLAZGO — el tamaño de letra de los selectores de mes / año es NO DETERMINISTA. La sonda dio 36 diffs contra un cambio que era SÓLO comentarios; corrida dos veces sobre el MISMO código dio 24, en los mismos dos nodos y las mismas dos propiedades. Causa medida: [data-calendar-month-select][data-button] (calendar-select.css, la regla que existe precisamente para fijar la letra del Button a la del calendario) y [data-popover-trigger]:not([data-archetype='field-trigger']) (popover.css) empatan a (0,2,0), así que gana el módulo que Vite inyecte el ÚLTIMO: cuatro cargas dieron 14 / 16 / 14 / 14 px, con el orden de las hojas invertido en la que dio 16. Es la clase del empate por orden de carga de tree-grid (§12); arreglarlo mueve píxel → next-features §13. Regla que confirma: ante un diff inesperado, corre la sonda dos veces sobre el mismo código ANTES de sospechar de tu cambio. 4. calendar.select-min-width no tiene nodo en NINGUNA página del repo: su único consumidor es [data-range-calendar-month-select] / -year-select, y la demo de range-calendar renderiza un heading de texto mientras DateRangePicker importa los selectores del CALENDARIO. Hueco de demo → §13. Diff de computed: sonda antes/después, 22.656 valores en 7 estados. Los 36 diffs son ÍNTEGRAMENTE el no-determinismo del punto 3 (misma corrida contra sí misma: 24) y caen en dos nodos y dos propiedades; fuera de ellos, 0. Capturas 2× antes/después: IDÉNTICAS byte a byte. Guards: censo --only calendar 89 % · component-audit PASS · eidos-lint 0 invalid · vitest src/uix/eidos sin rojos nuevos (los dos son ajenos: skin-media-player, el conocido, y un fantasma de radio-group de otra sesión) · rtl:check 0 · docs:check 0 · npm run check COMPLETED con 0 errores en ficheros de calendar · prettier limpio sobre el contenido normalizado a LF. README «Talla y tema» actualizado y pestaña Tokens en la demo (74 claves, verificada en navegador: 0 errores de consola). Co-Authored-By: Claude Opus 5 --- docs/audit/theming/calendar.md | 52 ++++++++++++++++++- docs/next-features.md | 27 ++++++++++ docs/process/PLAN-theming.md | 21 ++++++++ scripts/theming-sentinel-exceptions.ts | 18 +++++++ scripts/theming-sentinel.ts | 19 +++++++ src/uix/eidos/components/calendar/README.md | 32 +++++++++++- .../eidos/components/calendar/calendar.css | 8 +-- .../uix/components/calendar/+page.svelte | 10 +++- 8 files changed, 180 insertions(+), 7 deletions(-) diff --git a/docs/audit/theming/calendar.md b/docs/audit/theming/calendar.md index 4511cc927..2648a08cb 100644 --- a/docs/audit/theming/calendar.md +++ b/docs/audit/theming/calendar.md @@ -98,6 +98,56 @@ no distingue lo que debería — se marca `⚠`. -_(pendiente — lo escribe el autor; se conserva al regenerar)_ +**EJECUTADO 2026-08-24 — 84 % → 89 %.** **CERO claves acuñadas**: el censo no +tenía nada que coser (`global` ya era 0 tras la capa `calendar-surface` del +2026-08-21). Lo único fuera de alcance eran cuatro literales de IDENTIDAD, ahora +firmados. El trabajo real fue **MEDIR**: el guard R-5.4 pasó de **45/74 a +68/74** sin tocar una línea de CSS. + +**Los cuatro literales firmados** (§1.3 → §1.4, fuera del ratio): el +`inline-size: max-content` del panel y de la tabla (su anchura ES `day-size` × 7 +más el espaciado — precedente `progress`), el `min-inline-size: max-content` del +panel de mes y el `inline-size: 100%` de la cabecera. + +**Lo que queda fuera (7 privados, techo del componente):** los forwards de +paleta THM-2 (`--_calendar-palette-{solid,solid-hover,track,border,text}`) que +pintan el día seleccionado y el enfocado. No se acuñan. **CERRADO en su 89 %.** + +**La §4 generada proponía `width` / `month-panel-width` / `grid-width` para los +tres `max-content`**: sería acuñar una anchura para una caja cuya anchura es su +contenido, y un tema que la moviera rompería la rejilla. Se firman como +identidad, que es lo que son. + +### Lo que enseñó — una entrada de FAMILIA no se juzga en la ruta de UN componente + +1. **23 de sus 29 «tokens muertos» eran el instrumento.** Esta entrada de + `recipes/base.ts` es la vocabulario de la FAMILIA (lo dice + `lib/calendar-surface.css`): `month-grid`, `year-grid` y `range-calendar` + consumen `--calendar-*` y no acuñan nada — de ahí su 0 % HONESTO. Sus partes + llevan `data-month-grid-*` / `data-range-calendar-*`, invisibles al filtro + `data-calendar`. Con `urls` a las tres rutas de la familia + `extraNodes` + + barrido de `variant`: **45/74 → 68/74**. Ninguno de los tres consumidores se + tocó ni cambió de cifra. +2. **Y la propia demo tapa tres claves más**: su cabecera monta SELECTORES de mes + / año donde iría el `heading`, así que `heading-{fg,font-size,font-weight}` no + tienen nodo en esta ruta — los tiene `month-grid`. +3. **⚠ HALLAZGO: el tamaño de letra de los selectores de mes / año es NO + DETERMINISTA.** La sonda dio 36 diffs contra un cambio que era SÓLO + comentarios; corrida dos veces sobre el MISMO código dio 24. Causa medida: + `[data-calendar-month-select][data-button]` (calendar-select.css) y + `[data-popover-trigger]:not([data-archetype='field-trigger'])` (popover.css) + empatan a **(0,2,0)** y gana el módulo que Vite inyecte el ÚLTIMO — cuatro + cargas dieron 14px / 16px / 14px / 14px, con el orden de las hojas invertido + en la que dio 16. La regla que existe *precisamente* para fijar la letra del + Button a la del calendario pierde la mitad de las veces. Es la clase del + empate por orden de carga de `tree-grid` (§12); **arreglarlo mueve píxel** → + `next-features` §13. + **Regla que confirma**: ante un diff inesperado, corre la sonda dos veces + sobre el mismo código ANTES de sospechar de tu cambio. +4. **`select-min-width` no tiene nodo en NINGUNA página del repo**: su único + consumidor es `[data-range-calendar-month-select]` / `-year-select`, y ninguna + demo monta esas partes (la de range-calendar renderiza un heading de texto; el + DateRangePicker importa los selectores del CALENDARIO, que llevan + `data-calendar-month-select`). Hueco de demo, registrado. diff --git a/docs/next-features.md b/docs/next-features.md index 08236725c..e52a8aadc 100644 --- a/docs/next-features.md +++ b/docs/next-features.md @@ -1452,5 +1452,32 @@ Lo que `chart` destapó (2026-08-22): pinta nada en un panel que desborde. El arreglo (`flex: 0 0 auto` en `[data-select-separator]`) mueve píxel → fuera del eje de theming, pendiente. +- **Una entrada de receta de FAMILIA no se puede juzgar en la ruta de UN + componente** (medido 2026-08-24 en `calendar`). Su entrada de + `recipes/base.ts` es la de las cuatro superficies de rejilla de fechas — + `range-calendar`, `month-grid` y `year-grid` consumen `--calendar-*` y no + acuñan nada—, pero sus partes llevan `data-month-grid-*` / + `data-range-calendar-*` y el filtro `data-calendar` no las ve: **23 de sus 29 + «tokens muertos» eran eso**. Con `urls` a las tres rutas + `extraNodes` + + barrido de `variant`: 45/74 → 68/74. Vale para toda capa híbrida que conserve + entrada de receta. +- **El tamaño de letra de los selectores de mes / año del calendario es NO + DETERMINISTA** (medido 2026-08-24). `[data-calendar-month-select][data-button]` + —la regla de `calendar-select.css` que existe precisamente para fijar la letra + del Button a la del calendario— y + `[data-popover-trigger]:not([data-archetype='field-trigger'])` de `popover.css` + empatan a **(0,2,0)**, así que gana el módulo que Vite inyecte el ÚLTIMO: + cuatro cargas de la misma página dieron 14px / 16px / 14px / 14px, con el orden + de las hojas invertido en la que dio 16. Consecuencia de medición: la sonda dio + **24 diffs corriendo dos veces el MISMO código**, así que todo diff en esos dos + nodos es ruido hasta que se resuelva. Es la familia del empate por orden de + carga de §12 (tree-grid); arreglarlo mueve píxel. +- **`calendar.select-min-width` no tiene nodo en NINGUNA página del repo** + (medido 2026-08-24): su único consumidor es + `[data-range-calendar-month-select]` / `-year-select`, y la demo de + `range-calendar` renderiza un heading de texto en su lugar, mientras + `DateRangePicker` importa los selectores del CALENDARIO (que llevan + `data-calendar-month-select`). Hueco de DEMO: la parte existe en soma y en + eidos y ninguna superficie la monta. **Deps**: ninguna. Son mejoras del instrumental del eje, ejecutables cuando estorben. diff --git a/docs/process/PLAN-theming.md b/docs/process/PLAN-theming.md index a40d30953..f3f5f796c 100644 --- a/docs/process/PLAN-theming.md +++ b/docs/process/PLAN-theming.md @@ -486,6 +486,27 @@ commits de corrección. ## 8. Registro +- 2026-08-24 — **`calendar` 84 % → 89 %** (COMMIT), contrato **74 claves sin + tocar**, centinela **45/74 → 68/74** con 6 adjudicadas. **CERO claves + acuñadas: el censo no tenía nada que coser** (`global` ya era 0 desde la capa + `calendar-surface`), así que lo único fuera de alcance eran cuatro literales + de IDENTIDAD (`max-content` ×3 + `100%`), ahora firmados con su razón. **Todo + el trabajo fue MEDIR**, y enseñó la ley del bloque: **una entrada de receta de + FAMILIA no se juzga en la ruta de UN componente** — 23 de sus 29 «muertos» + eran el cromo que `month-grid` y `range-calendar` pintan con sus propios + atributos, invisible al filtro `data-calendar`; y la propia demo pone + SELECTORES de mes/año donde iría el `heading`, así que tres claves más no + tenían nodo aquí. Con `urls` a las tres rutas de la familia + `extraNodes` + + barrido de `variant`, sin tocar a los tres consumidores (siguen a 0 % honesto). + **Hallazgo de píxel**: el tamaño de letra de los selectores de mes / año es NO + DETERMINISTA — `[data-calendar-month-select][data-button]` y + `[data-popover-trigger]:not([data-archetype='field-trigger'])` empatan a + (0,2,0) y gana el módulo que Vite inyecte el último (cuatro cargas: + 14/16/14/14 px). Lo destapó la sonda dando **24 diffs corriendo dos veces el + MISMO código**; es la clase de `tree-grid` y va a §13. Sonda 22.656 valores × + 7 estados: **0 diffs fuera de esos dos nodos**; capturas 2× idénticas byte a + byte. Guards: censo `--only` 89 % (global 68 %, no baja) · audit PASS · + eidos-lint 0 invalid · rtl 0 · docs 0. - 2026-08-24 — **`select` 83 % → 92 %** (COMMIT), contrato 101 → **103** claves, centinela **69/103** con 34 adjudicadas. Dos claves de COSTURA: **`invalid-border`** (las TRES declaraciones del estado inválido leían diff --git a/scripts/theming-sentinel-exceptions.ts b/scripts/theming-sentinel-exceptions.ts index bc1929fa9..8deefdb94 100644 --- a/scripts/theming-sentinel-exceptions.ts +++ b/scripts/theming-sentinel-exceptions.ts @@ -1092,6 +1092,24 @@ export const SENTINEL_EXCEPTIONS: Record> = { 'the guard freezes transitions and this token IS the transition; measured unfrozen on the real trigger -> reaches (0.12s -> 11.5s on all four properties)', 'transition-ease': 'same freeze; measured unfrozen -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(3))' + }, + calendar: { + // 2026-08-24. This entry is the FAMILY's vocabulary (calendar-surface.css), + // so the guard measures it across calendar + month-grid + range-calendar; + // with those three routes 68 of its 74 keys move. These six are what the + // family cannot show, each measured on the real node. + 'invalid-border': + 'no calendar surface of the three routes boots invalid; forced [data-invalid] on the real host -> reaches (oklch(0.8059 0.1123 59.96) -> rgb(1, 2, 3))', + 'select-min-width': + 'its ONLY consumer is [data-range-calendar-month-select] / -year-select, and NO demo in the repo mounts those parts (0 nodes on the range-calendar route, which renders a text heading instead; DateRangePicker imports the CALENDAR selectors, so its nodes carry data-calendar-month-select). A part no page mounts has no node to paint — demo gap, registered in next-features §13', + 'range-start-solid-hover': + 'the hover pass stops at the first 30 nodes and the range endpoints sit past them on the range-calendar route; hovered the real [data-range-start] day -> reaches (oklch(0.6193 0.1094 181.3) -> rgb(1, 2, 3))', + 'range-end-solid-hover': + 'same limit on the other endpoint; hovered the real [data-range-end] day -> reaches (oklch(0.5246 0.1755 305.39) -> rgb(1, 2, 3))', + 'transition-duration': + 'the guard freezes transitions and this token IS the transition; measured unfrozen on a real day cell -> reaches (0.12s -> 11.5s on all five properties)', + 'transition-ease': + 'same freeze; measured unfrozen -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(3))' } }; diff --git a/scripts/theming-sentinel.ts b/scripts/theming-sentinel.ts index 6b5ad6ac5..417166590 100644 --- a/scripts/theming-sentinel.ts +++ b/scripts/theming-sentinel.ts @@ -463,6 +463,25 @@ const COMPONENT_OVERRIDES: Record< select: { extraNodes: '[data-floating-wrapper]:has(> [data-select-content]), [data-select-content] [data-scroll-area-viewport]' + }, + // The `calendar` recipe entry is the FAMILY's vocabulary, not this + // component's: `lib/calendar-surface.css` says so, and `month-grid`, + // `year-grid` and `range-calendar` consume `--calendar-*` while minting + // NOTHING of their own (0 % honest in the census). So a token of this entry + // lives if ANY surface of the family follows it — and their parts carry + // `data-month-grid-*` / `data-range-calendar-*`, invisible to the + // `data-calendar` filter. The header chrome (`control-*`, `heading-*`) has no + // node on THIS route either: the demo puts month / year SELECTORS where the + // text heading would be, and prev / next compose the system IconButton. + calendar: { + urls: [ + '/uix/components/calendar', + '/uix/components/month-grid', + '/uix/components/range-calendar' + ], + extraNodes: + '[data-month-grid], [data-month-grid] *, [data-range-calendar], [data-range-calendar] *', + sweepAttr: { attr: 'data-variant', values: ['surface', 'outline', 'ghost'] } } }; diff --git a/src/uix/eidos/components/calendar/README.md b/src/uix/eidos/components/calendar/README.md index c78ffc921..554e6aa52 100644 --- a/src/uix/eidos/components/calendar/README.md +++ b/src/uix/eidos/components/calendar/README.md @@ -144,7 +144,8 @@ Contrato en `lib/recipes/base.ts > calendar` — y esa entrada es la de la **FAMILIA**, no sólo la de este componente: `RangeCalendar`, `MonthGrid`, `YearGrid` y los calendarios de los dos pickers leen el mismo vocabulario `--calendar-*` (medido 2026-08-21: 114 / 77 / 77 referencias y CERO tokens -propios). Alcance del censo **84 %**. +propios). Alcance del censo **89 %** (2026-08-24); **74 claves públicas**, que +la pestaña **Tokens** de la demo resuelve en vivo. La resolución por talla vive en [`lib/calendar-surface.css`](../../lib/calendar-surface.css), enganchada al @@ -158,6 +159,35 @@ El anillo de evento y la marca de festivo también son de la capa: la FORMA allí, el ACENTO desde aquí (`--_calendar-surface-accent` ← el forward de paleta THM-2 de cada superficie). +Lo que entró el **2026-08-24** (84 % → 89 %), sin una línea de CSS nueva: **el +censo no tenía nada que acuñar** — cero knobs leían un primitivo global — así +que lo único fuera de alcance eran **cuatro literales de IDENTIDAD**, ahora +firmados con su razón (`recipe-contract` §3) y fuera del ratio: el +`inline-size: max-content` del panel y de la tabla (su anchura ES `day-size` × 7 +más el espaciado, no un knob), el `min-inline-size: max-content` del mes (nunca +se estrecha por debajo de su propia rejilla) y el `inline-size: 100%` de la +cabecera. Lo que queda fuera son los **siete forwards de paleta THM-2**, el +puente que no se acuña. + +**El trabajo real fue MEDIR.** El guard R-5.4 leía 45 de 74 claves porque +juzgaba la entrada de la FAMILIA sobre la ruta de UN componente: 23 de esas 29 +«muertas» eran el cromo que `month-grid` y `range-calendar` pintan con +`data-month-grid-*` / `data-range-calendar-*`, invisible al filtro +`data-calendar` — y la cabecera de esta demo pone SELECTORES de mes / año donde +iría el `heading`, así que sus tres claves tampoco tenían nodo aquí. Con las +tres rutas de la familia y el barrido de `variant`: **68 / 74**. Las seis +restantes van adjudicadas una a una en `theming-sentinel-exceptions.ts` +(inválido forzado, los dos hovers de extremo de rango, el par de transición — +que ES lo que el guard congela — y `select-min-width`, cuya parte **no la monta +ninguna demo del repo**: registrado en `docs/next-features.md` §13). + +⚠️ **El tamaño de letra de los selectores de mes / año es NO DETERMINISTA en +dev**: `[data-calendar-month-select][data-button]` (esta capa) y +`[data-popover-trigger]:not([data-archetype='field-trigger'])` (popover) empatan +a (0,2,0), así que gana el módulo que Vite inyecte el último — medido 2026-08-24 +en cuatro cargas: 14px / 16px / 14px / 14px. Registrado en §13 con las demás del +empate por orden de carga; arreglarlo mueve píxel. + ## Gaps | Gap | Disposición | Detalle | diff --git a/src/uix/eidos/components/calendar/calendar.css b/src/uix/eidos/components/calendar/calendar.css index 7dc81d494..0b7899073 100644 --- a/src/uix/eidos/components/calendar/calendar.css +++ b/src/uix/eidos/components/calendar/calendar.css @@ -9,7 +9,7 @@ display: inline-flex; flex-direction: column; gap: var(--calendar-gap); - inline-size: max-content; + inline-size: max-content; /* literal: identity — the panel is as wide as the grid it holds; its width is `day-size` × 7, not a knob */ max-inline-size: 100%; padding: var(--calendar-padding); border: var(--calendar-border-width) solid var(--calendar-border); @@ -44,7 +44,7 @@ grid-template-columns: var(--calendar-control-size) minmax(0, 1fr) var(--calendar-control-size); align-items: center; gap: var(--calendar-header-gap); - inline-size: 100%; + inline-size: 100%; /* literal: identity — the header spans the panel it heads */ min-inline-size: 0; } @@ -85,7 +85,7 @@ flex-direction: column; align-items: center; gap: var(--calendar-grid-gap); - min-inline-size: max-content; + min-inline-size: max-content; /* literal: identity — a month never narrows below its own grid */ } [data-calendar-month-panel] > [data-calendar-heading] { @@ -109,7 +109,7 @@ a [data-calendar] host, still get the styling when they reuse the selectors. */ [data-calendar-grid] { - inline-size: max-content; + inline-size: max-content; /* literal: identity — the table is seven `day-size` columns wide plus their spacing */ max-inline-size: 100%; margin-inline: auto; border-collapse: separate; diff --git a/web/routes/uix/components/calendar/+page.svelte b/web/routes/uix/components/calendar/+page.svelte index 5680f7c57..376e5d35b 100644 --- a/web/routes/uix/components/calendar/+page.svelte +++ b/web/routes/uix/components/calendar/+page.svelte @@ -11,8 +11,9 @@ import { calendarMorfo } from '@/uix/morfo/components/calendar'; import { getActiveUix } from '$active-uix'; import { CalendarDate, type DateValue, type WeekStartsOn } from '$libs/days'; + import TokensPanel from '../../lib/TokensPanel.svelte'; - type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; + type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens' | 'a11y'; type TraceEntry = { event: string; family: string; intent?: string; at: number }; type Direction = 'ltr' | 'rtl'; type WeekStartControl = 'locale' | '0' | '1'; @@ -587,6 +588,9 @@ + @@ -1016,6 +1020,10 @@ {/if} + {#if tab === 'tokens'} + + {/if} + {#if tab === 'recipe'}

Eidos recipe