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.
svelte-kit-vice/src/uix/contracts.test.ts

941 lines
36 KiB

// @vitest-environment jsdom
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
import { basename, dirname, join, sep } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import { createActiveApp, type ActiveApp } from '$active-app';
import { createActiveDom } from '$adom';
import { createActivePrefs, createActivePrefsDomProjection, standardPrefsDimensions } from '$prefs';
import type { LangNode } from '$libs/langs';
import { state } from '$libs/reactive';
import {
ActiveUixMissingServiceError,
attachActiveUix,
createActiveUix
} from './active-uix/active-uix.svelte';
import { defineUixServices, UIX_REQUIRED_SERVICES } from './active-uix/services';
import { createActiveEidos, ActiveEidosConfigError } from './eidos/active-eidos.svelte';
import {
_resetMorfoRegistryForTesting,
registerMorfo,
type Morfo,
v
} from './morfo';
import { toggleMorfo } from './morfo/components/toggle';
import { EngineSemantic, defineEngineSemantic } from './sema';
import { SemaConfigError } from './sema/errors';
import { createSomaRuntime } from './soma/runtime.svelte';
import { UIX_LAYER_CONTRACTS } from './contracts';
import { commonLangs } from './langs';
const minimalLang = { schema: {} satisfies LangNode, defaultLocale: 'es' as const };
const prefsProjectionSchema = standardPrefsDimensions({
languages: ['es-ES', 'ar-EG'],
locales: ['es-ES', 'ar-EG'],
currencies: ['EUR'],
defaults: {
language: 'es-ES',
locale: 'es-ES',
currency: 'EUR'
}
});
const HERE = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = join(HERE, '..', '..');
const SOURCE_EXTENSIONS = ['.ts', '.svelte'] as const;
function preferencesHarness() {
return {
getTheme: () => 'base',
getMode: () => 'light' as const,
getDensity: () => 'comfortable' as const,
getScaling: () => '100' as const,
onPreferenceChange: () => () => {}
};
}
function listSourceFiles(root: string): string[] {
const stat = statSync(root);
if (stat.isFile()) {
return SOURCE_EXTENSIONS.some((ext) => root.endsWith(ext)) ? [root] : [];
}
return readdirSync(root)
.flatMap((entry) => listSourceFiles(join(root, entry)))
.filter(
(path) => !path.endsWith('.test.ts') && !path.includes(`${join('components', 'icon', 'lib')}`)
)
// Active-dev-track components are excluded from the audited catalogue
// (same doctrine as collectPublicSomaComponentDirs) — their files are
// skipped by every file-scanning guard until their track lands.
.filter(
(path) =>
![...ACTIVE_DEV_TRACK].some(
(name) =>
path.includes(`${join('components', name)}${sep}`) ||
path.endsWith(`${join('components', `${name}.ts`)}`)
)
);
}
function grepSources(root: string, pattern: RegExp): string[] {
return listSourceFiles(root).filter((file) => pattern.test(readFileSync(file, 'utf8')));
}
function stripComments(source: string): string {
return source.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1');
}
function collectKnownMorfoDataAttrs(): Set<string> {
const attrs = new Set<string>();
// Public morfos (`morfo/components/*.ts`) AND internal primitives
// (`morfo/internal/*.ts`) — the internal directory holds picker-shell
// and any future shared chrome. Their `data-{name}` markers are part
// of the catalogue too (see audit-codex P1 #5).
const internalRoot = join(HERE, 'morfo', 'internal');
const morfoFiles = [
...listSourceFiles(join(HERE, 'morfo', 'components')),
...(existsSync(internalRoot) ? listSourceFiles(internalRoot) : [])
];
for (const file of morfoFiles) {
const source = stripComments(readFileSync(file, 'utf8'));
const component = /kebab:\s*'([^']+)'/.exec(source)?.[1];
if (!component) continue;
const partRe = /kebab:\s*'([^']+)'/g;
let partMatch: RegExpExecArray | null;
while ((partMatch = partRe.exec(source))) {
const part = partMatch[1];
attrs.add(part === 'provider' ? `data-${component}` : `data-${component}-${part}`);
}
const dataRe = /attr:\s*'([^']+)'/g;
let dataMatch: RegExpExecArray | null;
while ((dataMatch = dataRe.exec(source))) {
if (dataMatch[1].startsWith('data-')) attrs.add(dataMatch[1]);
}
}
// Compiler-emitted from every part's `archetype` declaration — a known
// name catalogue-wide. Composition overrides (a field-overlay trigger
// stamped over the composed Popover trigger's generic archetype) write it
// manually with the value their OWN morfo part declares.
attrs.add('data-archetype');
return attrs;
}
function collectDataAttrs(source: string): Set<string> {
const clean = stripComments(source);
const attrs = new Set<string>();
const stringRe = /['"`](data-[a-zA-Z0-9_-]+)['"`]/g;
const inlineRe = /\b(data-[a-zA-Z0-9_-]+)(?=(?:=|\s|>|\/|}|\]))/g;
let match: RegExpExecArray | null;
while ((match = stringRe.exec(clean))) attrs.add(match[1]);
while ((match = inlineRe.exec(clean))) attrs.add(match[1]);
return attrs;
}
function toPascalName(kebab: string): string {
return kebab
.split('-')
.map((segment) => `${segment.charAt(0).toUpperCase()}${segment.slice(1)}`)
.join('');
}
// Soma component directories that are internal primitives, not public
// components. They ship under `soma/components/{name}/` because they
// have eidos chrome + a provider, but their morfo lives in
// `morfo/internal/` rather than `morfo/components/` (see audit-codex P1
// #5). Audit guards must skip them when comparing soma <-> morfo
// catalogues, otherwise the asymmetry produces a false positive.
// Soma modules that are NOT public component namespaces: shared chrome or
// behaviour bridges consumed by other layers, with no Provider/parts API of
// their own (each documents the split in its index.ts — the eidos root owns
// state + render; the bridge owns runtime emission / keyboard dispatch / pure
// nav maths). They are excluded from BOTH sides of the dir↔scope equality:
// a bridge may truthfully declare 'soma' in its morfo scope (it implements
// soma behaviour) without being a public soma component namespace.
// picker-shell — the pickers' shared footer chrome (no morfo of its own)
// metrics — live-update signal bridge (scope ['sema','eidos'])
// menu-dial — keyboard dispatch + linear nav maths + emission bridge
// onion-menu — keyboard dispatch + radial nav maths + emission bridge
const SOMA_INTERNAL_PRIMITIVES = new Set(['picker-shell', 'metrics', 'menu-dial', 'onion-menu']);
function collectPublicSomaComponentDirs(): string[] {
const root = join(HERE, 'soma', 'components');
return readdirSync(root, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.filter(
(name) =>
name !== 'internal' &&
!SOMA_INTERNAL_PRIMITIVES.has(name) &&
!ACTIVE_DEV_TRACK.has(name) &&
existsSync(join(root, name, 'index.ts'))
)
.sort();
}
function collectMorfoComponentNamesByScope(scope: string): string[] {
const root = join(HERE, 'morfo', 'components');
return readdirSync(root)
.filter((file) => file.endsWith('.ts') && !file.endsWith('.test.ts'))
.filter((file) => {
const source = stripComments(readFileSync(join(root, file), 'utf8'));
return new RegExp(`scope:\\s*\\[[^\\]]*'${scope}'`).test(source);
})
.map((file) => file.replace(/\.ts$/, ''))
.filter((name) => !ACTIVE_DEV_TRACK.has(name))
.sort();
}
// Active-dev-track components, excluded from the audited/maintained catalogue
// (their morfo/eidos are still being authored). They are brought into
// compliance when their track lands; until then the catalogue guards skip them.
const ACTIVE_DEV_TRACK = new Set([
'words',
'palabras',
'chronos'
]);
// MOR-4 enumeration guard (clean-room 2026-07-10). The acceptance machinery
// (`component-audit`, `eidos-lint-all`) enumerates BY MORFO FILE — an eidos
// component dir with no morfo is invisible to every gate (card-group shipped
// that way). A morfo may live in `morfo/components/` (public catalogue) or
// `morfo/internal/` (shared layers, e.g. picker-shell). Exemptions, each with
// its own invariant:
// - shared internal layer still without a contract: spin-field (its CSS
// discipline is covered by recipe-css-contract; the foundation-scope
// R-* work owns the rest);
// - pure API aliases / glyph primitives with ZERO own surface (no css —
// the composed primitive's morfo IS the contract): icon-button (Button
// alias), svg (glyph atoms). The guard asserts the category stays true:
// the moment one ships a stylesheet it needs a morfo like everyone else.
const MORFO_EXEMPT_SHARED_LAYERS = new Set(['spin-field']);
const MORFO_EXEMPT_PURE_ALIASES = new Set(['icon-button', 'svg']);
function collectMorfoEnumerationViolations(): string[] {
const eidosRoot = join(HERE, 'eidos', 'components');
const morfoRoots = [join(HERE, 'morfo', 'components'), join(HERE, 'morfo', 'internal')];
const violations: string[] = [];
for (const entry of readdirSync(eidosRoot, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
const kebab = entry.name;
if (ACTIVE_DEV_TRACK.has(kebab) || MORFO_EXEMPT_SHARED_LAYERS.has(kebab)) continue;
if (MORFO_EXEMPT_PURE_ALIASES.has(kebab)) {
const css = readdirSync(join(eidosRoot, kebab)).filter((f) => f.endsWith('.css'));
if (css.length > 0)
violations.push(
`${kebab}: pure-alias exemption broken — ships ${css.join(', ')} → needs a morfo`
);
continue;
}
if (!morfoRoots.some((root) => existsSync(join(root, `${kebab}.ts`))))
violations.push(`${kebab}: no morfo — invisible to component-audit / eidos-lint`);
}
return violations.sort();
}
// SYS-1 scope-drift guard. `scope` enumerates the layers that IMPLEMENT a
// component, so a component shipping an eidos recipe (a wrapper `.svelte` or a
// `.css` under `eidos/components/{kebab}/` — a README-only dir does NOT count)
// MUST list `'eidos'` in its morfo `scope`. The 2026-06-26 audit found this
// drifted across 62 components; this guard keeps it from drifting back.
function collectScopeDriftViolations(): string[] {
const internalRoot = join(HERE, 'morfo', 'internal');
const eidosRoot = join(HERE, 'eidos', 'components');
const morfoFiles = [
...listSourceFiles(join(HERE, 'morfo', 'components')),
...(existsSync(internalRoot) ? listSourceFiles(internalRoot) : [])
];
const violations: string[] = [];
for (const file of morfoFiles) {
const source = stripComments(readFileSync(file, 'utf8'));
const kebab = /kebab:\s*'([^']+)'/.exec(source)?.[1];
if (kebab && ACTIVE_DEV_TRACK.has(kebab)) continue;
// The morfo-level scope is the only `scope: [ ... ]` ARRAY (part/event
// scope is a string literal), so the first array match is it.
const scopeBody = /\bscope:\s*\[([^\]]*)\]/.exec(source)?.[1];
if (!kebab || scopeBody === undefined) continue;
const eidosDir = join(eidosRoot, kebab);
const hasEidosRecipe =
existsSync(eidosDir) &&
readdirSync(eidosDir).some((entry) => entry.endsWith('.svelte') || entry.endsWith('.css'));
if (hasEidosRecipe && !/'eidos'/.test(scopeBody)) {
violations.push(`${kebab}: eidos recipe exists but morfo scope omits 'eidos'`);
}
}
return violations.sort();
}
// VG-8: a component morfo MUST be declared `as const satisfies Morfo`, never
// `: Morfo`. The annotation widens the literal object to the interface, so the
// compiler stops checking the contract against the actual literals — that is
// exactly how alert-dialog-001 shipped a morfo the schema couldn't validate.
function collectMorfoDeclarationViolations(): string[] {
const internalRoot = join(HERE, 'morfo', 'internal');
const morfoFiles = [
...listSourceFiles(join(HERE, 'morfo', 'components')),
...(existsSync(internalRoot) ? listSourceFiles(internalRoot) : [])
];
const violations: string[] = [];
for (const file of morfoFiles) {
const source = stripComments(readFileSync(file, 'utf8'));
const kebab = /kebab:\s*'([^']+)'/.exec(source)?.[1];
if (kebab && ACTIVE_DEV_TRACK.has(kebab)) continue;
if (!/export const \w+Morfo\b/.test(source)) continue;
const name = file.replace(REPO_ROOT, '');
if (/export const \w+Morfo\s*:\s*Morfo\b/.test(source)) {
violations.push(`${name}: ': Morfo' widens the literal — use 'as const satisfies Morfo'`);
} else if (!/as const satisfies Morfo\b/.test(source)) {
violations.push(`${name}: missing 'as const satisfies Morfo'`);
}
}
return violations.sort();
}
// A31: a per-item membership predicate (`isSelected` / `isItemChecked` /
// `isExpanded` / `isItemPressed` / …) must NOT read a reactive array with
// `.current.includes(...)` / `.indexOf(...)` — called once per item that is
// O(N²) and the documented "hangs at 30+" hazard. Lift a `Set` in a provider
// `$derived` and do O(1) `.has(value)` (the 9 components the audit fixed all do).
function collectA31Violations(): string[] {
const root = join(HERE, 'soma', 'components');
const re =
/\bis\w*(?:Selected|Checked|Expanded|Pressed|Highlighted|Active)\b[^{};]*\{[^{}]*\.current\.(?:includes|indexOf)\b/;
return listSourceFiles(root)
.filter((file) => file.endsWith('.svelte.ts'))
.filter((file) => !ACTIVE_DEV_TRACK.has(basename(dirname(file))))
.filter((file) => re.test(stripComments(readFileSync(file, 'utf8'))))
.map((file) => `${file.replace(REPO_ROOT, '')}: per-item membership uses .current.includes (A31 O(N²))`)
.sort();
}
// A30: a child must register its `inputId` with the parent Field via a DIRECT
// constructor assign, never inside a `$effect` — an effect for a one-shot id
// registration is wasteful + order-fragile (the 12 field-family providers that
// do it right assign directly; date/time/color-field drifted into an effect).
//
// The source MUST be `opts.<x>.current` — a prop that is already available at
// construction, so the effect is pure overhead. A reactive source set LATER by
// a child part (file-upload's `this.hiddenInputId`, '' until the hidden-input
// child mounts) genuinely needs the effect to observe it; that is NOT the
// anti-pattern and the `opts.*` requirement correctly leaves it green.
function collectA30Violations(): string[] {
const root = join(HERE, 'soma', 'components');
// `$effect(() => { … inputId.current = opts.X.current … })`, tempered so the
// assign must live INSIDE this effect's body (stop at the `});` that closes it).
const re =
/\$effect\s*\(\s*\(\s*\)\s*=>\s*\{(?:(?!\}\);)[\s\S]){0,400}?inputId\.current\s*=\s*opts\.\w+\.current/;
return listSourceFiles(root)
.filter((file) => file.endsWith('.svelte.ts'))
.filter((file) => !ACTIVE_DEV_TRACK.has(basename(dirname(file))))
.filter((file) => re.test(stripComments(readFileSync(file, 'utf8'))))
.map((file) => `${file.replace(REPO_ROOT, '')}: inputId registered inside a $effect (A30)`)
.sort();
}
// THEME-SYS-1: overlay z-index lives in a named scale, never a raw integer.
// A recipe `*-z` token must reference `var(--z-index-overlay-*)` (the flat
// overlay micro-band) or `var(--z-index-*)` (the depth-plane ladder) — a bare
// integer is an ad-hoc parallel scale that drifts out of order (the audit found
// tooltip 76 < dropdown 80). Local `z-index: 0..5` in component CSS is relative
// stacking, NOT this — it stays (THEMING.md §35). This guards the recipe defs.
function collectMagicZViolations(): string[] {
const base = stripComments(readFileSync(join(HERE, 'eidos', 'lib', 'recipes', 'base.ts'), 'utf8'));
const violations: string[] = [];
const re = /'([\w-]*-z)':\s*'(\d[^']*)'/g;
let match: RegExpExecArray | null;
while ((match = re.exec(base))) {
violations.push(
`recipes/base.ts: '${match[1]}': '${match[2]}' is a raw z-index — use var(--z-index-overlay-*)`
);
}
return violations.sort();
}
function collectComponentLangPathViolations(source: string, file: string): string[] {
const clean = stripComments(source);
const violations: string[] = [];
const refRe = /#\?components\.([^|'"`\s]+)/g;
let match: RegExpExecArray | null;
while ((match = refRe.exec(clean))) {
const path = match[1];
const segments = path.split('.');
if (segments.some((segment) => /[A-Z]/.test(segment))) {
violations.push(`${file.replace(REPO_ROOT, '')}: components.${path}`);
}
}
return violations;
}
function collectLangCatalogKeyViolations(node: unknown, prefix = 'components'): string[] {
if (!node || typeof node !== 'object') return [];
return Object.entries(node as Record<string, unknown>).flatMap(([key, value]) => {
const path = `${prefix}.${key}`;
const ownViolation = /[A-Z]/.test(key) ? [path] : [];
return [...ownViolation, ...collectLangCatalogKeyViolations(value, path)];
});
}
function collectBrokenSomaComponentReadmeLinks(): string[] {
const root = join(HERE, 'soma', 'components');
return collectPublicSomaComponentDirs().flatMap((dir) => {
const file = join(root, dir, 'README.md');
const source = readFileSync(file, 'utf8');
const violations: string[] = [];
const linkRe = /\]\((\.\.\/[^)]+README\.md)\)/g;
let match: RegExpExecArray | null;
while ((match = linkRe.exec(source))) {
const target = join(root, dir, match[1]);
if (!existsSync(target)) {
violations.push(`${dir}/README.md -> ${match[1]}`);
}
}
return violations;
});
}
describe('UIX layer contracts', () => {
it('pins ActiveUix public service names and rejects legacy aliases', () => {
const uix = createActiveUix({ langs: minimalLang });
try {
for (const key of UIX_LAYER_CONTRACTS.activeUix.publicSurface) {
expect(key in uix, key).toBe(true);
}
for (const key of UIX_LAYER_CONTRACTS.activeUix.forbiddenPublicSurface) {
expect(key in uix, key).toBe(false);
}
expect(uix.ownsApp).toBe(true);
expect(uix.app).toBeUndefined();
expect(uix.events).toBeInstanceOf(EngineSemantic);
} finally {
uix.dispose();
}
});
it('keeps attach mode as consumption-only: required services are langs + dom', () => {
expect(UIX_REQUIRED_SERVICES).toEqual(UIX_LAYER_CONTRACTS.activeUix.attachRequires);
const app = createActiveApp({ services: {} });
try {
attachActiveUix(app as unknown as ActiveApp);
expect.fail('attachActiveUix should reject an app without UIX services');
} catch (err) {
if (!(err instanceof ActiveUixMissingServiceError)) throw err;
expect(err.missing).toEqual(['langs', 'dom']);
expect(err.missing).not.toContain('events');
} finally {
app.dispose();
}
});
it('defineUixServices publishes events, never semantic, and cannot attach without dom', () => {
const services = defineUixServices({ langs: minimalLang });
expect(Object.keys(services).sort()).toEqual([
'clipboard',
'dom',
'events',
'format',
'langs',
'motion',
'scene',
'sound'
]);
expect('semantic' in services).toBe(false);
expect('lang' in services).toBe(false);
expect('frontend' in services).toBe(false);
const noDomServices = defineUixServices({ langs: minimalLang, dom: false });
expect('dom' in noDomServices).toBe(false);
expect('events' in noDomServices).toBe(false);
expect('motion' in noDomServices).toBe(false);
expect('sound' in noDomServices).toBe(false);
const app = createActiveApp({ services: noDomServices });
try {
expect(() => attachActiveUix(app as unknown as ActiveApp)).toThrow(
ActiveUixMissingServiceError
);
} finally {
app.dispose();
}
});
it('guards ActiveUix as composition root, not Soma/Eidos bootstrapper', () => {
const source = readFileSync(join(HERE, 'active-uix', 'active-uix.svelte.ts'), 'utf8');
expect(source).not.toMatch(/from ['"].*\/soma/);
expect(source).not.toMatch(/from ['"].*\/eidos/);
expect(source).not.toMatch(/\bSoma\.create\(/);
expect(source).not.toMatch(/\bActiveEidos\.create\(/);
expect(source).not.toMatch(/\bEidos\.create\(/);
});
it('prefs DOM projection owns only cross-modal attrs', () => {
expect(UIX_LAYER_CONTRACTS.prefsDomProjection.implementation).toBe(
'$prefs.createActivePrefsDomProjection'
);
expect(UIX_LAYER_CONTRACTS.prefsDomProjection.requires).toEqual(['prefs', 'dom']);
expect(UIX_LAYER_CONTRACTS.prefsDomProjection.ownsAttrs).toEqual([
'dir',
'data-motion',
'data-sound',
'data-haptic'
]);
expect(UIX_LAYER_CONTRACTS.prefsDomProjection.forbiddenAttrs).toEqual([
'data-theme',
'data-mode',
'data-density'
]);
});
it('implements cross-modal projection in prefs, not ActiveUix', () => {
const dom = createActiveDom();
const prefs = createActivePrefs({ schema: prefsProjectionSchema });
const target = document.createElement('div');
const projection = createActivePrefsDomProjection({ prefs, dom, target });
try {
for (const attr of UIX_LAYER_CONTRACTS.prefsDomProjection.ownsAttrs) {
expect(target.hasAttribute(attr), attr).toBe(true);
}
for (const attr of UIX_LAYER_CONTRACTS.prefsDomProjection.forbiddenAttrs) {
expect(target.hasAttribute(attr), attr).toBe(false);
}
} finally {
projection.dispose();
prefs.dispose();
dom.dispose();
}
});
it('SomaRuntime has one public part API and only depends on injected services', async () => {
const dom = createActiveDom();
try {
const runtime = createSomaRuntime(toggleMorfo, { dom });
for (const key of UIX_LAYER_CONTRACTS.somaRuntime.publicSurface) {
expect(key in runtime, key).toBe(true);
}
for (const key of UIX_LAYER_CONTRACTS.somaRuntime.forbiddenPublicSurface) {
expect(key in runtime, key).toBe(false);
}
const id = state('toggle-contract');
runtime.part('provider', { id });
expect(runtime.partProps('provider')).toMatchObject({
id: 'toggle-contract',
'data-toggle': ''
});
} finally {
dom.dispose();
}
});
it('guards Soma components from legacy runtime registration APIs', () => {
const violations = grepSources(
join(HERE, 'soma', 'components'),
/registerContract\(|registerPart\(/
);
expect(violations).toEqual([]);
});
it('guards Soma component barrel exports all public component modules', () => {
const indexSource = readFileSync(join(HERE, 'soma', 'components', 'index.ts'), 'utf8');
const missingExports = collectPublicSomaComponentDirs()
.map((dir) => ({
dir,
name: toPascalName(dir),
exportRe: new RegExp(`export \\* as ${toPascalName(dir)} from ['"]\\./${dir}['"];`)
}))
.filter(({ exportRe }) => !exportRe.test(indexSource))
.map(({ dir, name }) => `${dir} -> ${name}`);
expect(missingExports).toEqual([]);
});
it('guards Soma Provider exports use the canonical root wrapper filename', () => {
const violations = collectPublicSomaComponentDirs()
.filter((dir) => {
const source = readFileSync(join(HERE, 'soma', 'components', dir, 'exports.ts'), 'utf8');
const expected = `export { default as Provider } from './components/${dir}.svelte';`;
return !source.includes(expected);
})
.map((dir) => `${dir}/exports.ts`);
expect(violations).toEqual([]);
});
it('guards Date/Time field roots from swallowing provider DOM attrs', () => {
for (const dir of ['date-field', 'time-field']) {
const source = readFileSync(
join(HERE, 'soma', 'components', dir, 'components', `${dir}.svelte`),
'utf8'
);
expect(source, dir).toContain('ref = $bindable(null)');
expect(source, dir).toContain('...restProps');
expect(source, dir).toContain('const mergedProps');
expect(source, dir).toContain('<div {...mergedProps}>');
expect(source, dir).not.toContain('child({ props: {} })');
}
});
it('guards picker roots are DOM-passthrough composition shells', () => {
// Pickers are DOM-passthrough composition shells: they render a
// `<div>` (so morfo runtime / sema can latch onto the root),
// expose `ref` + `child` + DOM attrs via `WithChild`, and accept
// children as composition slot. The wrapper must merge restProps
// into the rendered element and offer both the child-snippet
// branch and the children branch.
const roots = [
['date-picker', 'DatePickerProviderProps'],
['date-range-picker', 'DateRangePickerProviderProps'],
['time-picker', 'TimePickerProviderProps'],
['time-range-picker', 'TimeRangePickerProviderProps']
] as const;
for (const [dir, typeName] of roots) {
const types = readFileSync(join(HERE, 'soma', 'components', dir, 'types.ts'), 'utf8');
const wrapper = readFileSync(
join(HERE, 'soma', 'components', dir, 'components', `${dir}.svelte`),
'utf8'
);
// Match both shapes:
// `WithChild<{ ... }>;`
// `WithChild<Omit<PrimitiveDivAttributes, ...> & { ... }>;`
// per audit-codex P0/P1 refactor of picker ProviderProps to
// extend PrimitiveDivAttributes.
const rootType =
new RegExp(`export type ${typeName}\\s*=\\s*WithChild<[\\s\\S]*?\\}>;`).exec(types)?.[0] ?? '';
expect(rootType, `${dir}.types.ts uses WithChild<{...}>`).not.toBe('');
expect(rootType, dir).toContain('children?: Snippet;');
expect(wrapper, dir).toContain('ref = $bindable(null)');
expect(wrapper, dir).toContain('...restProps');
expect(wrapper, dir).toContain('<div {...mergedProps}>');
expect(wrapper, dir).toContain('{@render children?.()}');
expect(wrapper, dir).not.toContain('child({ props: {} })');
}
});
it('guards Soma public barrels from provider implementation filenames', () => {
const violations = collectPublicSomaComponentDirs()
.map((dir) => join(HERE, 'soma', 'components', dir, 'exports.ts'))
.filter((file) =>
/from ['"][^'"]*provider\.svelte(?:\.ts)?['"]/.test(readFileSync(file, 'utf8'))
)
.map((file) => file.replace(REPO_ROOT, ''));
expect(violations).toEqual([]);
});
it('guards Soma public barrels from shared lib facades', () => {
const violations = collectPublicSomaComponentDirs()
.map((dir) => join(HERE, 'soma', 'components', dir, 'exports.ts'))
.filter((file) => /from ['"]\$libs\//.test(readFileSync(file, 'utf8')))
.map((file) => file.replace(REPO_ROOT, ''));
expect(violations).toEqual([]);
});
it('guards Soma public component modules against Morfo soma scope', () => {
// Internal bridges may declare 'soma' truthfully (they implement soma
// behaviour) without being public namespaces — subtract them so the
// dir↔scope equality compares public surfaces on both sides.
const publicSomaScoped = collectMorfoComponentNamesByScope('soma').filter(
(name) => !SOMA_INTERNAL_PRIMITIVES.has(name)
);
expect(collectPublicSomaComponentDirs()).toEqual(publicSomaScoped);
});
it("guards every component with an eidos recipe declares 'eidos' in morfo scope (SYS-1)", () => {
const violations = collectScopeDriftViolations();
expect(
violations,
`scope-drift — add 'eidos' to the morfo scope of:\n ${violations.join('\n ')}`
).toEqual([]);
});
it('guards every eidos component dir is enumerable by the acceptance machinery (MOR-4)', () => {
const violations = collectMorfoEnumerationViolations();
expect(
violations,
`morfo-enumeration holes:\n ${violations.join('\n ')}`
).toEqual([]);
});
it("guards every morfo is declared 'as const satisfies Morfo', never ': Morfo' (VG-8)", () => {
const violations = collectMorfoDeclarationViolations();
expect(violations, `morfo declaration drift:\n ${violations.join('\n ')}`).toEqual([]);
});
it('guards per-item membership predicates against O(N²) .current.includes (A31)', () => {
const violations = collectA31Violations();
expect(
violations,
`A31 — lift a Set + use .has() in:\n ${violations.join('\n ')}`
).toEqual([]);
});
it('guards inputId Field registration against $effect wrapping (A30)', () => {
const violations = collectA30Violations();
expect(
violations,
`A30 — assign inputId directly in the constructor, not in a $effect:\n ${violations.join('\n ')}`
).toEqual([]);
});
it('guards overlay z-index against raw integers in recipes (THEME-SYS-1)', () => {
const violations = collectMagicZViolations();
expect(
violations,
`magic z-index — reference the overlay scale:\n ${violations.join('\n ')}`
).toEqual([]);
});
it('guards Soma public component modules with local README docs', () => {
const missingReadmes = collectPublicSomaComponentDirs().filter(
(dir) => !existsSync(join(HERE, 'soma', 'components', dir, 'README.md'))
);
expect(missingReadmes).toEqual([]);
});
it('guards Soma component README links to existing component docs', () => {
expect(collectBrokenSomaComponentReadmeLinks()).toEqual([]);
});
it('guards Soma from external re-export facades', () => {
expect(existsSync(join(HERE, 'soma', 'external'))).toBe(false);
const violations = grepSources(join(HERE, 'soma'), /external\/(?:dates|colors)/);
expect(violations).toEqual([]);
});
it('guards Soma root barrel as scope-only', () => {
const source = readFileSync(join(HERE, 'soma', 'index.ts'), 'utf8');
expect(source).toContain("from './core/soma.svelte'");
expect(source).toContain("from './errors'");
expect(existsSync(join(HERE, 'soma', 'core', 'langs.ts'))).toBe(false);
expect(source).not.toMatch(
/from ['"]\.\/(?:provider|props|keyboard|typeahead|css|id|types|runtime\.svelte|components|layers|datetime|color)/
);
});
it('guards Soma internals from importing their own public alias', () => {
const violations = listSourceFiles(join(HERE, 'soma'))
.filter((file) => {
const source = stripComments(readFileSync(file, 'utf8'));
return /from ['"]\$soma(?:['"]|\/)/.test(source);
})
.map((file) => file.replace(REPO_ROOT, ''));
expect(violations).toEqual([]);
});
it('guards Soma from direct mutable DOM writes', () => {
const violations = grepSources(
join(HERE, 'soma'),
/\.setAttribute\(|\.removeAttribute\(|\.toggleAttribute\(|\.classList\.|\.style\.setProperty\(|\.style\.[a-zA-Z]+\s*=/
);
expect(violations).toEqual([]);
});
it('guards Soma timers through the injected scheduler (raw only as the no-timers fallback)', () => {
// Widened from soma/components to ALL of soma (SOM-3, clean-room
// 2026-07-10: layers/ + datetime/ escaped the old scope and held raw
// timers for months). Evidence-based instead of a file allowlist: a
// file may keep a raw setTimeout ONLY as the documented no-timers
// fallback of the scheduler-preferred shape (semaDelay / dismissal /
// safe-polygon / announce-global / announcer / image-provider), which
// the same file evidences by ALSO calling `.schedule(` on the injected
// service. A raw timer with no scheduler path anywhere in the file is
// a violation.
const violations = listSourceFiles(join(HERE, 'soma')).filter((file) => {
const source = stripComments(readFileSync(file, 'utf8'));
if (!/\bsetTimeout\(|\bsetInterval\(/.test(source)) return false;
return !/\.schedule\(|\.interval\(/.test(source);
});
expect(violations).toEqual([]);
});
it('guards Soma components through the shared typeahead helper', () => {
const violations = grepSources(
join(HERE, 'soma', 'components'),
/class\s+Typeahead|typeaheadBuffer|typeaheadTimerKey|clearTypeaheadTimer/
);
expect(violations).toEqual([]);
});
it('guards Soma component listeners through ActiveDom', () => {
// media-provider.ts is the pluggable playback-engine PORT — Svelte-
// agnostic BY DESIGN (its header: "no runes, no $adom"): third-party
// engines (hls.js adapters, embeds) implement the same contract, so it
// must not depend on the component runtime. Its listeners live on the
// engine's own element and are cleaned in destroy().
const allowedPort = join(HERE, 'soma', 'components', 'media-player', 'media-provider.ts');
const violations = grepSources(
join(HERE, 'soma', 'components'),
/\.(addEventListener|removeEventListener)\(/
).filter((file) => file !== allowedPort);
expect(violations).toEqual([]);
});
it('guards hardcoded Soma component data attrs with morfo contracts', () => {
const knownAttrs = collectKnownMorfoDataAttrs();
const violations = listSourceFiles(join(HERE, 'soma', 'components')).flatMap((file) => {
const attrs = collectDataAttrs(readFileSync(file, 'utf8'));
return [...attrs]
.filter((attr) => !knownAttrs.has(attr))
.map((attr) => `${file.replace(REPO_ROOT, '')}: ${attr}`);
});
expect(violations).toEqual([]);
});
it('guards component translation namespaces as kebab-case', () => {
const sourceRoots = [join(HERE, 'soma', 'components'), join(HERE, 'morfo', 'components')];
const pathViolations = sourceRoots.flatMap((root) =>
listSourceFiles(root).flatMap((file) =>
collectComponentLangPathViolations(readFileSync(file, 'utf8'), file)
)
);
expect(pathViolations).toEqual([]);
expect(collectLangCatalogKeyViolations(commonLangs, 'common')).toEqual([]);
});
it('events/sema uses events as service name and requires dom or projector for visual projection', () => {
const factory = defineEngineSemantic({ visual: false });
expect(factory.name).toBe(UIX_LAYER_CONTRACTS.events.publicServiceName);
// Derived from the table, never hardcoded: `sound` is in there because the
// factory forwards it as `soundEngine` (single AudioContext in attach).
expect(factory.serviceDependencies).toEqual(UIX_LAYER_CONTRACTS.events.serviceDependencies);
expect(UIX_LAYER_CONTRACTS.events.morfoPayloadName).toBe('semantic');
expect(UIX_LAYER_CONTRACTS.events.requiresOneOfForVisualProjection).toEqual([
'dom',
'projector'
]);
expect(() => new EngineSemantic()).toThrow(SemaConfigError);
expect(() => new EngineSemantic({ visual: false })).not.toThrow();
});
it('guards Sema production code from direct global listeners and console output', () => {
const violations = listSourceFiles(join(HERE, 'sema'))
.filter((file) => {
const source = stripComments(readFileSync(file, 'utf8'));
return /(?:document|window)\.(?:addEventListener|removeEventListener)\(|console\./.test(
source
);
})
.map((file) => file.replace(REPO_ROOT, ''));
expect(violations).toEqual([]);
});
it('ActiveEidos is the visual runtime and does not recreate an engine layer', () => {
expect(UIX_LAYER_CONTRACTS.activeEidos.publicRuntime).toBe('ActiveEidos');
expect(existsSync(new URL('./eidos/engine', import.meta.url))).toBe(false);
expect(existsSync(new URL('./eidos/core/headless', import.meta.url))).toBe(false);
expect(UIX_LAYER_CONTRACTS.activeEidos.constructionOptionalServices).toEqual([
'langs',
'format',
'prefs'
]);
expect(UIX_LAYER_CONTRACTS.activeEidos.requiredAccessors).toEqual(['langs', 'prefs']);
expect(UIX_LAYER_CONTRACTS.activeEidos.optionalAccessors).toEqual(['format']);
expect(() => createActiveEidos({ preferences: preferencesHarness() })).toThrow(
ActiveEidosConfigError
);
const eidos = createActiveEidos({
preferences: preferencesHarness(),
applyDom: false
});
expect(eidos.renderStaticCss()).toContain('--size-md-control-height');
expect(() => eidos.prefs).toThrow(ActiveEidosConfigError);
expect(eidos.format).toBeUndefined();
eidos.dispose();
});
it('guards Eidos public surface from wrapper/engine reintroduction', () => {
const eidosRoot = join(HERE, 'eidos');
const violations = grepSources(
eidosRoot,
/\bEngineEidos\b|\bclass\s+Eidos\b|\bEidos\.create\(|from ['"].*\/engine/
);
expect(violations).toEqual([]);
});
it('guards UIX docs shell from writing visual prefs through ActivePrefs', () => {
const violations = grepSources(
join(REPO_ROOT, 'web', 'routes', 'uix'),
/prefs\.theme|setIntent\(['"]theme['"]/
);
expect(violations).toEqual([]);
});
it('Morfo declares text slots as absolute idlangrefs (texts:) — catalog lives outside the morfo', () => {
_resetMorfoRegistryForTesting();
try {
const sample: Morfo = {
name: 'ContractSample',
kebab: 'contract-sample',
scope: ['soma'],
texts: {
label: '#?components.contract-sample.label|Contract'
},
parts: [
{
name: 'Provider',
kebab: 'provider',
kind: 'public',
defaultElement: 'div',
optional: false,
data: [],
aria: [
{
attr: 'aria-label',
value: v.translationRef('#?components.contract-sample.label|Contract')
}
]
}
]
};
registerMorfo(sample);
// Morfo owns the abstract surface (text slots as idlangrefs); content
// lives in src/uix/langs/components/{kebab}.ts and is merged by
// ActiveUix into the components.* namespace.
expect(sample.texts?.label).toBe('#?components.contract-sample.label|Contract');
expect(sample.texts?.label.startsWith('#?')).toBe(true);
} finally {
_resetMorfoRegistryForTesting();
}
});
});

Powered by TurnKey Linux.