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.

235 lines
6.4 KiB

Build runtime infrastructure layer: aapp + 5 new artifacts Adds the application composition root and six new runtime artifacts. lang and logr alone could not cover an app surface — locale propagation, format helpers, frontend preferences, validation, persistence and DOM service all needed independent artifacts that aapp wires together under a single locale source of truth and a shared logger. New artifacts: - aapp: composition root. createActiveApp + createTestApp + App.createSiumEngine + frontend.persist + Storage wiring. - stor: pluggable adapters (local/session/memory/cookie + SSR via cookieAdapter.fromCookies), envelope versioning + migrate + TTL, validate via Standard Schema, intra-tab + cross-tab sync, Storage.clear(), per-entry adapter/namespace overrides. - adom: reactive DOM service. shareViewport opt-in + per-window tracker, dispose(). breakpoints, viewport, attribute writes. - fend: frontend preferences (theme, mode, dir, density, reducedMotion, reducedSound) with auto/clear/isAuto pattern. - fmts: localized formatting (numbers, currency, units, dates) with shared LocaleSource and per-domain auto/clear pattern. - sium: validation engine. Standard Schema interop, Lang/Logger injection, codec/refine/transform, domain types (color/date/time), introspection. Existing artifacts extended: - lang: BCP 47 SupportedLocale (LangBase | base-region), mono lang for monolingual apps, extend() leaf-overwrite warning, EngineLang/ActiveLang naming alignment with the rest of the project (no Instance suffix). - logr: EngineLogger.dispose(), failureThrottleMs anti-cascade for transports, README sync to the per-level filter API (minLevel removed). Cross-cutting: - $locale: shared LocaleSource type consumed by fmts and fend. - arts/README.md: artifact map, dependency graph, naming conventions. - Test pages for every artifact under /test/<artifact>. 733 tests passing; svelte-check clean except 2 pre-existing errors on sium/svelte provider (port pending). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
import type { LocaleSource } from '$locale';
import { createActiveDom, type ActiveDom } from '$adom';
import { DEFAULT_DENSITY, DEFAULT_MODE, DEFAULT_THEME, FRONTEND_ATTRS } from './consts';
import { resolveDir } from './locale-defaults';
import type { Direction } from './locale-defaults';
export type FrontendMode = 'light' | 'dark' | 'auto';
export type ResolvedFrontendMode = Exclude<FrontendMode, 'auto'>;
export type FrontendDensity = 'compact' | 'normal' | 'comfortable';
export type FrontendDirMode = Direction | 'auto';
/**
* Reactive locale provider consumed by `ActiveFrontend`. Alias of the shared
* `LocaleSource` so existing imports keep working.
*/
export type FrontendLocaleSource = LocaleSource;
export type FrontendTarget = HTMLElement | (() => HTMLElement | null);
export type FrontendDom = Pick<ActiveDom, 'apply'>;
export interface ActiveFrontendOptions {
locale?: string;
localeSource?: FrontendLocaleSource;
dom?: FrontendDom;
target?: FrontendTarget;
applyDom?: boolean;
/** Initial dir override (`auto` = derive from locale). */
dir?: FrontendDirMode;
theme?: string;
mode?: FrontendMode;
reducedMotion?: boolean | 'auto';
reducedSound?: boolean;
density?: FrontendDensity;
}
export interface ActiveFrontend {
getLocale: () => string;
setLocale: (locale: string) => void;
getDir: () => Direction;
setDir: (dir: FrontendDirMode) => void;
clearDir: () => void;
isDirAuto: () => boolean;
getTheme: () => string;
setTheme: (theme: string) => void;
getMode: () => ResolvedFrontendMode;
setMode: (mode: FrontendMode) => void;
clearMode: () => void;
isModeAuto: () => boolean;
getReducedMotion: () => boolean;
setReducedMotion: (reduced: boolean | 'auto') => void;
clearReducedMotion: () => void;
isReducedMotionAuto: () => boolean;
getReducedSound: () => boolean;
setReducedSound: (reduced: boolean) => void;
getDensity: () => FrontendDensity;
setDensity: (density: FrontendDensity) => void;
onPreferenceChange: (fn: () => void) => () => void;
dispose: () => void;
}
function getDefaultTarget(): HTMLElement | null {
return typeof document === 'undefined' ? null : document.documentElement;
}
function resolveTarget(target?: FrontendTarget): HTMLElement | null {
if (target === undefined) return getDefaultTarget();
return typeof target === 'function' ? target() : target;
}
function subscribeMedia(query: string, fn: (matches: boolean) => void): () => void {
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return () => {};
const media = window.matchMedia(query);
const onChange = (event: MediaQueryListEvent): void => fn(event.matches);
fn(media.matches);
media.addEventListener('change', onChange);
return () => media.removeEventListener('change', onChange);
}
export function createActiveFrontend(options: ActiveFrontendOptions = {}): ActiveFrontend {
const localeSource = options.localeSource;
const ownedDom =
options.applyDom === false || options.dom !== undefined ? undefined : createActiveDom();
const dom = options.applyDom === false ? undefined : (options.dom ?? ownedDom);
Build runtime infrastructure layer: aapp + 5 new artifacts Adds the application composition root and six new runtime artifacts. lang and logr alone could not cover an app surface — locale propagation, format helpers, frontend preferences, validation, persistence and DOM service all needed independent artifacts that aapp wires together under a single locale source of truth and a shared logger. New artifacts: - aapp: composition root. createActiveApp + createTestApp + App.createSiumEngine + frontend.persist + Storage wiring. - stor: pluggable adapters (local/session/memory/cookie + SSR via cookieAdapter.fromCookies), envelope versioning + migrate + TTL, validate via Standard Schema, intra-tab + cross-tab sync, Storage.clear(), per-entry adapter/namespace overrides. - adom: reactive DOM service. shareViewport opt-in + per-window tracker, dispose(). breakpoints, viewport, attribute writes. - fend: frontend preferences (theme, mode, dir, density, reducedMotion, reducedSound) with auto/clear/isAuto pattern. - fmts: localized formatting (numbers, currency, units, dates) with shared LocaleSource and per-domain auto/clear pattern. - sium: validation engine. Standard Schema interop, Lang/Logger injection, codec/refine/transform, domain types (color/date/time), introspection. Existing artifacts extended: - lang: BCP 47 SupportedLocale (LangBase | base-region), mono lang for monolingual apps, extend() leaf-overwrite warning, EngineLang/ActiveLang naming alignment with the rest of the project (no Instance suffix). - logr: EngineLogger.dispose(), failureThrottleMs anti-cascade for transports, README sync to the per-level filter API (minLevel removed). Cross-cutting: - $locale: shared LocaleSource type consumed by fmts and fend. - arts/README.md: artifact map, dependency graph, naming conventions. - Test pages for every artifact under /test/<artifact>. 733 tests passing; svelte-check clean except 2 pre-existing errors on sium/svelte provider (port pending). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
let currentLocale = $state(localeSource?.getLocale() ?? options.locale ?? '');
let dirOverride = $state<Direction | null>(
options.dir === 'auto' || options.dir === undefined ? null : options.dir
);
let theme = $state(options.theme ?? DEFAULT_THEME);
let modeOverride = $state<ResolvedFrontendMode | null>(
options.mode === 'auto' || options.mode === undefined ? null : options.mode
);
let reducedMotionOverride = $state<boolean | null>(
options.reducedMotion === 'auto' || options.reducedMotion === undefined
? null
: options.reducedMotion
);
let reducedSound = $state(options.reducedSound ?? false);
let density = $state<FrontendDensity>(options.density ?? DEFAULT_DENSITY);
let osDarkMode = $state(false);
let osReducedMotion = $state(false);
const listeners = new Set<() => void>();
function getLocale(): string {
return currentLocale || localeSource?.getLocale() || options.locale || '';
}
function getDir(): Direction {
return dirOverride ?? resolveDir(getLocale());
}
function getMode(): ResolvedFrontendMode {
return modeOverride ?? (osDarkMode ? 'dark' : DEFAULT_MODE);
}
function getReducedMotion(): boolean {
return reducedMotionOverride ?? osReducedMotion;
}
function applyDom(): void {
const target = resolveTarget(options.target);
if (dom === undefined || target === null) return;
dom.apply({
target,
attrs: {
[FRONTEND_ATTRS.DIR]: getDir(),
[FRONTEND_ATTRS.THEME]: theme,
[FRONTEND_ATTRS.MODE]: getMode(),
[FRONTEND_ATTRS.REDUCED_MOTION]: String(getReducedMotion()),
[FRONTEND_ATTRS.REDUCED_SOUND]: String(reducedSound),
[FRONTEND_ATTRS.DENSITY]: density
}
});
}
function notify(): void {
applyDom();
for (const fn of listeners) fn();
}
function setLocale(locale: string): void {
currentLocale = locale;
notify();
}
const unsubscribeLocale = localeSource?.onLocaleChange?.(setLocale);
const unsubscribeDarkMode = subscribeMedia('(prefers-color-scheme: dark)', (matches) => {
osDarkMode = matches;
notify();
});
const unsubscribeReducedMotion = subscribeMedia('(prefers-reduced-motion: reduce)', (matches) => {
osReducedMotion = matches;
notify();
});
applyDom();
return {
getLocale,
setLocale,
getDir,
setDir: (dir) => {
dirOverride = dir === 'auto' ? null : dir;
notify();
},
clearDir: () => {
dirOverride = null;
notify();
},
isDirAuto: () => dirOverride === null,
getTheme: () => theme,
setTheme: (nextTheme) => {
theme = nextTheme;
notify();
},
getMode,
setMode: (mode) => {
modeOverride = mode === 'auto' ? null : mode;
notify();
},
clearMode: () => {
modeOverride = null;
notify();
},
isModeAuto: () => modeOverride === null,
getReducedMotion,
setReducedMotion: (reduced) => {
reducedMotionOverride = reduced === 'auto' ? null : reduced;
notify();
},
clearReducedMotion: () => {
reducedMotionOverride = null;
notify();
},
isReducedMotionAuto: () => reducedMotionOverride === null,
getReducedSound: () => reducedSound,
setReducedSound: (reduced) => {
reducedSound = reduced;
notify();
},
getDensity: () => density,
setDensity: (nextDensity) => {
density = nextDensity;
notify();
},
onPreferenceChange: (fn) => {
listeners.add(fn);
return () => listeners.delete(fn);
},
dispose() {
unsubscribeLocale?.();
unsubscribeDarkMode();
unsubscribeReducedMotion();
ownedDom?.dispose();
Build runtime infrastructure layer: aapp + 5 new artifacts Adds the application composition root and six new runtime artifacts. lang and logr alone could not cover an app surface — locale propagation, format helpers, frontend preferences, validation, persistence and DOM service all needed independent artifacts that aapp wires together under a single locale source of truth and a shared logger. New artifacts: - aapp: composition root. createActiveApp + createTestApp + App.createSiumEngine + frontend.persist + Storage wiring. - stor: pluggable adapters (local/session/memory/cookie + SSR via cookieAdapter.fromCookies), envelope versioning + migrate + TTL, validate via Standard Schema, intra-tab + cross-tab sync, Storage.clear(), per-entry adapter/namespace overrides. - adom: reactive DOM service. shareViewport opt-in + per-window tracker, dispose(). breakpoints, viewport, attribute writes. - fend: frontend preferences (theme, mode, dir, density, reducedMotion, reducedSound) with auto/clear/isAuto pattern. - fmts: localized formatting (numbers, currency, units, dates) with shared LocaleSource and per-domain auto/clear pattern. - sium: validation engine. Standard Schema interop, Lang/Logger injection, codec/refine/transform, domain types (color/date/time), introspection. Existing artifacts extended: - lang: BCP 47 SupportedLocale (LangBase | base-region), mono lang for monolingual apps, extend() leaf-overwrite warning, EngineLang/ActiveLang naming alignment with the rest of the project (no Instance suffix). - logr: EngineLogger.dispose(), failureThrottleMs anti-cascade for transports, README sync to the per-level filter API (minLevel removed). Cross-cutting: - $locale: shared LocaleSource type consumed by fmts and fend. - arts/README.md: artifact map, dependency graph, naming conventions. - Test pages for every artifact under /test/<artifact>. 733 tests passing; svelte-check clean except 2 pre-existing errors on sium/svelte provider (port pending). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
listeners.clear();
}
};
}

Powered by TurnKey Linux.