You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

247 lines
7.9 KiB

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
import type {
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
PrefsChangeHandler,
PrefsDimension,
PrefsEffectiveOf,
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
PrefsEnvironment,
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
PrefsIntentOf,
PrefsSchema,
PrefsSnapshot,
PrefsUnsubscribe
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
} from '$libs/prefs';
import { createEnginePrefs } from './engine-prefs.ts';
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
import { PREFS_KIND } from './consts.ts';
import { PrefsReservedKeyError, reservedKeyErrorMessage } from './errors.ts';
import type { EnginePrefsOptions } from './types.ts';
/**
* Per-dimension active surface. Every key in the schema becomes one of
* these on the parent `ActivePrefs<S>`, addressable as
* `App.prefs.<key>`. The verbs are uniform across every dimension: get
* the effective value, set / clear user intent, listen for changes.
*/
export interface ActivePrefsDimension<TIntent, TEffective = TIntent> {
/** Current effective value (intent → environment → default). */
get(): TEffective;
/**
* Set explicit user intent for this dimension. Validates first;
* throws `PrefsIntentInvalidError` on rejection.
*/
set(value: TIntent): void;
/** Drop user intent. Falls back to environment / default. */
clear(): void;
/**
* Subscribe to commits where this dimension's effective value
* changed. Returns an unsubscribe function. Best-effort: a handler
* that throws does not block its peers.
*/
onChange(handler: (value: TEffective) => void): PrefsUnsubscribe;
/** Optional capability catalog when the dimension exposes one. */
catalog(): readonly TIntent[] | undefined;
}
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
/**
* Reactive view of `EnginePrefs.state`. Backed by `$state` cells inside
* `createActivePrefs` — accessing the getters inside a Svelte template
* or `$derived` tracks dependencies the way you'd expect.
*
* `pending` and `lastError` are placeholders for async adapters (the
* storage bridge in particular). With a sync-only engine they stay
* `false` / `null`.
*/
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
export interface ActivePrefsState<S extends PrefsSchema> {
readonly snapshot: PrefsSnapshot<S>;
readonly effective: PrefsEffectiveOf<S>;
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
readonly environment: PrefsEnvironment;
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
readonly intent: PrefsIntentOf<S>;
readonly version: number;
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
readonly pending: boolean;
readonly lastError: unknown;
}
/**
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
* Reserved members of `ActivePrefs<S>`. A schema key that matches one
* of these would shadow the active surface — the constructor throws
* `PrefsReservedKeyError` so the misconfiguration fails fast.
*/
export const ACTIVE_PREFS_RESERVED_KEYS: readonly string[] = [
'kind',
'schema',
'state',
'snapshot',
'environment',
'intent',
'effective',
'resetIntent',
'refreshEnvironment',
'patchEnvironment',
'subscribe',
'dispose'
];
const RESERVED_SET = new Set(ACTIVE_PREFS_RESERVED_KEYS);
/**
* Reactive Svelte adapter over `EnginePrefs<S>`. Adds the
* `ActivePrefsState` block and exposes one `ActivePrefsDimension` per
* schema key as a property — i.e. for `schema = { locale, theme }` the
* returned object has `.locale.get()` / `.locale.set(...)` /
* `.theme.get()` / etc., type-checked against each dimension's
* `TIntent` and `TEffective`.
*/
/**
* Top-level (non-dimension) surface. Always present regardless of the
* schema. Service factories see this shape via `core.prefs` and read
* specific dimensions defensively at runtime through their string key.
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
*/
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
export interface ActivePrefsBase<S extends PrefsSchema = PrefsSchema> {
readonly kind: typeof PREFS_KIND;
readonly schema: S;
readonly state: ActivePrefsState<S>;
snapshot(): PrefsSnapshot<S>;
environment(): PrefsEnvironment;
intent(): PrefsIntentOf<S>;
effective(): PrefsEffectiveOf<S>;
/**
* Low-level mutator. The recommended public API is
* `App.prefs.<dim>.set(value)` — this stays exposed only for
* adapters (storage bridge, devtools) that operate generically over
* dimension keys.
*/
setIntent<K extends keyof S>(key: K, value: unknown): PrefsSnapshot<S>;
/** Low-level mutator; prefer `App.prefs.<dim>.clear()`. */
clearIntent<K extends keyof S>(key: K): PrefsSnapshot<S>;
resetIntent(next?: PrefsIntentOf<S>): PrefsSnapshot<S>;
refreshEnvironment(next: PrefsEnvironment): PrefsSnapshot<S>;
patchEnvironment(patch: Partial<PrefsEnvironment>): PrefsSnapshot<S>;
subscribe(handler: PrefsChangeHandler<S>): PrefsUnsubscribe;
dispose(): void;
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
}
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
/**
* Dimension surface — one slot per schema key. Only meaningful when
* `S` is a concrete schema literal. With the open `PrefsSchema`
* (`Record<string, PrefsDimension>`) the mapped type would collide
* with the index signature on the base, so we degrade to `unknown`
* for the open case (`X & unknown = X`).
*
* Service factories that need typed dimensions cast through their own
* schema generic; consumers that read `App.prefs.<key>` get the typed
* surface because `App` flows the concrete `TPrefsSchema`.
*/
export type ActivePrefsDimensions<S extends PrefsSchema> = string extends keyof S
? unknown
: {
readonly [K in keyof S]: S[K] extends PrefsDimension<infer TIntent, infer TEffective>
? ActivePrefsDimension<TIntent, TEffective>
: never;
};
export type ActivePrefs<S extends PrefsSchema = PrefsSchema> = ActivePrefsBase<S> &
ActivePrefsDimensions<S>;
export function createActivePrefs<S extends PrefsSchema>(
options: EnginePrefsOptions<S>
): ActivePrefs<S> {
for (const key of Object.keys(options.schema)) {
if (RESERVED_SET.has(key)) {
throw new PrefsReservedKeyError(key, reservedKeyErrorMessage(key));
}
}
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
const engine = createEnginePrefs(options);
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
let snapshotCell = $state<PrefsSnapshot<S>>(engine.snapshot());
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
// `pending` / `lastError` are placeholders today (sync engine has
// nothing async to track). Declared as `let` so async adapters
// (storage bridge in particular) can flip them when wired in
// without restructuring the rune layout.
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
const pendingCell = $state(false);
const lastErrorCell = $state<unknown>(null);
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
const detachCommit = engine.subscribe((event) => {
snapshotCell = event.next;
});
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
const state: ActivePrefsState<S> = {
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
get snapshot() {
return snapshotCell;
},
get effective() {
return snapshotCell.effective;
},
get environment() {
return snapshotCell.environment;
},
get intent() {
return snapshotCell.intent;
},
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
get version() {
return snapshotCell.version;
},
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
get pending() {
return pendingCell;
},
get lastError() {
return lastErrorCell;
}
};
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
const dimensionMembers: Record<string, ActivePrefsDimension<unknown, unknown>> = {};
for (const key of Object.keys(options.schema)) {
dimensionMembers[key] = makeDimension(engine, key);
}
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
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
const base = {
kind: PREFS_KIND as typeof PREFS_KIND,
schema: options.schema,
state,
snapshot: () => engine.snapshot(),
environment: () => engine.environment(),
intent: () => engine.intent(),
effective: () => engine.effective(),
setIntent: <K extends keyof S>(key: K, value: unknown) => engine.setIntent(key, value),
clearIntent: <K extends keyof S>(key: K) => engine.clearIntent(key),
resetIntent: (next?: PrefsIntentOf<S>) => engine.resetIntent(next),
refreshEnvironment: (next: PrefsEnvironment) => engine.refreshEnvironment(next),
patchEnvironment: (patch: Partial<PrefsEnvironment>) => engine.patchEnvironment(patch),
subscribe: (handler: PrefsChangeHandler<S>) => engine.subscribe(handler),
dispose: () => {
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
detachCommit();
engine.dispose();
}
};
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
return Object.assign(base, dimensionMembers) as unknown as ActivePrefs<S>;
}
function makeDimension(
engine: ReturnType<typeof createEnginePrefs>,
key: string
): ActivePrefsDimension<unknown, unknown> {
return {
get() {
return (engine.snapshot().effective as Record<string, unknown>)[key];
},
set(value: unknown) {
engine.setIntent(key, value);
},
clear() {
engine.clearIntent(key);
},
onChange(handler: (value: unknown) => void) {
return engine.subscribe((event) => {
const diff = event.effectiveDiff as Record<string, unknown>;
if (Object.prototype.hasOwnProperty.call(diff, key)) {
handler(diff[key]);
}
});
},
catalog() {
const dim = engine.schema[key];
return dim?.catalog?.();
}
};
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
}

Powered by TurnKey Linux.