6.8 KiB
Prefs
prefs es el artefacto activo de preferencias. Su trabajo es resolver, de
forma generica, la relacion entre intencion de usuario, entorno detectado y
valor efectivo para un esquema declarado por la app.
No traduce, no formatea, no persiste por si mismo y no escribe el DOM salvo
cuando el composition root cablea explicitamente createActivePrefsDomProjection(...).
Estado 2026-05-14
Decisiones vigentes:
prefs.languagealimentalangs.prefs.localealimentaformat.prefs.directionresuelve direccion efectiva.prefs.motion,prefs.soundyprefs.hapticson preferencias transversales de percepcion/interaccion.createActivePrefsDomProjection(...)proyecta solodir,data-motion,data-soundydata-haptic.theme,modeydensityvisuales pertenecen aActiveEidos, no al preset core deprefs,ActiveAppniActiveUix.- No hay
themeDimension(...)nidensityDimension(...)en el catalogo publico de prefs: si una app necesita dimensiones custom, usa las primitivas genericas (enumDimension,stringDimension, etc.) o unaPrefsDimensionpropia.
Composition Rule
Solo los composition roots crean ActivePrefs:
ActiveAppcrea o recibeprefs.createActiveUix(...)creaprefscuando UIX arranca standalone.attachActiveUix(app)reutilizaapp.prefs.
Las capas consumidoras leen slots concretos o reciben vistas acotadas. No deben crear otra instancia compensatoria de preferencias.
ActiveApp/createActiveUix -> ActivePrefs
langs -> prefs.language
format -> prefs.locale, currency, timezone, unitSystem
ActivePrefsDomProjection -> direction, motion, sound, haptic
ActiveEidos -> theme/mode/density visuales propios
Schema Model
La implementacion actual es schema-based:
type PrefsSchema = Record<string, PrefsDimension<TIntent, TEffective>>;
Cada dimension declara:
defaultValue: valor de fallback.validate(value): valida intencion de usuario.resolve(intent, env): opcional; convierte intencion + entorno en valor efectivo.catalog(): opcional; lista de valores seleccionables.
El motor mantiene tres planos:
intent = lo que el usuario eligio explicitamente
environment = lo que servidor/browser/sistema sugieren
effective = valor total que leen los consumidores
Solo intent se persiste. environment se recalcula y effective se deriva.
Standard Preset
standardPrefsDimensions(catalog) compone el preset transversal:
const schema = {
...standardPrefsDimensions({
languages: ['es', 'en'],
locales: ['es-ES', 'en-US'],
currencies: ['EUR', 'USD'],
defaults: {
language: 'es',
locale: 'es-ES',
currency: 'EUR'
}
}),
sidebarCollapsed: booleanDimension({ default: false })
};
Incluye:
language
locale
currency
timezone
unitSystem
motion
sound
haptic
direction
No incluye:
theme
mode
density
Esos valores son visuales en UIX. Una shell debe pasarlos a ActiveEidos
mediante theme, modeSource y densitySource.
Active Surface
createActivePrefs({ schema }) devuelve una superficie reactiva con un slot
por dimension:
const prefs = createActivePrefs({ schema });
prefs.locale.get();
prefs.locale.set('en-US');
prefs.locale.clear();
prefs.locale.onChange((locale) => {});
prefs.locale.catalog();
Tambien expone metodos genericos para adaptadores:
prefs.setIntent('locale', 'en-US');
prefs.clearIntent('locale');
prefs.resetIntent();
prefs.patchEnvironment({ reducedMotion: true });
prefs.refreshEnvironment(nextEnvironment);
prefs.subscribe((event) => {});
prefs.dispose();
Los servicios que reciben un ActivePrefs abierto y no conocen su schema en
tiempo de compilacion deben leer defensivamente:
const slot = readActivePrefsSlot<Locale>(prefs, 'locale');
const locale = slot?.get();
Si el slot no existe, el consumidor decide si puede degradar o debe lanzar su propio error de configuracion.
Environment
El entorno entra por adaptadores. Ninguna dimension lee window, cookies,
headers, localStorage o DOM directamente.
Adaptadores disponibles:
detectServerEnvironment(input)detectBrowserEnvironment(overrides?)applyBrowserEnvironment(prefs, overrides?)watchBrowserEnvironment(prefs, overrides?)
Ejemplos de entorno:
Accept-Language -> language/locale candidates
Intl timezone -> timezone
matchMedia -> reducedMotion/colorScheme
navigator -> languages, reduced sound/haptics when available
colorScheme puede existir en el entorno porque el browser lo expone, pero
UIX no lo convierte en prefs.theme; ActiveEidos puede leer el sistema por
su propia modeSource.
DOM Projection
ActivePrefs no escribe el DOM por si mismo. Si la app quiere atributos
globales, cablea el proyector:
const prefsProjection = createActivePrefsDomProjection({
prefs: App.prefs,
dom: App.dom
});
El proyector es idempotente, se suscribe a los slots disponibles y limpia los
atributos que gestiono en dispose().
Contrato de atributos:
prefs.direction -> dir
prefs.motion -> data-motion
prefs.sound -> data-sound
prefs.haptic -> data-haptic
No proyecta data-theme, data-mode ni data-density.
Eidos Boundary
Para una shell visual:
const uix = createActiveUix({ langs, prefs: { schema } });
const prefsProjection = createActivePrefsDomProjection({
prefs: uix.prefs,
dom: uix.dom
});
const eidos = ActiveEidos.create({
theme: 'base',
modeSource,
densitySource,
applyDom: true
});
Regla practica:
NO: uix.prefs.setIntent('theme', 'dark')
SI: modeSource notifica 'dark' a ActiveEidos
Si una app no UIX decide declarar una dimension visual propia en prefs, es
un contrato local de esa app. No debe filtrarse a ActiveUix, Soma, Sema ni
Morfo.
Storage
createPrefsStorageBridge(...) persiste intenciones, no valores efectivos:
const bridge = createPrefsStorageBridge({
prefs,
storage,
key: 'active:prefs'
});
Reglas:
- Persistir solo
intent. - No persistir
environment. - No persistir
effective. - No escribir durante hydrate salvo configuracion explicita.
- Un fallo de storage no debe corromper preferencias en memoria.
Errors
Los errores publicos usan la familia prefs::*:
prefs::unknown_dimensionprefs::intent_invalidprefs::reserved_keyprefs::disposed
Ejemplo conocido:
prefs::unknown_dimension: [prefs] no such dimension in schema: theme
En UIX ese error normalmente significa que una shell intento escribir
prefs.theme. La correccion es pasar el modo visual a ActiveEidos.
Tests
npx vitest run src/arts/prefs/test/engine-prefs.test.ts
npx vitest run src/arts/prefs/test/active-prefs.svelte.test.ts
npx vitest run src/arts/prefs/test/dom-projection.test.ts