Two structural changes that were overdue and got bundled because
they touched the same set of files.
## Prefs is now a schema, not a fixed shape
Previously every preference had to be declared in a closed
`PrefsCapabilities` interface (`languages`, `locales`, `currencies`,
`themes`, `densities`, `motions`, `timezones`, `unitSystems`). Adding
a new pref required forking `$libs/prefs` — bad framework design.
The redesign replaces the fixed shape with a schema:
PrefsSchema = Record<string, PrefsDimension<TIntent, TEffective>>
Each dimension owns its own validator (`validate`), environment-fed
resolver (`resolve`) and optional sibling-derived value (`derive`).
The engine is generic over the schema and iterates it; it knows
nothing about "locale" or "theme" specifically.
Built-in dimensions live in `arts/prefs/dimensions/*` (locale,
language, theme, density, motion, timezone, currency, unit-system,
direction, plus boolean / enum / string / number primitives). The
`standardPrefsDimensions(catalog)` preset composes the canonical
set; apps spread it and add their own:
const schema = {
...standardPrefsDimensions({ languages, locales, currencies }),
sidebarCollapsed: booleanDimension({ default: false }),
notificationLevel: enumDimension(
['all', 'mentions', 'none'] as const,
{ default: 'mentions' }
)
};
Active surface exposes one slot per schema key with uniform verbs:
App.prefs.locale.get()
App.prefs.locale.set('es-ES')
App.prefs.locale.clear()
App.prefs.locale.onChange((v) => …)
App.prefs.sidebarCollapsed.set(true)
`setIntent('locale', value)` stays available as a low-level pass-
through (storage bridge consumes it generically) but UI code uses
the dimension surface.
## Lowercase core surface
`App.Logger`, `App.Bus`, `App.Timers`, `App.Orca`, `App.Prefs` are
gone. The "PascalCase for core, lowercase for services" rule was
visual signalling against JS convention, no technical benefit, and
created an asymmetry on the same object. All core members are now
lowercase, matching services:
App.logger
App.bus
App.timers
App.orca
App.prefs
`createPrefsStorageBridge` keeps its old responsibilities; sources
helpers (`prefsLocaleSource`, …) are gone — the dimension API
replaces them.
## What changed
- `$libs/prefs`: fully generic schema-based types + resolver. Old
fixed `PrefsCapabilities` / `PrefsIntent` / `PrefsEffective`
removed; replaced by `PrefsDimension`, `PrefsSchema`,
`PrefsEffectiveOf<S>`, `PrefsIntentOf<S>`.
- `arts/prefs`: engine + active wrapper rewritten to schema. Per-
dimension active surface auto-built from schema keys. Sources file
deleted (replaced by dimension surface). New
`arts/prefs/dimensions/*` and `arts/prefs/standard.ts`. Storage
bridge made schema-generic.
- `arts/active-app`: lowercase `CoreServices` / `ActiveAppCore`,
`prefs?: ActiveAppPrefsOptions<S>` root option carrying the
schema. `defineActivePrefs` deleted (prefs is core, not service).
`lang` / `format` / `frontend` factories migrated to read
`core.prefs.<dim>` directly via defensive `readSlot()` helpers
(each dimension is optional from the factory's POV; if the app's
schema omits one, the integration degrades gracefully).
- Presets, demos, web routes, README docstrings, `check-aliases.mjs`
guards, marketing snippets all migrated.
- Tests: `engine-prefs`, `active-prefs`, `storage-bridge`,
`resolve-prefs`, `validate-intent`, `prefs-consumer-wiring`,
`service-factories` rewritten for the schema-based API.
`sources.test.ts` deleted (sources file is gone).
## Verification
- `npm run check`: 0 errors, 0 warnings (1527 files).
- `npm test`: 1645 tests across 139 files, all green.
- `node scripts/check-aliases.mjs`: clean (lowercase enforced for
every member of `App.*`, including `Logger`/`Bus`/`Timers`/`Orca`/
`Prefs` which now flag as forbidden capitals).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>