eidos(gradient-finish): override del dial por tema/modo - v1.x COMPLETA + capitulo del libro

- ThemeDefinition.gradientFinish.lift: el tema re-emite --gradient-finish-lift
  en su bloque (misma especificidad, despues en cascada -> el tema gana);
  claro y oscuro pueden llevar intensidades distintas, 0% apaga por tema.
  Test: active-eidos-config.test.ts ("per-theme gradient-finish dial override").
- Capitulo de nivel libro docs/theming/gradient-finish.md: registro de
  decisiones D1-D9 con el porque de cada una, las alternativas rechazadas con
  evidencia (color-value/variante/bg-prop; pasos 7/11 con valores reales;
  lift-up global medido y tumbado 52/84), la rectificacion documentada y la
  leccion de proceso (medir antes de fijar defaults). Indexado en docs/README.
- reference.md paragrafo 39 enlaza el capitulo; changelog paragrafo 42 y el plan
  marcan la v1.x completa (guard + Badge + override por tema).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
menubar-v4-safe
dev 3 months ago
parent 322a1939a8
commit a63d806b66

@ -116,9 +116,11 @@ por **un solo dial del tema**.
deuda preexistente del on-solid). Con la rampa anclada el guard pasa 3/3.
- ✅ **Badge** (2026-07-15): gate ampliado (paleta privada; sin `solid-hover` →
extremo profundo cae a `solid`); attr eidos-only de wrapper (NO morfo).
- **Override por tema/modo del lift**: hoy el token se emite una vez con las
primitivas; mover/duplicar la emisión al bloque de tema si un tema declara su
propio `gradientFinish`.
- ✅ **Override por tema/modo del lift** (2026-07-15):
`ThemeDefinition.gradientFinish.lift` → re-emisión en el bloque de tema
(misma especificidad, después en cascada → el tema gana; `0%` apaga por
tema). Test: `active-eidos-config.test.ts` («per-theme gradient-finish dial
override»). **v1.x COMPLETA.**
- **Composición del veil del state layer**: `background-image: veil, var(--_{c}-fill-finish, none)`
a nivel archetype — solo necesaria cuando un componente que DEPENDE del veil
(item/option) gane gradiente; Button/Badge no lo necesitan (su hover re-asserta).

@ -1623,8 +1623,13 @@ el emparejamiento base: seguridad constructiva, dial sin topes (0 regresiones
hasta 40%). Guard ejecutable: `gradient-finish-guard.test.ts` (no-regresión +
set flat-fail clavado: cyan/orange, deuda preexistente del on-solid). **Badge**
se suma al gate v1 (paleta privada; sin `solid-hover` → el extremo profundo
cae a `solid`). Cola v1.x restante: override por tema/modo del lift. Cola v2:
named finishes + `Surface` (aurora/mesh) + `data-on`.
cae a `solid`). **Override por tema/modo del lift** (mismo día):
`ThemeDefinition.gradientFinish.lift` re-emite el dial en el bloque de tema
(misma especificidad, después en la cascada → el tema gana; claro y oscuro
pueden llevar intensidades distintas, `0%` lo apaga por tema) — test en
`active-eidos-config.test.ts`. v1.x COMPLETA. Cola v2: named finishes +
`Surface` (aurora/mesh) + `data-on`. Capítulo del libro:
`docs/theming/gradient-finish.md` (registro de decisiones D1–D9).
---

@ -187,9 +187,13 @@ pasos?» panel, with the real theme values.
`primitives.gradientFinish.lift` (default `26%`) → emitted as the
`--gradient-finish-lift` token. One dial scales every ramp in the catalog;
`0%` degenerates the ramp to ≈flat (the dial is also the global off-switch).
Per-instance override is free via the custom-property cascade; the compound
`gradient-finish-*` prefix reserves the namespace against the named-gradient
open cage exactly like `--gradient-angle-*` does.
Per-instance override is free via the custom-property cascade; a **theme**
overrides it by declaring `gradientFinish.lift` in its `ThemeDefinition` —
the theme block re-emits the token at the same specificity, later in the
cascade, so light and dark can run different intensities (or `0%` to turn
the finish off per theme). The compound `gradient-finish-*` prefix reserves
the namespace against the named-gradient open cage exactly like
`--gradient-angle-*` does.
**Why.** This is the state-layer precedent (§38/§40) applied again: visual
magnitudes are **config data emitted by the generator**, never hand-written
@ -336,12 +340,12 @@ convention.
initiative (Card's solid variant needs it today, without gradients), with
known hard limits (nested components resolve their own tokens; portaled
content escapes ancestor inheritance).
- **Roadmap.** v1.x remaining: per-theme/mode override of the dial. v1.5:
the `spread` kind (constant-L hue rotation via relative color syntax —
APCA is luminance-driven, so contrast is ~preserved; needs real-browser
validation). v2: named finishes with authored, worst-stop-validated inks
(the rejected design A salvaged as an explicit, opt-in extension) +
`Surface` + `data-on`.
- **Roadmap.** v1.x: ✅ complete (guard · Badge · per-theme/mode dial
override, all 2026-07-15). v1.5: the `spread` kind (constant-L hue rotation
via relative color syntax — APCA is luminance-driven, so contrast is
~preserved; needs real-browser validation). v2: named finishes with
authored, worst-stop-validated inks (the rejected design A salvaged as an
explicit, opt-in extension) + `Surface` + `data-on`.
---

@ -1705,7 +1705,9 @@ Mechanics (v1, Button pilots — `GRADIENT_FINISH_COMPONENTS` gate in
- **ONE theme dial**: `--gradient-finish-lift` (default `26%`), emitted from
`primitives.gradientFinish.lift` — config data like the state-layer
magnitudes (§38). `0%` ≈ finish off; per-instance override is free via the
custom-property cascade. The invariant is pinned executable in
custom-property cascade, and a theme overrides it by declaring
`gradientFinish.lift` in its `ThemeDefinition` (re-emitted in the theme
block — light/dark can run different intensities). The invariant is pinned executable in
`src/uix/eidos/gradient-finish-guard.test.ts` (no-regression ≤ 40% + the
pre-existing flat-fail set — cyan/orange mid-tones, an on-solid reality —
must not grow).

@ -1494,6 +1494,19 @@ describe('ActiveEidos config', () => {
expect(css).toContain('--shadow-raised: var(--shadow-3);')
})
it('emits the per-theme gradient-finish dial override (§39)', () => {
// A theme that declares `gradientFinish.lift` re-emits the dial in its
// theme block — same specificity as the :root primitives default, later
// in the cascade, so the theme wins (e.g. a calmer ramp in dark).
const eidos = createThemeBaseEidos({
themes: { 'base-dark': { gradientFinish: { lift: '14%' } } }
})
expect(eidos.renderThemeCss('base-dark')).toContain('--gradient-finish-lift: 14%;')
// Without an override the theme block stays silent — the :root
// primitives default (26%) governs alone.
expect(eidos.renderThemeCss('base-light')).not.toContain('--gradient-finish-lift')
})
it('resolves the per-role contrast slot to on-solid by default and to an explicit step when overridden', () => {
const css = createThemeBaseEidos().renderThemeCss('base-light')

@ -1006,6 +1006,13 @@ export interface ThemeColorSet {
export interface ThemeDefinition {
readonly color?: ThemeColorSet;
readonly shadow?: ShadowScale;
/**
* Per-theme/mode override of the gradient-finish dial (§39). Emitted in
* the theme block — same specificity as the `:root` primitives default,
* later in the cascade, so the theme wins: a dark theme can run its own
* ramp intensity (`lift: '0%'` turns the finish off for that theme).
*/
readonly gradientFinish?: { readonly lift?: string };
}
export type ThemeMap = Record<string, ThemeDefinition>;

@ -679,6 +679,12 @@ export function renderThemeCss(
appendRecordDeclarations(declarations, 'shadow', theme.shadow)
}
if (theme.gradientFinish?.lift) {
// Per-theme/mode override of the finish dial (§39): same specificity as
// the `:root` primitives default, later in the cascade → the theme wins.
declarations.push(cssVar('gradient-finish-lift', theme.gradientFinish.lift))
}
return renderBlock(renderOptions.selector ?? getThemeSelector(themeId), declarations)
}

Loading…
Cancel
Save

Powered by TurnKey Linux.