docs: el contrato de consumo — una app del workspace consume UIX como fuente (docs/consuming.md)
El framework no tenía ni una línea sobre cómo se consume desde fuera de su
propio árbol de demos: todo el corpus asumía acceso al repositorio. El autor
decidió que la app que viene después lo consume COMO FUENTE, en un workspace de
este repositorio, sin paquete npm ni exports ni bin. docs/consuming.md es ese
contrato, sección a sección, enlazando lo que ya tiene página propia en vez de
copiarlo: dónde vive la app (apps/<name>, un solo node_modules y una sola copia
de Svelte), los alias (resolveUixAliases en kit.alias), el modo runas por
config, la raíz de composición standalone (attach existe y no lo consume
nadie), el CSS y los assets servidos por URL (fuentes y sonidos se copian al
static/ de la app), y el boot pre-hidratación (la receta de la guía de theming
con <uix> = ../..).
docs/README.md lo enlaza en E4 y en «I want to…». La guía de theming deja de
llamar «fila abierta» al empaquetado: el consumo es un contrato de workspace.
La app base de F3 es el primer consumidor real y la verificación del contrato:
lo que no se sostenga al construirla se corrige aquí.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
---
|
|
|
|
|
|
title: Consuming the framework from an app
|
|
|
|
|
|
type: guide
|
|
|
|
|
|
audience: human + agent
|
|
|
|
|
|
authority: E4 guide — the contract an app in this repository follows to consume UIX as source
|
|
|
|
|
|
status: current
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# Consuming the framework from an app
|
|
|
|
|
|
|
|
|
|
|
|
UIX is consumed **as source, from a workspace in this repository**. There is no
|
|
|
|
|
|
npm package, no `exports` map and no `bin`: an app lives next to the framework,
|
|
|
|
|
|
imports it through the same alias table every tool in the repository reads, and
|
|
|
|
|
|
compiles it with its own build. This page is that contract, section by section.
|
|
|
|
|
|
Where a subject already has a reference page, this page links it instead of
|
|
|
|
|
|
repeating it.
|
|
|
|
|
|
|
|
|
|
|
|
## 1. Where the app lives
|
|
|
|
|
|
|
|
|
|
|
|
An app is a workspace under `apps/<name>/` with its own `package.json`, sharing
|
|
|
|
|
|
the repository's single `node_modules` and lockfile. One install, one copy of
|
|
|
|
|
|
`svelte`, `vite` and `@sveltejs/kit` for the framework and every app — two
|
|
|
|
|
|
copies of Svelte would mean two runtimes of runes, and the framework's
|
|
|
|
|
|
`.svelte.ts` modules would not share state with the app's components.
|
|
|
|
|
|
|
|
|
|
|
|
Vite finds the workspace root from the root `package.json`'s `workspaces` field,
|
|
|
|
|
|
which is what lets the app's dev server read files under the repository's
|
|
|
|
|
|
`src/`. An app taken outside the workspace must add the repository root to its
|
|
|
|
|
|
own `server.fs.allow`.
|
|
|
|
|
|
|
feat(apps): apps/base — la primera app que consume UIX como fuente desde un workspace, y su check entra en el gate (F3 del cierre)
Hasta hoy ninguna app consumía el framework fuera de su propio árbol de demos,
y por eso la receta del boot pudo estar rota en producción sin que nadie lo
supiera. apps/base es un esqueleto SvelteKit con adapter-static estricto y
prerender, que sigue docs/consuming.md al pie de la letra: alias por
resolveUixAliases, runas por config, raíz standalone (createActiveUix +
setActiveUix + Soma + ActiveEidos con applyDom), boot compilado con SU esquema
(es/en/ar), CSP por hash con el artefacto propio, fuentes y sonidos copiados al
static/ de la app (assets:sync, con --check). La página es un AppShell con un
Toggle de modo, un Select de idioma y un Dialog.
Raíz: workspaces ["apps/*"] (una sola copia de svelte 5.55.0, vite 7.3.1,
kit 2.55.0 y esbuild 0.27.4; lock +23 líneas); apps:check = check + build +
smoke de la app, dentro del gate justo antes de la suite; .gitignore y
.prettierignore para lo generado y lo copiado.
El smoke sirve build/ por HTTP y pasa tres visitantes (es-ES, ar-EG, es-ES con
modo oscuro y movimiento reducido), cada uno con los módulos abortados (sellos
del boot) y con carga completa (0 pageerror, 0 console.error, 0 violaciones CSP,
delta 0 entre boot e hidratación). Comprueba además que el tag del boot va
DESPUÉS del <meta> CSP: encima, corre sin política y Kit no lo avisa.
Constructor, adversarial y dos rondas de cierre (Opus). El adversarial escribió
una segunda app solo con el contrato y la hizo arrancar en Chromium, Firefox y
WebKit; midió la CSP en siete variantes y tres motores, y mutó la app para ver
si el smoke muerde. Defectos cerrados:
- D1: el smoke prometía contar traducciones ausentes y en una build de
producción no hay señal (engine-langs las emite solo en DEV). Ya no lo
promete; README y consuming §8 dicen dónde se ve cada cosa.
- D2: el smoke nunca abría modo oscuro. Tercer visitante con el valor afirmado;
un pin 'light' olvidado en la raíz ahora da exit 1.
- D3: sin reset de documento el Dialog medía 409 px en un móvil de 375. La app
lleva su reset de tres reglas (doctrina vigente: el reset es de la app); que
la fundación lo traiga es fila del ledger.
- D5: los textos del esqueleto seguían en castellano bajo lang=ar. Salen de
src/lib/strings.ts por langs.ts(record), tipados.
- D6: esa garantía de tipo la sostenía una unión de idiomas escrita a mano.
Ahora hay UNA tupla (APP_LANGUAGES en prefs-schema.ts) que leen el esquema,
los textos y el Select. Visto fallar: con 'fr' en la tupla, check exit 1 con
8 errores que nombran fr; restaurado byte a byte.
- C1-C3: el contrato gana el tsconfig mínimo medido
(rewriteRelativeImportExtensions, sin él 578 errores), las rutas de import de
Soma/ActiveEidos/createThemeBaseEidosConfig y adapter-static + prerender.
Cero cambios en src/**. Huecos del framework que la app destapa, al ledger:
el Dialog modal no devuelve el foco al trigger (queda en BODY en los 3 motores:
FocusScope enfoca mientras HideOthers aún tiene el inert), Select.Value muestra
el value crudo hasta abrir el popup, los catálogos de componentes no tienen
'ar', la foundation se carga dos veces (482 KB enlazados + 426 KB inyectados
por applyDom, 5 153 tokens duplicados), guide.md llama eidos.listThemes()
dentro del hook, la regla 6 de active-uix.md está caducada, y uix.langs no
lleva el tipo del catálogo de la app.
Verificación: apps:check exit 0 (1391 ficheros 0 errores · build · smoke
`boot 27/27 · unguarded 0 · pageerror 0 · console.error 0 · csp 0 · delta 0 ·
flips 0`) · boot de la app 15 365 B, byte-idéntico en dos compilaciones, su
hash en el <meta> de build/index.html · docs:check 0/0 en 820 docs. Sobre el
lote antes de D6 (D6 no toca src/, scripts/ ni la suite): gate entero exit 0
en 357 s, check:gate 89 dentro del ledger, suite 464/464 ficheros · 5442/5442
tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
The contract is written for, and verified on, a fully prerendered static site:
|
|
|
|
|
|
`@sveltejs/adapter-static` with no `fallback`, and `export const prerender = true` in
|
|
|
|
|
|
`src/routes/+layout.ts`. The pre-hydration boot (§6) and the CSP `<meta>` are then written into
|
|
|
|
|
|
every HTML file at build time, and a page that fails to render fails the build.
|
|
|
|
|
|
|
docs: el contrato de consumo — una app del workspace consume UIX como fuente (docs/consuming.md)
El framework no tenía ni una línea sobre cómo se consume desde fuera de su
propio árbol de demos: todo el corpus asumía acceso al repositorio. El autor
decidió que la app que viene después lo consume COMO FUENTE, en un workspace de
este repositorio, sin paquete npm ni exports ni bin. docs/consuming.md es ese
contrato, sección a sección, enlazando lo que ya tiene página propia en vez de
copiarlo: dónde vive la app (apps/<name>, un solo node_modules y una sola copia
de Svelte), los alias (resolveUixAliases en kit.alias), el modo runas por
config, la raíz de composición standalone (attach existe y no lo consume
nadie), el CSS y los assets servidos por URL (fuentes y sonidos se copian al
static/ de la app), y el boot pre-hidratación (la receta de la guía de theming
con <uix> = ../..).
docs/README.md lo enlaza en E4 y en «I want to…». La guía de theming deja de
llamar «fila abierta» al empaquetado: el consumo es un contrato de workspace.
La app base de F3 es el primer consumidor real y la verificación del contrato:
lo que no se sostenga al construirla se corrige aquí.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
## 2. Aliases
|
|
|
|
|
|
|
|
|
|
|
|
The framework imports itself through `$…` and `@/` specifiers. The table lives
|
|
|
|
|
|
in ONE module at the repository root, [`uix.aliases.js`](../uix.aliases.js), and
|
|
|
|
|
|
the app reads it exactly as the framework's own configs do:
|
|
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
|
// apps/<name>/svelte.config.js
|
|
|
|
|
|
import { dirname, resolve } from 'node:path';
|
|
|
|
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
|
|
import { resolveUixAliases } from '../../uix.aliases.js';
|
|
|
|
|
|
|
|
|
|
|
|
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
kit: {
|
|
|
|
|
|
alias: resolveUixAliases(REPO_ROOT)
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
SvelteKit writes the aliases into the app's generated tsconfig and hands them to
|
|
|
|
|
|
Vite, so the app does not repeat them in `resolve.alias`. Order is load-bearing
|
|
|
|
|
|
(a longer specifier before any key that is its prefix); never re-sort or copy the
|
|
|
|
|
|
table — `src/uix/aliases.test.ts` guards it where it is written.
|
|
|
|
|
|
|
|
|
|
|
|
`@` is the framework's `src/`, not the app's. The app keeps its own `$lib`.
|
|
|
|
|
|
|
feat(apps): apps/base — la primera app que consume UIX como fuente desde un workspace, y su check entra en el gate (F3 del cierre)
Hasta hoy ninguna app consumía el framework fuera de su propio árbol de demos,
y por eso la receta del boot pudo estar rota en producción sin que nadie lo
supiera. apps/base es un esqueleto SvelteKit con adapter-static estricto y
prerender, que sigue docs/consuming.md al pie de la letra: alias por
resolveUixAliases, runas por config, raíz standalone (createActiveUix +
setActiveUix + Soma + ActiveEidos con applyDom), boot compilado con SU esquema
(es/en/ar), CSP por hash con el artefacto propio, fuentes y sonidos copiados al
static/ de la app (assets:sync, con --check). La página es un AppShell con un
Toggle de modo, un Select de idioma y un Dialog.
Raíz: workspaces ["apps/*"] (una sola copia de svelte 5.55.0, vite 7.3.1,
kit 2.55.0 y esbuild 0.27.4; lock +23 líneas); apps:check = check + build +
smoke de la app, dentro del gate justo antes de la suite; .gitignore y
.prettierignore para lo generado y lo copiado.
El smoke sirve build/ por HTTP y pasa tres visitantes (es-ES, ar-EG, es-ES con
modo oscuro y movimiento reducido), cada uno con los módulos abortados (sellos
del boot) y con carga completa (0 pageerror, 0 console.error, 0 violaciones CSP,
delta 0 entre boot e hidratación). Comprueba además que el tag del boot va
DESPUÉS del <meta> CSP: encima, corre sin política y Kit no lo avisa.
Constructor, adversarial y dos rondas de cierre (Opus). El adversarial escribió
una segunda app solo con el contrato y la hizo arrancar en Chromium, Firefox y
WebKit; midió la CSP en siete variantes y tres motores, y mutó la app para ver
si el smoke muerde. Defectos cerrados:
- D1: el smoke prometía contar traducciones ausentes y en una build de
producción no hay señal (engine-langs las emite solo en DEV). Ya no lo
promete; README y consuming §8 dicen dónde se ve cada cosa.
- D2: el smoke nunca abría modo oscuro. Tercer visitante con el valor afirmado;
un pin 'light' olvidado en la raíz ahora da exit 1.
- D3: sin reset de documento el Dialog medía 409 px en un móvil de 375. La app
lleva su reset de tres reglas (doctrina vigente: el reset es de la app); que
la fundación lo traiga es fila del ledger.
- D5: los textos del esqueleto seguían en castellano bajo lang=ar. Salen de
src/lib/strings.ts por langs.ts(record), tipados.
- D6: esa garantía de tipo la sostenía una unión de idiomas escrita a mano.
Ahora hay UNA tupla (APP_LANGUAGES en prefs-schema.ts) que leen el esquema,
los textos y el Select. Visto fallar: con 'fr' en la tupla, check exit 1 con
8 errores que nombran fr; restaurado byte a byte.
- C1-C3: el contrato gana el tsconfig mínimo medido
(rewriteRelativeImportExtensions, sin él 578 errores), las rutas de import de
Soma/ActiveEidos/createThemeBaseEidosConfig y adapter-static + prerender.
Cero cambios en src/**. Huecos del framework que la app destapa, al ledger:
el Dialog modal no devuelve el foco al trigger (queda en BODY en los 3 motores:
FocusScope enfoca mientras HideOthers aún tiene el inert), Select.Value muestra
el value crudo hasta abrir el popup, los catálogos de componentes no tienen
'ar', la foundation se carga dos veces (482 KB enlazados + 426 KB inyectados
por applyDom, 5 153 tokens duplicados), guide.md llama eidos.listThemes()
dentro del hook, la regla 6 de active-uix.md está caducada, y uix.langs no
lleva el tipo del catálogo de la app.
Verificación: apps:check exit 0 (1391 ficheros 0 errores · build · smoke
`boot 27/27 · unguarded 0 · pageerror 0 · console.error 0 · csp 0 · delta 0 ·
flips 0`) · boot de la app 15 365 B, byte-idéntico en dos compilaciones, su
hash en el <meta> de build/index.html · docs:check 0/0 en 820 docs. Sobre el
lote antes de D6 (D6 no toca src/, scripts/ ni la suite): gate entero exit 0
en 357 s, check:gate 89 dentro del ledger, suite 464/464 ficheros · 5442/5442
tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
The app's `tsconfig.json` extends the one Kit generates, and carries one option the framework's
|
|
|
|
|
|
sources need:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"extends": "./.svelte-kit/tsconfig.json",
|
|
|
|
|
|
"compilerOptions": {
|
|
|
|
|
|
"rewriteRelativeImportExtensions": true,
|
|
|
|
|
|
"allowJs": true,
|
|
|
|
|
|
"checkJs": true,
|
|
|
|
|
|
"strict": true,
|
|
|
|
|
|
"moduleResolution": "bundler"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
The framework imports some of its modules with their `.ts` extension (`scripts/generate-boot.ts`,
|
|
|
|
|
|
`src/libs/errs/`), and `svelte-check` follows the app's `vite.config.ts` into them. Without
|
|
|
|
|
|
`rewriteRelativeImportExtensions`, the check of `apps/base` reports 578 errors, every one
|
|
|
|
|
|
`An import path can only end with a '.ts' extension`; with it, none.
|
|
|
|
|
|
|
docs: el contrato de consumo — una app del workspace consume UIX como fuente (docs/consuming.md)
El framework no tenía ni una línea sobre cómo se consume desde fuera de su
propio árbol de demos: todo el corpus asumía acceso al repositorio. El autor
decidió que la app que viene después lo consume COMO FUENTE, en un workspace de
este repositorio, sin paquete npm ni exports ni bin. docs/consuming.md es ese
contrato, sección a sección, enlazando lo que ya tiene página propia en vez de
copiarlo: dónde vive la app (apps/<name>, un solo node_modules y una sola copia
de Svelte), los alias (resolveUixAliases en kit.alias), el modo runas por
config, la raíz de composición standalone (attach existe y no lo consume
nadie), el CSS y los assets servidos por URL (fuentes y sonidos se copian al
static/ de la app), y el boot pre-hidratación (la receta de la guía de theming
con <uix> = ../..).
docs/README.md lo enlaza en E4 y en «I want to…». La guía de theming deja de
llamar «fila abierta» al empaquetado: el consumo es un contrato de workspace.
La app base de F3 es el primer consumidor real y la verificación del contrato:
lo que no se sostenga al construirla se corrige aquí.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
## 3. Runes
|
|
|
|
|
|
|
|
|
|
|
|
The framework is written in Svelte 5 runes mode, and the repository enforces it
|
|
|
|
|
|
by config rather than per file. The app's `svelte.config.js` carries the same
|
|
|
|
|
|
three lines, or framework components that happen to use no rune compile in
|
|
|
|
|
|
legacy mode:
|
|
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
|
vitePlugin: {
|
|
|
|
|
|
dynamicCompileOptions: ({ filename }) =>
|
|
|
|
|
|
filename.includes('node_modules') ? undefined : { runes: true };
|
docs: el contrato de consumo — una app del workspace consume UIX como fuente (docs/consuming.md)
El framework no tenía ni una línea sobre cómo se consume desde fuera de su
propio árbol de demos: todo el corpus asumía acceso al repositorio. El autor
decidió que la app que viene después lo consume COMO FUENTE, en un workspace de
este repositorio, sin paquete npm ni exports ni bin. docs/consuming.md es ese
contrato, sección a sección, enlazando lo que ya tiene página propia en vez de
copiarlo: dónde vive la app (apps/<name>, un solo node_modules y una sola copia
de Svelte), los alias (resolveUixAliases en kit.alias), el modo runas por
config, la raíz de composición standalone (attach existe y no lo consume
nadie), el CSS y los assets servidos por URL (fuentes y sonidos se copian al
static/ de la app), y el boot pre-hidratación (la receta de la guía de theming
con <uix> = ../..).
docs/README.md lo enlaza en E4 y en «I want to…». La guía de theming deja de
llamar «fila abierta» al empaquetado: el consumo es un contrato de workspace.
La app base de F3 es el primer consumidor real y la verificación del contrato:
lo que no se sostenga al construirla se corrige aquí.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 4. The composition root
|
|
|
|
|
|
|
|
|
|
|
|
An app boots UIX in **standalone** mode: `createActiveUix` owns the services,
|
|
|
|
|
|
`setActiveUix` publishes it to the tree, `Soma.create()` opens the headless
|
|
|
|
|
|
layer and `ActiveEidos.create({ applyDom: true })` the visual one. Both are
|
|
|
|
|
|
disposed in `onDestroy`. The boot sequence, the minimum contracts and the prefs
|
|
|
|
|
|
schema are described in [`architecture/active-uix.md`](./architecture/active-uix.md);
|
|
|
|
|
|
the per-app prefs schema is the same module the pre-hydration boot compiles
|
feat(apps): apps/base — la primera app que consume UIX como fuente desde un workspace, y su check entra en el gate (F3 del cierre)
Hasta hoy ninguna app consumía el framework fuera de su propio árbol de demos,
y por eso la receta del boot pudo estar rota en producción sin que nadie lo
supiera. apps/base es un esqueleto SvelteKit con adapter-static estricto y
prerender, que sigue docs/consuming.md al pie de la letra: alias por
resolveUixAliases, runas por config, raíz standalone (createActiveUix +
setActiveUix + Soma + ActiveEidos con applyDom), boot compilado con SU esquema
(es/en/ar), CSP por hash con el artefacto propio, fuentes y sonidos copiados al
static/ de la app (assets:sync, con --check). La página es un AppShell con un
Toggle de modo, un Select de idioma y un Dialog.
Raíz: workspaces ["apps/*"] (una sola copia de svelte 5.55.0, vite 7.3.1,
kit 2.55.0 y esbuild 0.27.4; lock +23 líneas); apps:check = check + build +
smoke de la app, dentro del gate justo antes de la suite; .gitignore y
.prettierignore para lo generado y lo copiado.
El smoke sirve build/ por HTTP y pasa tres visitantes (es-ES, ar-EG, es-ES con
modo oscuro y movimiento reducido), cada uno con los módulos abortados (sellos
del boot) y con carga completa (0 pageerror, 0 console.error, 0 violaciones CSP,
delta 0 entre boot e hidratación). Comprueba además que el tag del boot va
DESPUÉS del <meta> CSP: encima, corre sin política y Kit no lo avisa.
Constructor, adversarial y dos rondas de cierre (Opus). El adversarial escribió
una segunda app solo con el contrato y la hizo arrancar en Chromium, Firefox y
WebKit; midió la CSP en siete variantes y tres motores, y mutó la app para ver
si el smoke muerde. Defectos cerrados:
- D1: el smoke prometía contar traducciones ausentes y en una build de
producción no hay señal (engine-langs las emite solo en DEV). Ya no lo
promete; README y consuming §8 dicen dónde se ve cada cosa.
- D2: el smoke nunca abría modo oscuro. Tercer visitante con el valor afirmado;
un pin 'light' olvidado en la raíz ahora da exit 1.
- D3: sin reset de documento el Dialog medía 409 px en un móvil de 375. La app
lleva su reset de tres reglas (doctrina vigente: el reset es de la app); que
la fundación lo traiga es fila del ledger.
- D5: los textos del esqueleto seguían en castellano bajo lang=ar. Salen de
src/lib/strings.ts por langs.ts(record), tipados.
- D6: esa garantía de tipo la sostenía una unión de idiomas escrita a mano.
Ahora hay UNA tupla (APP_LANGUAGES en prefs-schema.ts) que leen el esquema,
los textos y el Select. Visto fallar: con 'fr' en la tupla, check exit 1 con
8 errores que nombran fr; restaurado byte a byte.
- C1-C3: el contrato gana el tsconfig mínimo medido
(rewriteRelativeImportExtensions, sin él 578 errores), las rutas de import de
Soma/ActiveEidos/createThemeBaseEidosConfig y adapter-static + prerender.
Cero cambios en src/**. Huecos del framework que la app destapa, al ledger:
el Dialog modal no devuelve el foco al trigger (queda en BODY en los 3 motores:
FocusScope enfoca mientras HideOthers aún tiene el inert), Select.Value muestra
el value crudo hasta abrir el popup, los catálogos de componentes no tienen
'ar', la foundation se carga dos veces (482 KB enlazados + 426 KB inyectados
por applyDom, 5 153 tokens duplicados), guide.md llama eidos.listThemes()
dentro del hook, la regla 6 de active-uix.md está caducada, y uix.langs no
lleva el tipo del catálogo de la app.
Verificación: apps:check exit 0 (1391 ficheros 0 errores · build · smoke
`boot 27/27 · unguarded 0 · pageerror 0 · console.error 0 · csp 0 · delta 0 ·
flips 0`) · boot de la app 15 365 B, byte-idéntico en dos compilaciones, su
hash en el <meta> de build/index.html · docs:check 0/0 en 820 docs. Sobre el
lote antes de D6 (D6 no toca src/, scripts/ ni la suite): gate entero exit 0
en 357 s, check:gate 89 dentro del ledger, suite 464/464 ficheros · 5442/5442
tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
(§6). The root layout of `apps/base`, with the app's own imports reduced to a comment:
|
|
|
|
|
|
|
|
|
|
|
|
```svelte
|
|
|
|
|
|
<!-- apps/<name>/src/routes/+layout.svelte -->
|
|
|
|
|
|
<script lang="ts">
|
|
|
|
|
|
import '$uix/eidos/index.css';
|
|
|
|
|
|
import { onDestroy } from 'svelte';
|
|
|
|
|
|
import { createActiveUix, setActiveUix } from '$active-uix';
|
|
|
|
|
|
import { Soma } from '$soma/core/soma.svelte';
|
|
|
|
|
|
import { ActiveEidos } from '$uix/eidos';
|
|
|
|
|
|
// The app's own modules: its reset (§5), `eidosConfig` and `bootPrefsSchema` (§6).
|
|
|
|
|
|
|
|
|
|
|
|
let { children } = $props();
|
|
|
|
|
|
|
|
|
|
|
|
const uix = createActiveUix({
|
|
|
|
|
|
langs: { schema: {}, defaultLocale: 'es' },
|
|
|
|
|
|
prefs: { schema: bootPrefsSchema('es') }
|
|
|
|
|
|
});
|
|
|
|
|
|
setActiveUix(uix);
|
|
|
|
|
|
Soma.create();
|
|
|
|
|
|
|
|
|
|
|
|
const eidos = ActiveEidos.create({ applyDom: true, config: eidosConfig });
|
|
|
|
|
|
|
|
|
|
|
|
onDestroy(() => {
|
|
|
|
|
|
eidos.dispose();
|
|
|
|
|
|
uix.dispose();
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
{@render children()}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
The reset is §5; `eidosConfig` and `bootPrefsSchema` are §6. `langs.schema` is the
|
|
|
|
|
|
app's catalogue for `langs.t(path)` — the `strings` of the theming guide's snippet — and `{}`
|
|
|
|
|
|
when the app has none; the framework registers its components' own words under `components.*`
|
|
|
|
|
|
by itself. `uix.langs` is not typed with the app's catalogue, so a misspelt `t()` path
|
|
|
|
|
|
compiles, and a production build renders the raw path without a word. `apps/base` keeps its
|
|
|
|
|
|
words in records instead (`src/lib/strings.ts`, `satisfies` one string per language) and reads
|
|
|
|
|
|
them with `langs.ts(record)`, which follows the language preference the same way. The languages
|
|
|
|
|
|
are one tuple in `src/prefs-schema.ts` that the schema reads too, so a missing language, a
|
|
|
|
|
|
language added to the schema or a misspelt name fails `npm run check`.
|
docs: el contrato de consumo — una app del workspace consume UIX como fuente (docs/consuming.md)
El framework no tenía ni una línea sobre cómo se consume desde fuera de su
propio árbol de demos: todo el corpus asumía acceso al repositorio. El autor
decidió que la app que viene después lo consume COMO FUENTE, en un workspace de
este repositorio, sin paquete npm ni exports ni bin. docs/consuming.md es ese
contrato, sección a sección, enlazando lo que ya tiene página propia en vez de
copiarlo: dónde vive la app (apps/<name>, un solo node_modules y una sola copia
de Svelte), los alias (resolveUixAliases en kit.alias), el modo runas por
config, la raíz de composición standalone (attach existe y no lo consume
nadie), el CSS y los assets servidos por URL (fuentes y sonidos se copian al
static/ de la app), y el boot pre-hidratación (la receta de la guía de theming
con <uix> = ../..).
docs/README.md lo enlaza en E4 y en «I want to…». La guía de theming deja de
llamar «fila abierta» al empaquetado: el consumo es un contrato de workspace.
La app base de F3 es el primer consumidor real y la verificación del contrato:
lo que no se sostenga al construirla se corrige aquí.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
|
|
|
|
|
|
`attachActiveUix` — an external `ActiveApp` that owns its services — exists and
|
|
|
|
|
|
is tested, but no app consumes it and the pre-hydration boot does not cover it.
|
|
|
|
|
|
Build on standalone.
|
|
|
|
|
|
|
|
|
|
|
|
## 5. CSS and static assets
|
|
|
|
|
|
|
|
|
|
|
|
- **CSS.** Import `$uix/eidos/index.css` once, in the root layout. It carries the
|
|
|
|
|
|
static foundation, which a prerendered or server-rendered page needs in its
|
|
|
|
|
|
HTML so the first paint is styled before hydration.
|
feat(apps): apps/base — la primera app que consume UIX como fuente desde un workspace, y su check entra en el gate (F3 del cierre)
Hasta hoy ninguna app consumía el framework fuera de su propio árbol de demos,
y por eso la receta del boot pudo estar rota en producción sin que nadie lo
supiera. apps/base es un esqueleto SvelteKit con adapter-static estricto y
prerender, que sigue docs/consuming.md al pie de la letra: alias por
resolveUixAliases, runas por config, raíz standalone (createActiveUix +
setActiveUix + Soma + ActiveEidos con applyDom), boot compilado con SU esquema
(es/en/ar), CSP por hash con el artefacto propio, fuentes y sonidos copiados al
static/ de la app (assets:sync, con --check). La página es un AppShell con un
Toggle de modo, un Select de idioma y un Dialog.
Raíz: workspaces ["apps/*"] (una sola copia de svelte 5.55.0, vite 7.3.1,
kit 2.55.0 y esbuild 0.27.4; lock +23 líneas); apps:check = check + build +
smoke de la app, dentro del gate justo antes de la suite; .gitignore y
.prettierignore para lo generado y lo copiado.
El smoke sirve build/ por HTTP y pasa tres visitantes (es-ES, ar-EG, es-ES con
modo oscuro y movimiento reducido), cada uno con los módulos abortados (sellos
del boot) y con carga completa (0 pageerror, 0 console.error, 0 violaciones CSP,
delta 0 entre boot e hidratación). Comprueba además que el tag del boot va
DESPUÉS del <meta> CSP: encima, corre sin política y Kit no lo avisa.
Constructor, adversarial y dos rondas de cierre (Opus). El adversarial escribió
una segunda app solo con el contrato y la hizo arrancar en Chromium, Firefox y
WebKit; midió la CSP en siete variantes y tres motores, y mutó la app para ver
si el smoke muerde. Defectos cerrados:
- D1: el smoke prometía contar traducciones ausentes y en una build de
producción no hay señal (engine-langs las emite solo en DEV). Ya no lo
promete; README y consuming §8 dicen dónde se ve cada cosa.
- D2: el smoke nunca abría modo oscuro. Tercer visitante con el valor afirmado;
un pin 'light' olvidado en la raíz ahora da exit 1.
- D3: sin reset de documento el Dialog medía 409 px en un móvil de 375. La app
lleva su reset de tres reglas (doctrina vigente: el reset es de la app); que
la fundación lo traiga es fila del ledger.
- D5: los textos del esqueleto seguían en castellano bajo lang=ar. Salen de
src/lib/strings.ts por langs.ts(record), tipados.
- D6: esa garantía de tipo la sostenía una unión de idiomas escrita a mano.
Ahora hay UNA tupla (APP_LANGUAGES en prefs-schema.ts) que leen el esquema,
los textos y el Select. Visto fallar: con 'fr' en la tupla, check exit 1 con
8 errores que nombran fr; restaurado byte a byte.
- C1-C3: el contrato gana el tsconfig mínimo medido
(rewriteRelativeImportExtensions, sin él 578 errores), las rutas de import de
Soma/ActiveEidos/createThemeBaseEidosConfig y adapter-static + prerender.
Cero cambios en src/**. Huecos del framework que la app destapa, al ledger:
el Dialog modal no devuelve el foco al trigger (queda en BODY en los 3 motores:
FocusScope enfoca mientras HideOthers aún tiene el inert), Select.Value muestra
el value crudo hasta abrir el popup, los catálogos de componentes no tienen
'ar', la foundation se carga dos veces (482 KB enlazados + 426 KB inyectados
por applyDom, 5 153 tokens duplicados), guide.md llama eidos.listThemes()
dentro del hook, la regla 6 de active-uix.md está caducada, y uix.langs no
lleva el tipo del catálogo de la app.
Verificación: apps:check exit 0 (1391 ficheros 0 errores · build · smoke
`boot 27/27 · unguarded 0 · pageerror 0 · console.error 0 · csp 0 · delta 0 ·
flips 0`) · boot de la app 15 365 B, byte-idéntico en dos compilaciones, su
hash en el <meta> de build/index.html · docs:check 0/0 en 820 docs. Sobre el
lote antes de D6 (D6 no toca src/, scripts/ ni la suite): gate entero exit 0
en 357 s, check:gate 89 dentro del ledger, suite 464/464 ficheros · 5442/5442
tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
- **The document reset is the app's.** The foundation ships none, and its
|
|
|
|
|
|
recipes assume three rules: `box-sizing: border-box` on every box, no browser
|
|
|
|
|
|
margin on `body`, and the theme's body style on `body`, by token
|
|
|
|
|
|
(`--style-body-font-family` and its siblings — the foundation declares them
|
|
|
|
|
|
and applies them to no element). `apps/base/src/lib/reset.css` is those
|
|
|
|
|
|
rules, imported after `index.css`. Without them, measured in Chromium,
|
|
|
|
|
|
Firefox and WebKit at 375 × 667: the document is 683 px tall under an
|
|
|
|
|
|
`AppShell` that fills `100dvh`, an open `Dialog` is 409 px wide
|
|
|
|
|
|
(`content-box` plus its padding), and `body` computes each browser's default
|
|
|
|
|
|
family, which the shell's skip link inherits. Whether the foundation should
|
|
|
|
|
|
own the reset instead is an open framework question
|
|
|
|
|
|
([F18](./process/PLAN-blocks-quality.md)); until it does, this is a step of
|
|
|
|
|
|
the contract.
|
docs: el contrato de consumo — una app del workspace consume UIX como fuente (docs/consuming.md)
El framework no tenía ni una línea sobre cómo se consume desde fuera de su
propio árbol de demos: todo el corpus asumía acceso al repositorio. El autor
decidió que la app que viene después lo consume COMO FUENTE, en un workspace de
este repositorio, sin paquete npm ni exports ni bin. docs/consuming.md es ese
contrato, sección a sección, enlazando lo que ya tiene página propia en vez de
copiarlo: dónde vive la app (apps/<name>, un solo node_modules y una sola copia
de Svelte), los alias (resolveUixAliases en kit.alias), el modo runas por
config, la raíz de composición standalone (attach existe y no lo consume
nadie), el CSS y los assets servidos por URL (fuentes y sonidos se copian al
static/ de la app), y el boot pre-hidratación (la receta de la guía de theming
con <uix> = ../..).
docs/README.md lo enlaza en E4 y en «I want to…». La guía de theming deja de
llamar «fila abierta» al empaquetado: el consumo es un contrato de workspace.
La app base de F3 es el primer consumidor real y la verificación del contrato:
lo que no se sostenga al construirla se corrige aquí.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
- **Fonts and sounds are served by URL, not imported.** The type scale points at
|
|
|
|
|
|
`/fonts/{family}/…` and the default sound pack at `/sounds/ui/…`, both absolute
|
|
|
|
|
|
paths on the page's own origin. Their files live in the repository's
|
|
|
|
|
|
`static/fonts` and `static/sounds`; the app copies them into its own `static/`
|
feat(apps): apps/base — la primera app que consume UIX como fuente desde un workspace, y su check entra en el gate (F3 del cierre)
Hasta hoy ninguna app consumía el framework fuera de su propio árbol de demos,
y por eso la receta del boot pudo estar rota en producción sin que nadie lo
supiera. apps/base es un esqueleto SvelteKit con adapter-static estricto y
prerender, que sigue docs/consuming.md al pie de la letra: alias por
resolveUixAliases, runas por config, raíz standalone (createActiveUix +
setActiveUix + Soma + ActiveEidos con applyDom), boot compilado con SU esquema
(es/en/ar), CSP por hash con el artefacto propio, fuentes y sonidos copiados al
static/ de la app (assets:sync, con --check). La página es un AppShell con un
Toggle de modo, un Select de idioma y un Dialog.
Raíz: workspaces ["apps/*"] (una sola copia de svelte 5.55.0, vite 7.3.1,
kit 2.55.0 y esbuild 0.27.4; lock +23 líneas); apps:check = check + build +
smoke de la app, dentro del gate justo antes de la suite; .gitignore y
.prettierignore para lo generado y lo copiado.
El smoke sirve build/ por HTTP y pasa tres visitantes (es-ES, ar-EG, es-ES con
modo oscuro y movimiento reducido), cada uno con los módulos abortados (sellos
del boot) y con carga completa (0 pageerror, 0 console.error, 0 violaciones CSP,
delta 0 entre boot e hidratación). Comprueba además que el tag del boot va
DESPUÉS del <meta> CSP: encima, corre sin política y Kit no lo avisa.
Constructor, adversarial y dos rondas de cierre (Opus). El adversarial escribió
una segunda app solo con el contrato y la hizo arrancar en Chromium, Firefox y
WebKit; midió la CSP en siete variantes y tres motores, y mutó la app para ver
si el smoke muerde. Defectos cerrados:
- D1: el smoke prometía contar traducciones ausentes y en una build de
producción no hay señal (engine-langs las emite solo en DEV). Ya no lo
promete; README y consuming §8 dicen dónde se ve cada cosa.
- D2: el smoke nunca abría modo oscuro. Tercer visitante con el valor afirmado;
un pin 'light' olvidado en la raíz ahora da exit 1.
- D3: sin reset de documento el Dialog medía 409 px en un móvil de 375. La app
lleva su reset de tres reglas (doctrina vigente: el reset es de la app); que
la fundación lo traiga es fila del ledger.
- D5: los textos del esqueleto seguían en castellano bajo lang=ar. Salen de
src/lib/strings.ts por langs.ts(record), tipados.
- D6: esa garantía de tipo la sostenía una unión de idiomas escrita a mano.
Ahora hay UNA tupla (APP_LANGUAGES en prefs-schema.ts) que leen el esquema,
los textos y el Select. Visto fallar: con 'fr' en la tupla, check exit 1 con
8 errores que nombran fr; restaurado byte a byte.
- C1-C3: el contrato gana el tsconfig mínimo medido
(rewriteRelativeImportExtensions, sin él 578 errores), las rutas de import de
Soma/ActiveEidos/createThemeBaseEidosConfig y adapter-static + prerender.
Cero cambios en src/**. Huecos del framework que la app destapa, al ledger:
el Dialog modal no devuelve el foco al trigger (queda en BODY en los 3 motores:
FocusScope enfoca mientras HideOthers aún tiene el inert), Select.Value muestra
el value crudo hasta abrir el popup, los catálogos de componentes no tienen
'ar', la foundation se carga dos veces (482 KB enlazados + 426 KB inyectados
por applyDom, 5 153 tokens duplicados), guide.md llama eidos.listThemes()
dentro del hook, la regla 6 de active-uix.md está caducada, y uix.langs no
lleva el tipo del catálogo de la app.
Verificación: apps:check exit 0 (1391 ficheros 0 errores · build · smoke
`boot 27/27 · unguarded 0 · pageerror 0 · console.error 0 · csp 0 · delta 0 ·
flips 0`) · boot de la app 15 365 B, byte-idéntico en dos compilaciones, su
hash en el <meta> de build/index.html · docs:check 0/0 en 820 docs. Sobre el
lote antes de D6 (D6 no toca src/, scripts/ ni la suite): gate entero exit 0
en 357 s, check:gate 89 dentro del ledger, suite 464/464 ficheros · 5442/5442
tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
before `dev` and `build` (`predev` / `prebuild`), and git-ignores the copy. A
|
|
|
|
|
|
symlink is not an option on Windows without privileges, and a committed copy
|
|
|
|
|
|
duplicates binaries that drift. `apps/base/scripts/sync-assets.mjs` is the
|
|
|
|
|
|
copy; with `--check` it changes nothing and exits 1 when the copy drifted.
|
docs: el contrato de consumo — una app del workspace consume UIX como fuente (docs/consuming.md)
El framework no tenía ni una línea sobre cómo se consume desde fuera de su
propio árbol de demos: todo el corpus asumía acceso al repositorio. El autor
decidió que la app que viene después lo consume COMO FUENTE, en un workspace de
este repositorio, sin paquete npm ni exports ni bin. docs/consuming.md es ese
contrato, sección a sección, enlazando lo que ya tiene página propia en vez de
copiarlo: dónde vive la app (apps/<name>, un solo node_modules y una sola copia
de Svelte), los alias (resolveUixAliases en kit.alias), el modo runas por
config, la raíz de composición standalone (attach existe y no lo consume
nadie), el CSS y los assets servidos por URL (fuentes y sonidos se copian al
static/ de la app), y el boot pre-hidratación (la receta de la guía de theming
con <uix> = ../..).
docs/README.md lo enlaza en E4 y en «I want to…». La guía de theming deja de
llamar «fila abierta» al empaquetado: el consumo es un contrato de workspace.
La app base de F3 es el primer consumidor real y la verificación del contrato:
lo que no se sostenga al construirla se corrige aquí.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
|
|
|
|
|
|
## 6. The pre-hydration boot
|
|
|
|
|
|
|
|
|
|
|
|
The recipe — placeholder under `%sveltekit.head%`, a `transformPageChunk` with a
|
|
|
|
|
|
function replacement, the CSP hash in `kit.csp` — is
|
|
|
|
|
|
[`theming/guide.md` § How to kill the dark-mode flash](./theming/guide.md). An
|
|
|
|
|
|
app with its own prefs schema compiles its own boot, and must: the section
|
|
|
|
|
|
[Your schema, your boot](./theming/guide.md) explains why the framework's
|
|
|
|
|
|
default artifact resolves a different direction and language than a
|
|
|
|
|
|
multi-language app's runtime. Inside this workspace every `<uix>` in that recipe
|
feat(apps): apps/base — la primera app que consume UIX como fuente desde un workspace, y su check entra en el gate (F3 del cierre)
Hasta hoy ninguna app consumía el framework fuera de su propio árbol de demos,
y por eso la receta del boot pudo estar rota en producción sin que nadie lo
supiera. apps/base es un esqueleto SvelteKit con adapter-static estricto y
prerender, que sigue docs/consuming.md al pie de la letra: alias por
resolveUixAliases, runas por config, raíz standalone (createActiveUix +
setActiveUix + Soma + ActiveEidos con applyDom), boot compilado con SU esquema
(es/en/ar), CSP por hash con el artefacto propio, fuentes y sonidos copiados al
static/ de la app (assets:sync, con --check). La página es un AppShell con un
Toggle de modo, un Select de idioma y un Dialog.
Raíz: workspaces ["apps/*"] (una sola copia de svelte 5.55.0, vite 7.3.1,
kit 2.55.0 y esbuild 0.27.4; lock +23 líneas); apps:check = check + build +
smoke de la app, dentro del gate justo antes de la suite; .gitignore y
.prettierignore para lo generado y lo copiado.
El smoke sirve build/ por HTTP y pasa tres visitantes (es-ES, ar-EG, es-ES con
modo oscuro y movimiento reducido), cada uno con los módulos abortados (sellos
del boot) y con carga completa (0 pageerror, 0 console.error, 0 violaciones CSP,
delta 0 entre boot e hidratación). Comprueba además que el tag del boot va
DESPUÉS del <meta> CSP: encima, corre sin política y Kit no lo avisa.
Constructor, adversarial y dos rondas de cierre (Opus). El adversarial escribió
una segunda app solo con el contrato y la hizo arrancar en Chromium, Firefox y
WebKit; midió la CSP en siete variantes y tres motores, y mutó la app para ver
si el smoke muerde. Defectos cerrados:
- D1: el smoke prometía contar traducciones ausentes y en una build de
producción no hay señal (engine-langs las emite solo en DEV). Ya no lo
promete; README y consuming §8 dicen dónde se ve cada cosa.
- D2: el smoke nunca abría modo oscuro. Tercer visitante con el valor afirmado;
un pin 'light' olvidado en la raíz ahora da exit 1.
- D3: sin reset de documento el Dialog medía 409 px en un móvil de 375. La app
lleva su reset de tres reglas (doctrina vigente: el reset es de la app); que
la fundación lo traiga es fila del ledger.
- D5: los textos del esqueleto seguían en castellano bajo lang=ar. Salen de
src/lib/strings.ts por langs.ts(record), tipados.
- D6: esa garantía de tipo la sostenía una unión de idiomas escrita a mano.
Ahora hay UNA tupla (APP_LANGUAGES en prefs-schema.ts) que leen el esquema,
los textos y el Select. Visto fallar: con 'fr' en la tupla, check exit 1 con
8 errores que nombran fr; restaurado byte a byte.
- C1-C3: el contrato gana el tsconfig mínimo medido
(rewriteRelativeImportExtensions, sin él 578 errores), las rutas de import de
Soma/ActiveEidos/createThemeBaseEidosConfig y adapter-static + prerender.
Cero cambios en src/**. Huecos del framework que la app destapa, al ledger:
el Dialog modal no devuelve el foco al trigger (queda en BODY en los 3 motores:
FocusScope enfoca mientras HideOthers aún tiene el inert), Select.Value muestra
el value crudo hasta abrir el popup, los catálogos de componentes no tienen
'ar', la foundation se carga dos veces (482 KB enlazados + 426 KB inyectados
por applyDom, 5 153 tokens duplicados), guide.md llama eidos.listThemes()
dentro del hook, la regla 6 de active-uix.md está caducada, y uix.langs no
lleva el tipo del catálogo de la app.
Verificación: apps:check exit 0 (1391 ficheros 0 errores · build · smoke
`boot 27/27 · unguarded 0 · pageerror 0 · console.error 0 · csp 0 · delta 0 ·
flips 0`) · boot de la app 15 365 B, byte-idéntico en dos compilaciones, su
hash en el <meta> de build/index.html · docs:check 0/0 en 820 docs. Sobre el
lote antes de D6 (D6 no toca src/, scripts/ ni la suite): gate entero exit 0
en 357 s, check:gate 89 dentro del ledger, suite 464/464 ficheros · 5442/5442
tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
is the path from the app to the repository root, `../..` for `apps/<name>`, and
|
|
|
|
|
|
`tsx` and `esbuild` go in the app's `devDependencies` at the root's versions.
|
|
|
|
|
|
|
|
|
|
|
|
Three things the recipe does not say, measured in `apps/base`:
|
|
|
|
|
|
|
|
|
|
|
|
- **`themeIds` has no instance to ask.** `hooks.server.ts` runs on the server,
|
|
|
|
|
|
where no `ActiveEidos` exists, so the recipe's `eidos.listThemes()` has nothing
|
|
|
|
|
|
to call. Keep the config in ONE module the root and the hook both import
|
|
|
|
|
|
(`apps/base/src/lib/eidos-config.ts`): `createThemeBaseEidosConfig()` from
|
|
|
|
|
|
`$uix/eidos/lib/themes/base` builds it, the root passes it to
|
|
|
|
|
|
`ActiveEidos.create({ applyDom: true, config })`, and the hook passes
|
|
|
|
|
|
`listEidosThemes(config)` from `$uix/eidos/lib/config` — deep imports, no
|
|
|
|
|
|
runes. The list the boot receives is then the list eidos registers, by
|
|
|
|
|
|
construction.
|
|
|
|
|
|
- **Nothing in the build checks the placeholder's position.** With `%uix.boot%`
|
|
|
|
|
|
moved above `%sveltekit.head%`, the build is green, the boot tag lands before
|
|
|
|
|
|
the CSP `<meta>` and it runs under no policy — measured with the framework's
|
|
|
|
|
|
hash instead of the app's in `kit.csp`: nine attributes stamped, zero
|
|
|
|
|
|
violations. The app's smoke checks the order in `build/index.html`. Prettier
|
|
|
|
|
|
joins the two placeholders on one line (`%sveltekit.head% %uix.boot%`); the
|
|
|
|
|
|
order is what matters.
|
|
|
|
|
|
- **The staleness guard and Kit read the current directory.** `uixBootCheck`
|
|
|
|
|
|
resolves `schema` and `out` against it, and Kit loads `svelte.config.js` from
|
|
|
|
|
|
it; `npm run <script> -w apps/<name>` sets it to the app's directory.
|
docs: el contrato de consumo — una app del workspace consume UIX como fuente (docs/consuming.md)
El framework no tenía ni una línea sobre cómo se consume desde fuera de su
propio árbol de demos: todo el corpus asumía acceso al repositorio. El autor
decidió que la app que viene después lo consume COMO FUENTE, en un workspace de
este repositorio, sin paquete npm ni exports ni bin. docs/consuming.md es ese
contrato, sección a sección, enlazando lo que ya tiene página propia en vez de
copiarlo: dónde vive la app (apps/<name>, un solo node_modules y una sola copia
de Svelte), los alias (resolveUixAliases en kit.alias), el modo runas por
config, la raíz de composición standalone (attach existe y no lo consume
nadie), el CSS y los assets servidos por URL (fuentes y sonidos se copian al
static/ de la app), y el boot pre-hidratación (la receta de la guía de theming
con <uix> = ../..).
docs/README.md lo enlaza en E4 y en «I want to…». La guía de theming deja de
llamar «fila abierta» al empaquetado: el consumo es un contrato de workspace.
La app base de F3 es el primer consumidor real y la verificación del contrato:
lo que no se sostenga al construirla se corrige aquí.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
|
|
|
|
|
|
The direction contract the boot follows — `<html dir>` is a projection, the
|
|
|
|
|
|
template's `dir` is the only one read back — is
|
|
|
|
|
|
[`canon/direction-contract.md` §6](./canon/direction-contract.md).
|
|
|
|
|
|
|
|
|
|
|
|
## 7. What stays with the framework
|
|
|
|
|
|
|
|
|
|
|
|
Everything under `src/` is the framework's. An app that needs a component, a
|
|
|
|
|
|
token or an API the framework lacks flags the gap so it is built as a reusable
|
|
|
|
|
|
part of the framework, never reinvented inside the app
|
|
|
|
|
|
([`guides/component-guide.md`](./guides/component-guide.md) §4).
|
feat(apps): apps/base — la primera app que consume UIX como fuente desde un workspace, y su check entra en el gate (F3 del cierre)
Hasta hoy ninguna app consumía el framework fuera de su propio árbol de demos,
y por eso la receta del boot pudo estar rota en producción sin que nadie lo
supiera. apps/base es un esqueleto SvelteKit con adapter-static estricto y
prerender, que sigue docs/consuming.md al pie de la letra: alias por
resolveUixAliases, runas por config, raíz standalone (createActiveUix +
setActiveUix + Soma + ActiveEidos con applyDom), boot compilado con SU esquema
(es/en/ar), CSP por hash con el artefacto propio, fuentes y sonidos copiados al
static/ de la app (assets:sync, con --check). La página es un AppShell con un
Toggle de modo, un Select de idioma y un Dialog.
Raíz: workspaces ["apps/*"] (una sola copia de svelte 5.55.0, vite 7.3.1,
kit 2.55.0 y esbuild 0.27.4; lock +23 líneas); apps:check = check + build +
smoke de la app, dentro del gate justo antes de la suite; .gitignore y
.prettierignore para lo generado y lo copiado.
El smoke sirve build/ por HTTP y pasa tres visitantes (es-ES, ar-EG, es-ES con
modo oscuro y movimiento reducido), cada uno con los módulos abortados (sellos
del boot) y con carga completa (0 pageerror, 0 console.error, 0 violaciones CSP,
delta 0 entre boot e hidratación). Comprueba además que el tag del boot va
DESPUÉS del <meta> CSP: encima, corre sin política y Kit no lo avisa.
Constructor, adversarial y dos rondas de cierre (Opus). El adversarial escribió
una segunda app solo con el contrato y la hizo arrancar en Chromium, Firefox y
WebKit; midió la CSP en siete variantes y tres motores, y mutó la app para ver
si el smoke muerde. Defectos cerrados:
- D1: el smoke prometía contar traducciones ausentes y en una build de
producción no hay señal (engine-langs las emite solo en DEV). Ya no lo
promete; README y consuming §8 dicen dónde se ve cada cosa.
- D2: el smoke nunca abría modo oscuro. Tercer visitante con el valor afirmado;
un pin 'light' olvidado en la raíz ahora da exit 1.
- D3: sin reset de documento el Dialog medía 409 px en un móvil de 375. La app
lleva su reset de tres reglas (doctrina vigente: el reset es de la app); que
la fundación lo traiga es fila del ledger.
- D5: los textos del esqueleto seguían en castellano bajo lang=ar. Salen de
src/lib/strings.ts por langs.ts(record), tipados.
- D6: esa garantía de tipo la sostenía una unión de idiomas escrita a mano.
Ahora hay UNA tupla (APP_LANGUAGES en prefs-schema.ts) que leen el esquema,
los textos y el Select. Visto fallar: con 'fr' en la tupla, check exit 1 con
8 errores que nombran fr; restaurado byte a byte.
- C1-C3: el contrato gana el tsconfig mínimo medido
(rewriteRelativeImportExtensions, sin él 578 errores), las rutas de import de
Soma/ActiveEidos/createThemeBaseEidosConfig y adapter-static + prerender.
Cero cambios en src/**. Huecos del framework que la app destapa, al ledger:
el Dialog modal no devuelve el foco al trigger (queda en BODY en los 3 motores:
FocusScope enfoca mientras HideOthers aún tiene el inert), Select.Value muestra
el value crudo hasta abrir el popup, los catálogos de componentes no tienen
'ar', la foundation se carga dos veces (482 KB enlazados + 426 KB inyectados
por applyDom, 5 153 tokens duplicados), guide.md llama eidos.listThemes()
dentro del hook, la regla 6 de active-uix.md está caducada, y uix.langs no
lleva el tipo del catálogo de la app.
Verificación: apps:check exit 0 (1391 ficheros 0 errores · build · smoke
`boot 27/27 · unguarded 0 · pageerror 0 · console.error 0 · csp 0 · delta 0 ·
flips 0`) · boot de la app 15 365 B, byte-idéntico en dos compilaciones, su
hash en el <meta> de build/index.html · docs:check 0/0 en 820 docs. Sobre el
lote antes de D6 (D6 no toca src/, scripts/ ni la suite): gate entero exit 0
en 357 s, check:gate 89 dentro del ledger, suite 464/464 ficheros · 5442/5442
tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
|
|
|
|
|
|
## 8. Verification
|
|
|
|
|
|
|
|
|
|
|
|
[`apps/base`](../apps/base/README.md) is this contract followed to the letter, and
|
|
|
|
|
|
its check is part of the gate:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
npm run apps:check # check + build + smoke of apps/base (member of `npm run gate`)
|
|
|
|
|
|
npm run boot -w apps/base # recompiles src/generated/boot.js; twice in a row is byte-identical
|
|
|
|
|
|
npm run assets:sync -w apps/base -- --check # the copied fonts and sounds have not drifted
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
The smoke serves `build/` from a real HTTP server and prints one line of
|
|
|
|
|
|
figures: the boot tag after the CSP `<meta>`; then, for three visitors —
|
|
|
|
|
|
`es-ES`, `ar-EG`, and `es-ES` asking for a dark scheme and reduced motion —
|
|
|
|
|
|
the nine boot attributes on `<html>` with the app's modules aborted, carrying
|
|
|
|
|
|
the values that visitor asks for (`dir="rtl" lang="ar"`;
|
|
|
|
|
|
`data-mode="dark" data-motion="reduce"`), and a full load with zero page errors,
|
|
|
|
|
|
`console.error` and CSP violations and the same nine attributes after
|
|
|
|
|
|
hydration. It does not count missing translations: langs reports them in dev
|
|
|
|
|
|
only, so a production build has nothing to show. A missing context never
|
|
|
|
|
|
reaches it: it fails the prerender. The commands and the passes are described in
|
|
|
|
|
|
[`apps/base/README.md`](../apps/base/README.md).
|