master
${ noResults }
2 Commits (7adf93ca5741986d3c5c2bd9aa6550044fe5949f)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
7adf93ca57 |
Prefs as schema-based core + lowercase App.* surface
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>
|
5 months ago |
|
|
80d7b9ad45 |
Bloque L2 — arts/prefs engine + rune adapter + capability sources
Runtime layer over `libs/prefs`. Pure data engine, runes-free so server-only
modules can import it; the Svelte adapter is opt-in.
- `engine-prefs.ts` — `createEnginePrefs({ capabilities, environment?, intent? })`
holds the four-layer state, recomputes `effective` per commit, and notifies
subscribers with `{previous, next, effectiveDiff, cause}`. No-op writes
short-circuit (no version bump, no listener walk). `setIntent` validates
synchronously and throws `PrefsIntentInvalidError` with the structured
failure code from `validateIntentValue`. `setCapabilities` validates the
new `defaults` against the new sets but preserves existing intent —
capability shrink does not corrupt the persisted user choice.
- `active-prefs.svelte.ts` — rune adapter exposing `state.snapshot`,
`state.effective`, `state.{capabilities,environment,intent}`. `pending` and
`lastError` are placeholders for the storage bridge to flip later.
- `sources.ts` — nine narrow `Source<T>` proxies (`prefsLanguageSource`,
`prefsLocaleSource`, …). Each forwards `onChange` only when its specific
field appears in `effectiveDiff`, so consumers wake up per-dimension.
- `errors.ts` — `PrefsDisposedError`, `PrefsIntentInvalidError`,
`PrefsCapabilitiesInvalidError`, all rooted at `PREFS_ERR` via `libs/errs`.
- README rewritten to match the locked-in design (parallel projection,
language/locale split, Source<T> port, persisted-intent-survives-shrink
rule).
Tests: 27 engine + 5 rune adapter (client project) + 8 capability sources.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |