feat(eidos)!: un escalar es una instancia CLAVADA — mueren el throw de la puerta 2 y las fuentes por eje (c')
Cierra el estado INTERINO de e3c0899dd. `resolvePreferences` lanzaba con `uix` +
escalar y `ActiveEidos.create()` inyecta `uix` SIEMPRE, así que la regla no decía
«no mezcles dos motores»: decía «ningún escalar, nunca», y toda demo que clava un
panel en oscuro arrancaba con excepción. Forma firmada por el autor (c'):
- Pines SÍ: `theme` / `mode` / `density` / `scaling` en `ActiveEidosOptions` son
PINES — el eje queda clavado en la instancia y gana sobre la fuente, prefs
incluido (precedente en el mismo constructor: `options.dom ?? options.uix?.dom`).
Un eje clavado sigue suscrito: `apply()` corre y lo encuentra quieto.
- Sources por eje FUERA sin shim: `modeSource` / `densitySource` / `scalingSource`
eran la API del segundo motor. La puerta de sustitución ENTERA sigue siendo
`preferences`. Mueren `createComposedPreferenceSource`, `createStaticValueSource`
y `PREFERENCE_OPTION_KEYS`; nace `createStandalonePreferenceSource(dom)`:
sin primer motor no hay segundo — standalone sigue al SO EN VIVO para `mode`.
- Sin throw. UNA precedencia por UN envoltorio (`createPinnedPreferenceSource`)
sobre la puerta que responda (`preferences` · `uix.prefs` · standalone):
`pin ?? fuente ?? fallback` en las tres.
- Una función pura, dos lectores: `src/uix/eidos/lib/visual-preference.ts`
(`VisualPreferencePins` + `resolveVisualPreference(pin, value)`), importable por
el boot compilado como `lib/theme-id.ts`. `ActiveEidosOptions extends
VisualPreferencePins`; `UixBootParams.pins` lo toma entero;
`renderUixBootScript({ pins })` los embarca. DOS parámetros y no tres: el
fallback no es compartido (el boot resuelve siempre los cuatro ejes; en runtime
lo aporta cada fuente) — acta en changelog §60.
- Delta cero a CINCO casos: instancia clavada a dark con sobre en light (ni un
attr se mueve al hidratar) + familia clavada `acme` con modo de prefs
(`acme-dark` a los dos lados: el pin atraviesa `resolveThemeId`). Mutaciones
probadas: sin pin en el boot → 2 rojos; sin pin en el envoltorio → 2 rojos;
restauración por sha256.
- `contracts.test.ts`: retirado el guard «guards UIX docs shell from writing
visual prefs through ActivePrefs» — codificaba la doctrina REVOCADA el
2026-09-14 (escribir `theme` en prefs es el camino canónico); no se invierte:
un guard sobre un árbol congelado que se reconstruye no mide nada. Acta en §60.
- Coste en el árbol congelado: diez demos pasan `*Source` y pierden el tipo →
ledger `check-debt.ts` +22 (8 entradas nuevas, 2 subidas), cada una con causa
fechada (excepción firmada 2026-09-13). `src/` a CERO.
- Docs: eidos.md §preferencias · prefs README §Eidos Boundary · guide.md
(`pins`, tercer parámetro del boot) · changelog §60 · active-uix / overview /
active-architecture / blocks (snippets con `modeSource` al flujo canónico).
Verificación: vitest eidos+active-uix+prefs+contracts+value-channels 59/640 ·
check 95 = 73 + 22 exacto, src/ 0 · check:gate OK · docs:check 819 OK ·
generate:boot 15104 bytes con sync verde · adversarial Opus independiente
(informe en el handoff). Constructor + adversarial Opus 5; la sesión coordina.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
alpha-0.1-background
parent
9c465a8445
commit
f875f60b97
File diff suppressed because one or more lines are too long
@ -0,0 +1,18 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { resolveVisualPreference } from './visual-preference.ts';
|
||||
|
||||
describe('resolveVisualPreference', () => {
|
||||
it('lets a pinned axis win over what the source resolved', () => {
|
||||
expect(resolveVisualPreference('dark', 'light')).toBe('dark');
|
||||
});
|
||||
|
||||
it('keeps the source value when the axis is not pinned', () => {
|
||||
expect(resolveVisualPreference(undefined, 'light')).toBe('light');
|
||||
});
|
||||
|
||||
it('pins on DEFINEDNESS, not on truthiness', () => {
|
||||
// `||` would hand the source back here. A theme family is a plain
|
||||
// string and an empty one is still a caller's explicit answer.
|
||||
expect(resolveVisualPreference('', 'base')).toBe('');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,36 @@
|
||||
import type { Density } from '$libs/density';
|
||||
import type { ThemeEffective } from '$libs/theme';
|
||||
import type { ScalingKey } from './config-types';
|
||||
|
||||
/**
|
||||
* The four visual axes an instance may PIN. A pinned axis is a nailed
|
||||
* instance — a preview panel, a hero that is dark whatever the user
|
||||
* prefers — not a default and not a user preference.
|
||||
*
|
||||
* One declaration for two readers: `ActiveEidosOptions` spreads it as
|
||||
* its four scalars and `UixBootParams.pins` takes it whole, so the
|
||||
* pre-hydration script and the runtime cannot disagree about which axes
|
||||
* can be nailed.
|
||||
*/
|
||||
export interface VisualPreferencePins {
|
||||
readonly theme?: string;
|
||||
readonly mode?: ThemeEffective;
|
||||
readonly density?: Density;
|
||||
readonly scaling?: ScalingKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ONE precedence rule for a visual axis: a pin wins over whatever
|
||||
* the source resolved. End to end that reads `pin ?? source ?? fallback`
|
||||
* — the last step belongs to each source (the prefs adapter degrades to
|
||||
* eidos' defaults when a schema omits a dimension; the boot resolves
|
||||
* against the default schema, which never does), so what has to stay
|
||||
* identical between the two readers, and lives here, is this one.
|
||||
*
|
||||
* Pure and Svelte-free on purpose: `boot.ts` is compiled into the
|
||||
* pre-hydration bundle and may not reach a `.svelte.ts` module. Same
|
||||
* reason `resolveThemeId` lives next door.
|
||||
*/
|
||||
export function resolveVisualPreference<T>(pin: T | undefined, value: T): T {
|
||||
return pin ?? value;
|
||||
}
|
||||
Loading…
Reference in new issue