|
|
|
|
# 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`.
|
|
|
|
|
- `themeDimension(...)` y `densityDimension(...)` quedan como factories
|
|
|
|
|
legacy/custom para apps ajenas a UIX; no usarlas en shells UIX nuevas.
|
|
|
|
|
|
|
|
|
|
## 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<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:
|
|
|
|
|
|
|
|
|
|
```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<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:
|
|
|
|
|
|
|
|
|
|
```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
|
|
|
|
|
```
|