From e2ba371bebb467f6108cf0f5465faa03cc70bcec Mon Sep 17 00:00:00 2001 From: dev Date: Thu, 14 May 2026 22:12:00 +0200 Subject: [PATCH] Add UIX architecture regression guards --- continue.md | 5 ++++ src/uix/contracts.test.ts | 56 ++++++++++++++++++++++++++++++++++----- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/continue.md b/continue.md index 6bd0030bf..18718e13a 100644 --- a/continue.md +++ b/continue.md @@ -29,6 +29,9 @@ Actualizacion 2026-05-14: - La shell `/uix` ya no carga fuentes remotas; reutiliza las fuentes locales de Eidos (`Instrument Sans`, `Azeret Mono`) para que las validaciones Playwright no dependan de red externa. +- `src/uix/contracts.test.ts` ahora incluye guardias de regresion para que + `ActiveUix` no vuelva a bootear Soma/Eidos, los providers Soma no usen + `registerContract/registerPart`, y la shell `/uix` no escriba `prefs.theme`. - Commits nuevos empujados: - `694eb5c5` — `Clarify prefs projection contract` - `223cdf9e` — `Align sema docs with channel ownership` @@ -40,11 +43,13 @@ Actualizacion 2026-05-14: - `4233be62` — `Update UIX continuation notes` - `2df58074` — `Use local fonts in UIX docs` - `8ad04714` — `Refresh UIX architecture docs` + - commit actual — `Add UIX architecture regression guards` - Validado de nuevo: - `npx vitest run src/uix/contracts.test.ts src/arts/prefs/test/dom-projection.test.ts src/uix/active-uix/active-uix.svelte.test.ts src/uix/eidos/active-eidos.test.ts src/uix/soma/runtime.svelte.test.ts` -> 79 tests OK. - `npx vitest run src/uix/sema/engine.test.ts src/uix/soma/runtime.svelte.test.ts` -> 48 tests OK. - `npx vitest run src/uix/eidos/active-eidos-config.test.ts src/uix/eidos/generated-css.test.ts src/uix/eidos/active-eidos.test.ts src/uix/contracts.test.ts` -> 52 tests OK. - `npm run check` -> 0 errores, 0 warnings. + - `npx vitest run src/uix/contracts.test.ts` -> 12 tests OK. - Playwright `/uix`: toggle dark actualiza `html[data-theme="base-dark"]`, `html[data-mode="dark"]` y mantiene `html[data-density="comfortable"]`; sin `pageerror`, sin errores de consola y sin `requestfailed`. diff --git a/src/uix/contracts.test.ts b/src/uix/contracts.test.ts index 37b69bd1b..9217f7fe4 100644 --- a/src/uix/contracts.test.ts +++ b/src/uix/contracts.test.ts @@ -1,15 +1,13 @@ // @vitest-environment jsdom -import { existsSync } from 'node:fs'; +import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'; +import { dirname, join } 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 { createActivePrefs, createActivePrefsDomProjection, standardPrefsDimensions } from '$prefs'; import type { LangNode } from '$libs/langs'; import { state } from '$libs/reactive'; import { @@ -43,6 +41,9 @@ const prefsProjectionSchema = standardPrefsDimensions({ currency: 'EUR' } }); +const HERE = dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = join(HERE, '..', '..'); +const SOURCE_EXTENSIONS = ['.ts', '.svelte'] as const; function preferencesHarness() { return { @@ -53,6 +54,23 @@ function preferencesHarness() { }; } +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')}`) + ); +} + +function grepSources(root: string, pattern: RegExp): string[] { + return listSourceFiles(root).filter((file) => pattern.test(readFileSync(file, 'utf8'))); +} + describe('UIX layer contracts', () => { it('pins ActiveUix public service names and rejects legacy aliases', () => { const uix = createActiveUix({ langs: minimalLang }); @@ -109,6 +127,16 @@ describe('UIX layer contracts', () => { } }); + 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' @@ -169,6 +197,14 @@ describe('UIX layer contracts', () => { } }); + it('guards Soma components from legacy runtime registration APIs', () => { + const violations = grepSources( + join(HERE, 'soma', 'components'), + /registerContract\(|registerPart\(/ + ); + expect(violations).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); @@ -207,6 +243,14 @@ describe('UIX layer contracts', () => { eidos.dispose(); }); + 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 owns translations declaratively and only registers them into a target', () => { _resetMorfoRegistryForTesting(); const applied: Array<{ namespace: string; module: LangNode }> = [];