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/scripts/translations-check.ts

380 lines
13 KiB

uix: rename morfo.translations → morfo.texts (idlangref discipline) Morfo now declares text slots as absolute idlangrefs in `texts: Record<string, LangRef>`, not literal multilingual records. Multilingual content moves out of morfo entirely into per-component catalogs under `src/uix/langs/components/{kebab}.ts`, merged globally by `ActiveUix` via the new `componentLangs` barrel. Morfo stays a pure abstract surface; localization lives in its own layer. - Type: `translations?: LangNode` → `texts?: Record<string, LangRef>` in `src/uix/morfo/types.ts`. Doc and test fixtures updated. - Catalog: new `src/uix/langs/` directory with `common.ts` (was `langs.ts`), `components/` (one file per migrated morfo, plus a barrel), and an aggregate `uixLangs`. `ActiveUix` registers both namespaces at boot. - Registry: `registerMorfo` no longer carries a translations fingerprint and no longer extends `ActiveLangs` dynamically. `connectMorfoTranslations` and `MorfoTranslationTarget` deleted from the public morfo barrel. - Validation: schema.ts drops the relative-translationRef-in-morfo.translations check; catalog presence is enforced by the new `scripts/translations-check.ts` (wired as `npm run translations:check`). - Audit: `scripts/component-audit.ts` rule A-1.3 now validates `texts.label` idlangref shape (`#?components.{kebab}.label|Fallback`) and explicitly errors on any surviving legacy `translations:` field. - Migration: `scripts/migrate-translations-to-texts.ts` is the one-off Node script that produced the 42 morfo rewrites and 42 catalog files. - Tests: `morfo/registry.test.ts` rewritten around the simpler contract; `active-uix.svelte.test.ts` reflects boot-time catalog availability instead of lazy per-morfo extension. Verification: `npm run check` (0 errors), `npm run translations:check` (0/0), `npm run morfo:check` (36/36 routed). The 4 pre-existing failures in `test` are unrelated (date-picker types, css regen, range-calendar clear, color contract). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
/**
* translations-check — validate the texts/idlangref discipline across UIX.
*
* Runs four independent checks and exits non-zero if any of them fails:
*
* 1. **Morfo `texts:` shape**: every value is an absolute idlangref
* (`'#?…|fallback'`). Component-scoped refs (`#?components.{kebab}.…`) must
* match the morfo's own `kebab`.
*
* 2. **Component catalog coverage**: every component-scoped idlangref points
* into `src/uix/langs/components/{kebab}.ts`. The dotted path must resolve
* to a leaf `LangRecord` (object whose values are strings).
*
* 3. **Common catalog coverage**: every `#?common.…` idlangref points into
* `src/uix/langs/common.ts`.
*
* 4. **Orphan detection**: every leaf in the component catalogs is referenced
* by at least one `morfo.texts` entry OR a soma/eidos source file. Orphan
* entries are surfaced as warnings (not errors) so consumers can remove
* them safely.
*
* Also enforces:
* - No raw `translations:` field survives anywhere under `src/uix/morfo/`.
* - No source file (.ts / .svelte under src/uix) declares an idlangref to a
* nonexistent path.
*
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
* The implementation evaluates each `src/uix/langs/components/*.ts` and the
* common catalog via a controlled `Function(...)` call; those files are pure
* data so this is safe in CI.
uix: rename morfo.translations → morfo.texts (idlangref discipline) Morfo now declares text slots as absolute idlangrefs in `texts: Record<string, LangRef>`, not literal multilingual records. Multilingual content moves out of morfo entirely into per-component catalogs under `src/uix/langs/components/{kebab}.ts`, merged globally by `ActiveUix` via the new `componentLangs` barrel. Morfo stays a pure abstract surface; localization lives in its own layer. - Type: `translations?: LangNode` → `texts?: Record<string, LangRef>` in `src/uix/morfo/types.ts`. Doc and test fixtures updated. - Catalog: new `src/uix/langs/` directory with `common.ts` (was `langs.ts`), `components/` (one file per migrated morfo, plus a barrel), and an aggregate `uixLangs`. `ActiveUix` registers both namespaces at boot. - Registry: `registerMorfo` no longer carries a translations fingerprint and no longer extends `ActiveLangs` dynamically. `connectMorfoTranslations` and `MorfoTranslationTarget` deleted from the public morfo barrel. - Validation: schema.ts drops the relative-translationRef-in-morfo.translations check; catalog presence is enforced by the new `scripts/translations-check.ts` (wired as `npm run translations:check`). - Audit: `scripts/component-audit.ts` rule A-1.3 now validates `texts.label` idlangref shape (`#?components.{kebab}.label|Fallback`) and explicitly errors on any surviving legacy `translations:` field. - Migration: `scripts/migrate-translations-to-texts.ts` is the one-off Node script that produced the 42 morfo rewrites and 42 catalog files. - Tests: `morfo/registry.test.ts` rewritten around the simpler contract; `active-uix.svelte.test.ts` reflects boot-time catalog availability instead of lazy per-morfo extension. Verification: `npm run check` (0 errors), `npm run translations:check` (0/0), `npm run morfo:check` (36/36 routed). The 4 pre-existing failures in `test` are unrelated (date-picker types, css regen, range-calendar clear, color contract). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
*/
import { readdirSync, readFileSync, statSync } from 'node:fs';
import { join, dirname, basename } from 'node:path';
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
import { fileURLToPath } from 'node:url';
uix: rename morfo.translations → morfo.texts (idlangref discipline) Morfo now declares text slots as absolute idlangrefs in `texts: Record<string, LangRef>`, not literal multilingual records. Multilingual content moves out of morfo entirely into per-component catalogs under `src/uix/langs/components/{kebab}.ts`, merged globally by `ActiveUix` via the new `componentLangs` barrel. Morfo stays a pure abstract surface; localization lives in its own layer. - Type: `translations?: LangNode` → `texts?: Record<string, LangRef>` in `src/uix/morfo/types.ts`. Doc and test fixtures updated. - Catalog: new `src/uix/langs/` directory with `common.ts` (was `langs.ts`), `components/` (one file per migrated morfo, plus a barrel), and an aggregate `uixLangs`. `ActiveUix` registers both namespaces at boot. - Registry: `registerMorfo` no longer carries a translations fingerprint and no longer extends `ActiveLangs` dynamically. `connectMorfoTranslations` and `MorfoTranslationTarget` deleted from the public morfo barrel. - Validation: schema.ts drops the relative-translationRef-in-morfo.translations check; catalog presence is enforced by the new `scripts/translations-check.ts` (wired as `npm run translations:check`). - Audit: `scripts/component-audit.ts` rule A-1.3 now validates `texts.label` idlangref shape (`#?components.{kebab}.label|Fallback`) and explicitly errors on any surviving legacy `translations:` field. - Migration: `scripts/migrate-translations-to-texts.ts` is the one-off Node script that produced the 42 morfo rewrites and 42 catalog files. - Tests: `morfo/registry.test.ts` rewritten around the simpler contract; `active-uix.svelte.test.ts` reflects boot-time catalog availability instead of lazy per-morfo extension. Verification: `npm run check` (0 errors), `npm run translations:check` (0/0), `npm run morfo:check` (36/36 routed). The 4 pre-existing failures in `test` are unrelated (date-picker types, css regen, range-calendar clear, color contract). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
const HERE = dirname(fileURLToPath(import.meta.url));
const REPO = join(HERE, '..');
const UIX = join(REPO, 'src', 'uix');
const MORFO_DIR = join(UIX, 'morfo', 'components');
const LANGS_COMMON = join(UIX, 'langs', 'common.ts');
const LANGS_COMPONENTS = join(UIX, 'langs', 'components');
type LangLeaf = Record<string, string>;
type LangBranch = { [key: string]: LangLeaf | LangBranch };
interface Finding {
severity: 'error' | 'warn';
source: string;
message: string;
}
const findings: Finding[] = [];
function err(source: string, message: string) {
findings.push({ severity: 'error', source, message });
}
function warn(source: string, message: string) {
findings.push({ severity: 'warn', source, message });
}
// ── Catalog evaluation ────────────────────────────────────────────────────
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
function evalCatalogFile(file: string, exportName: string): LangBranch {
const source = readFileSync(file, 'utf8');
const stripped = source
.replace(/\/\/[^\n]*/g, '')
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/,(\s*[}\]])/g, '$1');
const re = new RegExp(`export\\s+const\\s+${exportName}\\s*=\\s*(\\{[\\s\\S]*?\\})\\s+satisfies`);
const match = stripped.match(re);
if (!match) throw new Error(`No export const ${exportName} found in ${file}`);
// eslint-disable-next-line @typescript-eslint/no-implied-eval
const fn = new Function(`return (${match[1]});`);
return fn() as LangBranch;
uix: rename morfo.translations → morfo.texts (idlangref discipline) Morfo now declares text slots as absolute idlangrefs in `texts: Record<string, LangRef>`, not literal multilingual records. Multilingual content moves out of morfo entirely into per-component catalogs under `src/uix/langs/components/{kebab}.ts`, merged globally by `ActiveUix` via the new `componentLangs` barrel. Morfo stays a pure abstract surface; localization lives in its own layer. - Type: `translations?: LangNode` → `texts?: Record<string, LangRef>` in `src/uix/morfo/types.ts`. Doc and test fixtures updated. - Catalog: new `src/uix/langs/` directory with `common.ts` (was `langs.ts`), `components/` (one file per migrated morfo, plus a barrel), and an aggregate `uixLangs`. `ActiveUix` registers both namespaces at boot. - Registry: `registerMorfo` no longer carries a translations fingerprint and no longer extends `ActiveLangs` dynamically. `connectMorfoTranslations` and `MorfoTranslationTarget` deleted from the public morfo barrel. - Validation: schema.ts drops the relative-translationRef-in-morfo.translations check; catalog presence is enforced by the new `scripts/translations-check.ts` (wired as `npm run translations:check`). - Audit: `scripts/component-audit.ts` rule A-1.3 now validates `texts.label` idlangref shape (`#?components.{kebab}.label|Fallback`) and explicitly errors on any surviving legacy `translations:` field. - Migration: `scripts/migrate-translations-to-texts.ts` is the one-off Node script that produced the 42 morfo rewrites and 42 catalog files. - Tests: `morfo/registry.test.ts` rewritten around the simpler contract; `active-uix.svelte.test.ts` reflects boot-time catalog availability instead of lazy per-morfo extension. Verification: `npm run check` (0 errors), `npm run translations:check` (0/0), `npm run morfo:check` (36/36 routed). The 4 pre-existing failures in `test` are unrelated (date-picker types, css regen, range-calendar clear, color contract). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
}
function kebabToCamel(kebab: string): string {
return kebab.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
}
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
function loadCommonCatalog(): LangBranch {
uix: rename morfo.translations → morfo.texts (idlangref discipline) Morfo now declares text slots as absolute idlangrefs in `texts: Record<string, LangRef>`, not literal multilingual records. Multilingual content moves out of morfo entirely into per-component catalogs under `src/uix/langs/components/{kebab}.ts`, merged globally by `ActiveUix` via the new `componentLangs` barrel. Morfo stays a pure abstract surface; localization lives in its own layer. - Type: `translations?: LangNode` → `texts?: Record<string, LangRef>` in `src/uix/morfo/types.ts`. Doc and test fixtures updated. - Catalog: new `src/uix/langs/` directory with `common.ts` (was `langs.ts`), `components/` (one file per migrated morfo, plus a barrel), and an aggregate `uixLangs`. `ActiveUix` registers both namespaces at boot. - Registry: `registerMorfo` no longer carries a translations fingerprint and no longer extends `ActiveLangs` dynamically. `connectMorfoTranslations` and `MorfoTranslationTarget` deleted from the public morfo barrel. - Validation: schema.ts drops the relative-translationRef-in-morfo.translations check; catalog presence is enforced by the new `scripts/translations-check.ts` (wired as `npm run translations:check`). - Audit: `scripts/component-audit.ts` rule A-1.3 now validates `texts.label` idlangref shape (`#?components.{kebab}.label|Fallback`) and explicitly errors on any surviving legacy `translations:` field. - Migration: `scripts/migrate-translations-to-texts.ts` is the one-off Node script that produced the 42 morfo rewrites and 42 catalog files. - Tests: `morfo/registry.test.ts` rewritten around the simpler contract; `active-uix.svelte.test.ts` reflects boot-time catalog availability instead of lazy per-morfo extension. Verification: `npm run check` (0 errors), `npm run translations:check` (0/0), `npm run morfo:check` (36/36 routed). The 4 pre-existing failures in `test` are unrelated (date-picker types, css regen, range-calendar clear, color contract). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
return evalCatalogFile(LANGS_COMMON, 'commonLangs');
}
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
function loadComponentCatalogs(): Map<string, LangBranch> {
uix: rename morfo.translations → morfo.texts (idlangref discipline) Morfo now declares text slots as absolute idlangrefs in `texts: Record<string, LangRef>`, not literal multilingual records. Multilingual content moves out of morfo entirely into per-component catalogs under `src/uix/langs/components/{kebab}.ts`, merged globally by `ActiveUix` via the new `componentLangs` barrel. Morfo stays a pure abstract surface; localization lives in its own layer. - Type: `translations?: LangNode` → `texts?: Record<string, LangRef>` in `src/uix/morfo/types.ts`. Doc and test fixtures updated. - Catalog: new `src/uix/langs/` directory with `common.ts` (was `langs.ts`), `components/` (one file per migrated morfo, plus a barrel), and an aggregate `uixLangs`. `ActiveUix` registers both namespaces at boot. - Registry: `registerMorfo` no longer carries a translations fingerprint and no longer extends `ActiveLangs` dynamically. `connectMorfoTranslations` and `MorfoTranslationTarget` deleted from the public morfo barrel. - Validation: schema.ts drops the relative-translationRef-in-morfo.translations check; catalog presence is enforced by the new `scripts/translations-check.ts` (wired as `npm run translations:check`). - Audit: `scripts/component-audit.ts` rule A-1.3 now validates `texts.label` idlangref shape (`#?components.{kebab}.label|Fallback`) and explicitly errors on any surviving legacy `translations:` field. - Migration: `scripts/migrate-translations-to-texts.ts` is the one-off Node script that produced the 42 morfo rewrites and 42 catalog files. - Tests: `morfo/registry.test.ts` rewritten around the simpler contract; `active-uix.svelte.test.ts` reflects boot-time catalog availability instead of lazy per-morfo extension. Verification: `npm run check` (0 errors), `npm run translations:check` (0/0), `npm run morfo:check` (36/36 routed). The 4 pre-existing failures in `test` are unrelated (date-picker types, css regen, range-calendar clear, color contract). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
const map = new Map<string, LangBranch>();
const files = readdirSync(LANGS_COMPONENTS).filter(
(f) => f.endsWith('.ts') && f !== 'index.ts'
);
for (const file of files) {
const kebab = file.replace(/\.ts$/, '');
const exportName = `${kebabToCamel(kebab)}Langs`;
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
const data = evalCatalogFile(join(LANGS_COMPONENTS, file), exportName);
uix: rename morfo.translations → morfo.texts (idlangref discipline) Morfo now declares text slots as absolute idlangrefs in `texts: Record<string, LangRef>`, not literal multilingual records. Multilingual content moves out of morfo entirely into per-component catalogs under `src/uix/langs/components/{kebab}.ts`, merged globally by `ActiveUix` via the new `componentLangs` barrel. Morfo stays a pure abstract surface; localization lives in its own layer. - Type: `translations?: LangNode` → `texts?: Record<string, LangRef>` in `src/uix/morfo/types.ts`. Doc and test fixtures updated. - Catalog: new `src/uix/langs/` directory with `common.ts` (was `langs.ts`), `components/` (one file per migrated morfo, plus a barrel), and an aggregate `uixLangs`. `ActiveUix` registers both namespaces at boot. - Registry: `registerMorfo` no longer carries a translations fingerprint and no longer extends `ActiveLangs` dynamically. `connectMorfoTranslations` and `MorfoTranslationTarget` deleted from the public morfo barrel. - Validation: schema.ts drops the relative-translationRef-in-morfo.translations check; catalog presence is enforced by the new `scripts/translations-check.ts` (wired as `npm run translations:check`). - Audit: `scripts/component-audit.ts` rule A-1.3 now validates `texts.label` idlangref shape (`#?components.{kebab}.label|Fallback`) and explicitly errors on any surviving legacy `translations:` field. - Migration: `scripts/migrate-translations-to-texts.ts` is the one-off Node script that produced the 42 morfo rewrites and 42 catalog files. - Tests: `morfo/registry.test.ts` rewritten around the simpler contract; `active-uix.svelte.test.ts` reflects boot-time catalog availability instead of lazy per-morfo extension. Verification: `npm run check` (0 errors), `npm run translations:check` (0/0), `npm run morfo:check` (36/36 routed). The 4 pre-existing failures in `test` are unrelated (date-picker types, css regen, range-calendar clear, color contract). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
map.set(kebab, data);
}
return map;
}
// ── Path resolution inside a catalog branch ───────────────────────────────
function resolvePath(node: LangBranch, path: string[]): unknown {
let cur: unknown = node;
for (const seg of path) {
if (cur == null || typeof cur !== 'object') return undefined;
cur = (cur as Record<string, unknown>)[seg];
}
return cur;
}
function isLeafRecord(v: unknown): v is LangLeaf {
return (
typeof v === 'object' &&
v !== null &&
!Array.isArray(v) &&
Object.values(v as Record<string, unknown>).every((x) => typeof x === 'string')
);
}
function collectLeafPaths(node: unknown, prefix: string[] = []): string[][] {
if (isLeafRecord(node)) return [prefix];
if (typeof node !== 'object' || node === null) return [];
return Object.entries(node).flatMap(([k, v]) => collectLeafPaths(v, [...prefix, k]));
}
// ── Morfo file scanning ───────────────────────────────────────────────────
interface MorfoTextEntry {
file: string;
kebab: string;
textKey: string;
rawRef: string;
}
function extractKebab(source: string): string | null {
const m = source.match(/kebab:\s*'([^']+)'/);
return m?.[1] ?? null;
}
function extractTextsBlock(source: string): string | null {
const m = source.match(/^\ttexts:\s*\{/m);
if (!m) return null;
const open = m.index! + m[0].length - 1;
let depth = 1;
let i = open + 1;
while (i < source.length && depth > 0) {
if (source[i] === '{') depth++;
else if (source[i] === '}') depth--;
i++;
}
if (depth !== 0) return null;
return source.slice(open + 1, i - 1);
}
function scanMorfoTexts(): MorfoTextEntry[] {
const out: MorfoTextEntry[] = [];
const files = readdirSync(MORFO_DIR).filter((f) => f.endsWith('.ts') && !f.endsWith('.test.ts'));
for (const file of files) {
const path = join(MORFO_DIR, file);
const source = readFileSync(path, 'utf8');
// Hard ban: no `translations:` field at root indent.
if (/^\ttranslations:\s*\{/m.test(source)) {
err(path, 'Legacy `translations:` field present. Replace with `texts:` idlangrefs.');
}
const kebab = extractKebab(source);
if (!kebab) continue;
const block = extractTextsBlock(source);
if (!block) continue;
// Walk key: '#?…' or "key": '#?…' lines. The value is always a single
// quoted string per the migrated layout.
const lineRe = /['"]?([\w-]+(?:\.[\w-]+)*)['"]?\s*:\s*['"`](#\?[^'"`]+)['"`]/g;
let lm: RegExpExecArray | null;
while ((lm = lineRe.exec(block))) {
out.push({ file: path, kebab, textKey: lm[1], rawRef: lm[2] });
}
}
return out;
}
// ── Cross-source idlangref scanning ───────────────────────────────────────
function listSourceFiles(root: string): string[] {
if (!statSync(root).isDirectory()) return [];
const out: string[] = [];
for (const entry of readdirSync(root)) {
const path = join(root, entry);
const stat = statSync(path);
if (stat.isDirectory()) {
out.push(...listSourceFiles(path));
} else if (
(entry.endsWith('.ts') || entry.endsWith('.svelte')) &&
!entry.endsWith('.test.ts') &&
!entry.endsWith('.test.svelte')
) {
out.push(path);
}
}
return out;
}
function stripCommentsAndStrings(source: string): string {
// Drop // line comments, /* */ block comments, and `<!-- -->` HTML comments
// inside .svelte. Templates with `{var}` placeholders inside comments would
// otherwise produce false-positive idlangref scans.
return source
.replace(/\/\/[^\n]*/g, '')
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/<!--[\s\S]*?-->/g, '');
}
function scanIdlangrefsInSource(): Map<string, Set<string>> {
const occurrences = new Map<string, Set<string>>(); // ref → files
const files = listSourceFiles(UIX);
// Only match refs with no `{` `}` `:` whitespace — those are real idlangrefs,
// not doc placeholders like `#?components.{kebab}.{key}`.
const re = /#\?([A-Za-z][\w.-]*)(?:\|([^'"`\n]*))?/g;
for (const file of files) {
const source = stripCommentsAndStrings(readFileSync(file, 'utf8'));
let m: RegExpExecArray | null;
while ((m = re.exec(source))) {
const path = m[1];
if (!occurrences.has(path)) occurrences.set(path, new Set());
occurrences.get(path)!.add(file);
}
}
return occurrences;
}
// ── Checks ────────────────────────────────────────────────────────────────
function checkMorfoTextsShape(
entries: MorfoTextEntry[],
components: Map<string, LangBranch>,
common: LangBranch
): void {
for (const { file, kebab, textKey, rawRef } of entries) {
if (!rawRef.startsWith('#?')) {
err(
file,
`texts.${textKey}: must be an absolute idlangref starting with '#?'. Got ${JSON.stringify(rawRef)}.`
);
continue;
}
const body = rawRef.slice(2);
const pipe = body.indexOf('|');
const refPath = pipe === -1 ? body : body.slice(0, pipe);
const fallback = pipe === -1 ? undefined : body.slice(pipe + 1);
if (!fallback) {
warn(file, `texts.${textKey}: idlangref ${rawRef} has no fallback after '|'.`);
}
const segments = refPath.split('.');
if (segments[0] === 'components') {
if (segments[1] !== kebab) {
err(
file,
`texts.${textKey}: ref points to ${segments[1]} but the morfo kebab is ${kebab}.`
);
continue;
}
const branch = components.get(kebab);
if (!branch) {
err(
file,
`texts.${textKey}: no catalog found at src/uix/langs/components/${kebab}.ts.`
);
continue;
}
const leaf = resolvePath(branch, segments.slice(2));
if (!isLeafRecord(leaf)) {
err(
file,
`texts.${textKey}: ref ${refPath} does not resolve to a LangRecord leaf in components/${kebab}.ts.`
);
}
} else if (segments[0] === 'common') {
const leaf = resolvePath(common, segments.slice(1));
if (!isLeafRecord(leaf)) {
err(
file,
`texts.${textKey}: ref ${refPath} does not resolve to a LangRecord leaf in langs/common.ts.`
);
}
} else {
// app-scoped refs like `#?app.foo.bar`: cannot validate inside UIX.
warn(
file,
`texts.${textKey}: ref ${refPath} is outside common/components; not validated by UIX.`
);
}
}
}
function checkComponentCatalogOrphans(
components: Map<string, LangBranch>,
morfoEntries: MorfoTextEntry[],
sourceOccurrences: Map<string, Set<string>>
): void {
const referenced = new Set<string>();
for (const e of morfoEntries) {
const refPath = e.rawRef.slice(2).split('|')[0];
referenced.add(refPath);
}
for (const path of sourceOccurrences.keys()) referenced.add(path);
for (const [kebab, branch] of components) {
const leaves = collectLeafPaths(branch);
for (const leafPath of leaves) {
const full = `components.${kebab}.${leafPath.join('.')}`;
if (!referenced.has(full)) {
warn(
join(LANGS_COMPONENTS, `${kebab}.ts`),
`Orphan: ${full} is not referenced by any morfo.texts entry or source file.`
);
}
}
}
}
function checkSourceIdlangrefs(
occurrences: Map<string, Set<string>>,
components: Map<string, LangBranch>,
common: LangBranch
): void {
for (const [path, files] of occurrences) {
const segments = path.split('.');
if (segments[0] === 'components') {
const kebab = segments[1];
const branch = components.get(kebab);
if (!branch) continue; // not a UIX-owned component
const leaf = resolvePath(branch, segments.slice(2));
if (!isLeafRecord(leaf)) {
for (const file of files) {
err(file, `idlangref ${path} does not resolve to a leaf in components/${kebab}.ts.`);
}
}
} else if (segments[0] === 'common') {
const leaf = resolvePath(common, segments.slice(1));
if (!isLeafRecord(leaf)) {
for (const file of files) {
err(file, `idlangref ${path} does not resolve to a leaf in langs/common.ts.`);
}
}
}
}
}
// ── Main ──────────────────────────────────────────────────────────────────
revert: deshacer la auditoría entera — se hizo sin leer la doctrina Revert de los 7 commits de la sesión del 2026-07-29/30: 352ca8bbe style(soma): formato Prettier en el test de gradient-picker 17a1f167b test(soma): chat-list e70397fca test(soma): field-langs y gradient-picker c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados feb4a8424 test(soma): los primeros providers que no tenían red f8e35b8fd fix(morfo): el eje intent/color c39170abb fix(uix): la auditoría del sistema `4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta. ## Por qué se revierte todo y no una parte La instrucción de partida era «audita el sistema, **para ello previamente lee toda la documentación**». No se leyó. Se auditó primero y se justificó después, y eso contaminó el conjunto, no unos commits concretos: - Tres hallazgos del informe eran FALSOS, todos de la misma forma — heurísticas de una sola vía dadas por hechas sin abrir el código: D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de `defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` / `rotate-align` sí están implementados, co-locados en el directorio del padre); «29 morfos con `kind:'public'` irreal» (medía si existe `<Componente.Parte>` e ignoraba que un primitivo de API plana compone por props y snippets). - `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior. - Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la primera regla de propiedad de `architecture/active-uix.md`: «Only composition roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents: they receive them from `ActiveUix`.» El arranque real son tres líneas (`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas. - Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus (`testing-and-tooling.md` dice que los tests de provider son convención, no guard; la regla 1 zanja el arnés). Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de uniones de props, que hacía compilar `<Avatar color="nonsense">`; que `translations:check` crashease en cada ejecución de su historia). Nada se pierde: los commits siguen en la historia y se recuperan con `cherry-pick`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
function main() {
const common = loadCommonCatalog();
const components = loadComponentCatalogs();
uix: rename morfo.translations → morfo.texts (idlangref discipline) Morfo now declares text slots as absolute idlangrefs in `texts: Record<string, LangRef>`, not literal multilingual records. Multilingual content moves out of morfo entirely into per-component catalogs under `src/uix/langs/components/{kebab}.ts`, merged globally by `ActiveUix` via the new `componentLangs` barrel. Morfo stays a pure abstract surface; localization lives in its own layer. - Type: `translations?: LangNode` → `texts?: Record<string, LangRef>` in `src/uix/morfo/types.ts`. Doc and test fixtures updated. - Catalog: new `src/uix/langs/` directory with `common.ts` (was `langs.ts`), `components/` (one file per migrated morfo, plus a barrel), and an aggregate `uixLangs`. `ActiveUix` registers both namespaces at boot. - Registry: `registerMorfo` no longer carries a translations fingerprint and no longer extends `ActiveLangs` dynamically. `connectMorfoTranslations` and `MorfoTranslationTarget` deleted from the public morfo barrel. - Validation: schema.ts drops the relative-translationRef-in-morfo.translations check; catalog presence is enforced by the new `scripts/translations-check.ts` (wired as `npm run translations:check`). - Audit: `scripts/component-audit.ts` rule A-1.3 now validates `texts.label` idlangref shape (`#?components.{kebab}.label|Fallback`) and explicitly errors on any surviving legacy `translations:` field. - Migration: `scripts/migrate-translations-to-texts.ts` is the one-off Node script that produced the 42 morfo rewrites and 42 catalog files. - Tests: `morfo/registry.test.ts` rewritten around the simpler contract; `active-uix.svelte.test.ts` reflects boot-time catalog availability instead of lazy per-morfo extension. Verification: `npm run check` (0 errors), `npm run translations:check` (0/0), `npm run morfo:check` (36/36 routed). The 4 pre-existing failures in `test` are unrelated (date-picker types, css regen, range-calendar clear, color contract). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
const morfoEntries = scanMorfoTexts();
const sourceOccurrences = scanIdlangrefsInSource();
checkMorfoTextsShape(morfoEntries, components, common);
checkSourceIdlangrefs(sourceOccurrences, components, common);
checkComponentCatalogOrphans(components, morfoEntries, sourceOccurrences);
const errors = findings.filter((f) => f.severity === 'error');
const warnings = findings.filter((f) => f.severity === 'warn');
for (const f of findings) {
const rel = f.source.replace(REPO + '\\', '').replace(REPO + '/', '');
const tag = f.severity === 'error' ? 'ERROR' : 'WARN';
console.log(`${tag} ${rel} ${f.message}`);
}
console.log();
console.log(
`Summary: ${morfoEntries.length} morfo text refs · ${components.size} component catalogs · ${errors.length} errors · ${warnings.length} warnings.`
);
if (errors.length > 0) process.exit(1);
}
main();

Powered by TurnKey Linux.