# 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.language` alimenta `langs`. - `prefs.locale` alimenta `format`. - `prefs.direction` resuelve direccion efectiva. - `prefs.motion`, `prefs.sound` y `prefs.haptic` son preferencias transversales de percepcion/interaccion. - `createActivePrefsDomProjection(...)` proyecta solo `dir`, `data-motion`, `data-sound` y `data-haptic`. - `theme`, `mode` y `density` visuales pertenecen a `ActiveEidos`, no al preset core de `prefs`, `ActiveApp` ni `ActiveUix`. - No hay `themeDimension(...)` ni `densityDimension(...)` en el catalogo publico de prefs: si una app necesita dimensiones custom, usa las primitivas genericas (`enumDimension`, `stringDimension`, etc.) o una `PrefsDimension` propia. ## Composition Rule Solo los composition roots crean `ActivePrefs`: - `ActiveApp` crea o recibe `prefs`. - `createActiveUix(...)` crea `prefs` cuando UIX arranca standalone. - `attachActiveUix(app)` reutiliza `app.prefs`. Las capas consumidoras leen slots concretos o reciben vistas acotadas. No deben crear otra instancia compensatoria de preferencias. ```text 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: ```ts type PrefsSchema = Record>; ``` 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: ```text 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: ```ts 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: ```text language locale currency timezone unitSystem motion sound haptic direction ``` No incluye: ```text 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: ```ts 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: ```ts 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: ```ts const slot = readActivePrefsSlot(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: ```text 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: ```ts 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: ```text 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: ```ts 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: ```text 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: ```ts 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_dimension` - `prefs::intent_invalid` - `prefs::reserved_key` - `prefs::disposed` Ejemplo conocido: ```text 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 ```bash 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 ```