@ -1,78 +1,80 @@
# 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.
`prefs` is the active preferences artifact. Its job is to resolve, generically,
the relationship between user intent, detected environment and effective value
for a schema declared by the app.
It does not translate, does not format, does not persist by itself, and does not
write the DOM except when the composition root explicitly wires
`createActivePrefsDomProjection(...)` .
## Status 2026-05-14
Current decisions:
- `prefs.language` feeds `langs` .
- `prefs.locale` feeds `format` .
- `prefs.direction` resolves the effective direction.
- `prefs.motion` , `prefs.sound` and `prefs.haptic` are cross-cutting
perception/interaction preferences.
- `createActivePrefsDomProjection(...)` projects only `dir` , `data-motion` ,
`data-sound` and `data-haptic` .
- Visual `theme` , `mode` and `density` belong to `ActiveEidos` , not to the core
preset of `prefs` , `ActiveApp` or `ActiveUix` .
- There is no `themeDimension(...)` or `densityDimension(...)` in the public
prefs catalog: if an app needs custom dimensions, it uses the generic
primitives (`enumDimension`, `stringDimension` , etc.) or its own
`PrefsDimension` .
## Composition Rule
Solo los composition roots crean `ActivePrefs` :
Only composition roots create `ActivePrefs` :
- `ActiveApp` crea o recibe `prefs` .
- `createActiveUix(...)` crea `prefs` cuando UIX arranca standalone.
- `attachActiveUix(app)` reutiliza `app.prefs` .
- `ActiveApp` creates or receives `prefs` .
- `createActiveUix(...)` creates `prefs` when UIX boots standalone.
- `attachActiveUix(app)` reuses `app.prefs` .
Las capas consumidoras leen slots concretos o reciben vistas acotadas. No
deben crear otra instancia compensatoria de preferencias .
Consuming layers read specific slots or receive scoped views. They must not
create another compensatory preferences instance .
```text
ActiveApp/createActiveUix -> ActivePrefs
langs -> prefs.language
format -> prefs.locale, currency, timezone, unitSystem
ActivePrefsDomProjection -> direction, motion, sound, haptic
ActiveEidos -> theme/mode/density visuales propios
ActiveEidos -> its own visual theme/mode/density
```
## Schema Model
La implementacion actual e s schema-based:
The current implementation i s schema-based:
```ts
type PrefsSchema = Record< string , PrefsDimension < TIntent , TEffective > >;
```
Cada dimension declara :
Each dimension declares :
- `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 seleccionabl es.
- `defaultValue` : fallback value .
- `validate(value)` : validates user intent .
- `resolve(intent, env)` : optional; turns intent + environment into the
effective value .
- `catalog()` : optional; list of selectable valu es.
El motor mantiene tres plano s:
The engine keeps three plane s:
```text
intent = lo que el usuario eligio explicitament e
environment = lo que servidor/browser/sistema sugieren
effective = valor total que leen los consumidores
intent = what the user explicitly chos e
environment = what the server/browser/system suggest
effective = the final value consumers read
```
Solo `intent` se persiste. `environment` se recalcula y `effective` se deriva.
Only `intent` is persisted. `environment` is recomputed and `effective` is
derived.
## Standard Preset
`standardPrefsDimensions(catalog)` compone el preset transversal :
`standardPrefsDimensions(catalog)` composes the cross-cutting preset :
```ts
const schema = {
@ -90,7 +92,7 @@ const schema = {
};
```
Incluye :
Includes :
```text
language
@ -104,7 +106,7 @@ haptic
direction
```
No incluy e:
Does not includ e:
```text
theme
@ -112,13 +114,13 @@ mode
density
```
Esos valores son visuales en UIX. Una shell debe pasarlos a `ActiveEidos`
mediante `theme` , `modeSource` y `densitySource` .
Those values are visual in UIX. A shell must pass them to `ActiveEidos` via
`theme` , `modeSource` and `densitySource` .
## Active Surface
`createActivePrefs({ schema })` devuelve una superficie reactiva con un slot
por dimension:
`createActivePrefs({ schema })` returns a reactive surface with one slot per
dimension:
```ts
const prefs = createActivePrefs({ schema });
@ -130,7 +132,7 @@ prefs.locale.onChange((locale) => {});
prefs.locale.catalog();
```
Tambien expone metodos genericos para adaptadore s:
It also exposes generic methods for adapter s:
```ts
prefs.setIntent('locale', 'en-US');
@ -142,30 +144,31 @@ prefs.subscribe((event) => {});
prefs.dispose();
```
Los servicios que reciben un `ActivePrefs` abierto y no conocen su schema en
tiempo de compilacion deben leer defensivamente :
Services that receive an open `ActivePrefs` and do not know its schema at
compile time should read defensively :
```ts
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 .
If the slot does not exist, the consumer decides whether it can degrade or must
throw its own configuration error .
## Environment
El entorno entra por adaptadores. Ninguna dimension lee `window` , cookies,
headers, `localStorage` o DOM directamente .
The environment comes in through adapters. No dimension reads `window` , cookies,
headers, `localStorage` or the DOM directly .
Adaptadores disponible s:
Available adapter s:
- `detectServerEnvironment(input)`
- `detectBrowserEnvironment(overrides?)`
- `applyBrowserEnvironment(engine, overrides?)`
- `watchBrowserEnvironment(apply, overrides?)` — `apply` recibe el patch de entorno (`(patch) => void`); `overrides` solo `{ matchMedia }`
- `watchBrowserEnvironment(apply, overrides?)` — `apply` receives the environment
patch (`(patch) => void`); `overrides` is only `{ matchMedia }`
Ejemplos de entorno :
Environment examples :
```text
Accept-Language -> language/locale candidates
@ -174,14 +177,14 @@ 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` .
`colorScheme` can exist in the environment because the browser exposes it, but
UIX does not turn it into `prefs.theme` ; `ActiveEidos` can read the system
through its own `modeSource` .
## DOM Projection
`ActivePrefs` no escribe el DOM por si mismo. Si la app quiere atributos
globales, cablea el proy ector:
`ActivePrefs` does not write the DOM by itself. If the app wants global
attributes, it wires the proj ector:
```ts
const prefsProjection = createActivePrefsDomProjection({
@ -190,10 +193,10 @@ const prefsProjection = createActivePrefsDomProjection({
});
```
El proyector es idempotente, se suscribe a los slots disponibles y limpia los
atributos que gestiono e n `dispose()` .
The projector is idempotent, subscribes to the available slots and clears the
attributes it manages o n `dispose()` .
Contrato de atributos :
Attribute contract :
```text
prefs.direction -> dir
@ -202,11 +205,11 @@ prefs.sound -> data-sound
prefs.haptic -> data-haptic
```
No proyecta `data-theme` , `data-mode` ni `data-density` .
It does not project `data-theme` , `data-mode` or `data-density` .
## Eidos Boundary
Para una shell visua l:
For a visual shel l:
```ts
const uix = createActiveUix({ langs, prefs: { schema } });
@ -223,55 +226,55 @@ const eidos = ActiveEidos.create({
});
```
Regla practica :
Practical rule :
```text
NO: uix.prefs.setIntent('theme', 'dark')
SI: modeSource notifica 'dark' a ActiveEidos
YES: modeSource notifies 'dark' to ActiveEidos
```
Si una app no UIX decide declarar una dimension visual propia en `prefs` , e s
un contrato local de esa app. No debe filtrarse a `ActiveUix` , Soma, Sema ni
If a non-UIX app decides to declare its own visual dimension in `prefs` , that i s
a local contract of that app. It must not leak into `ActiveUix` , Soma, Sema or
Morfo.
## Storage
`createPrefsStorageBridge(...)` persiste intenciones, no valores efectivo s:
`createPrefsStorageBridge(...)` persists intents, not effective value s:
```ts
const bridge = createPrefsStorageBridge({
engine, // EnginePrefs
storage, // PrefsIntentStorage
onError: (error, op) => report(error, op), // opc ional
skipHydrate: false // opc ional (default false)
onError: (error, op) => report(error, op), // opt ional
skipHydrate: false // opt ional (default false)
});
```
Regla s:
Rul es:
- 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 .
- Persist only `intent` .
- Do not persist `environment` .
- Do not persist `effective` .
- Do not write during hydrate unless explicitly configured .
- A storage failure must not corrupt in-memory preferences .
## Errors
Los errores publicos usan la familia `prefs::*` :
Public errors use the `prefs::*` family :
- `prefs::unknown_dimension`
- `prefs::intent_invalid`
- `prefs::reserved_key`
- `prefs::disposed`
Ejemplo conocido :
Known example :
```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` .
In UIX that error usually means a shell tried to write `prefs.theme` . The fix is
to pass the visual mode to `ActiveEidos` .
## Tests