From 819078400329897f4bc5ee106791545b658f6c88 Mon Sep 17 00:00:00 2001 From: dev Date: Thu, 17 Sep 2026 21:12:44 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20el=20repositorio,=20el=20formato=20y=20?= =?UTF-8?q?los=20guards=20de=20texto=20entran=20en=20el=20corpus;=20cuatro?= =?UTF-8?q?=20afirmaciones=20caducas=20mueren=20(documentaci=C3=B3n=20del?= =?UTF-8?q?=20cierre)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El plan de cierre cambió cosas de un nivel que el corpus no cubría: la forma del repositorio, la política de formato, el inventario de lo generado y la clase de guard que lee TEXTO fuente. Un inventario previo de todo `docs/**` (más los README de raíz, `src/**` y `apps/**`) midió qué había: los temas de capa estaban cubiertos, y este nivel no. NUEVO - `docs/repository.md` (E0): las zonas y quién escribe en cada una; la LEY de `web/routes/` congelado y sus dos consecuencias (los validadores de navegador siguen manuales; el formato no llega ahí); un solo install y un solo workspace (el argumento de la copia única de Svelte); un solo mapa de importación con el orden como contrato; qué debe cero y qué debe un ledger que solo mengua. Enlazada desde el mapa, el README de la raíz, getting-started y AGENTS.md. - `docs/testing-and-tooling.md` §Format policy: `.prettierignore` enumerado y justificado (cinco clases), el commit único de formato, el `git config blame.ignoreRevsFile` que hay que ejecutar a mano y que Gitea no lo lee. - `docs/testing-and-tooling.md` §Guards that read source text: la doctrina que faltaba. Un guard de texto está acoplado al formateador; el positivo se pone ROJO y te enteras, el NEGATIVO pasa en VERDE sin inspeccionar nada. Los cinco síntomas medidos en el formateo de una sola vez, seis reglas para escribir uno que no dependa del formato, y los cuatro pasos antes de commitear un formateo masivo (neutralidad compilada, la vista de los guards, los validadores que NO están en el gate, un commit puro). CORREGIDO (afirmaciones vivas y falsas) - `README.md` de la raíz: era una plantilla vacía que mandaba `npm install vicen` con el repositorio `private: true` y sin paquete. Ahora es una puerta. - `AGENTS.md`: su pre-flight INVIOLABLE mandaba leer dos guías del árbol congelado (el canónico está migrado), su ejemplo de test apuntaba a `src/lib/ling/`, borrado en el refactor, y describía cuatro librerías que no existen. Además decía que los comentarios en castellano valen, contra CLAUDE.md. - `docs/theming/guide.md`: los cuatro sitios que llamaban `eidos.listThemes()` DENTRO de `hooks.server.ts`, donde no hay instancia; ahora `THEME_IDS` derivado con `listEidosThemes(config)` del módulo que la raíz también importa. - `docs/architecture/active-uix.md`: la regla 6 decía que la raíz no proyecta preferencias; hoy standalone proyecta por defecto (`projectPrefs`, `@default true`) y attach es opt-in. Su ejemplo de arranque montaba una SEGUNDA proyección a mano. - `announce`: el opt-in queda calificado (motor desnudo) frente al cableado por defecto de las raíces, en `book-deviations.md`, `channels.md:58` y el docblock del canal. - `docs/getting-started.md` y `docs/architecture/morfo.md`: la política de `check:gate` también cubre `scripts/`. - `docs/canon/direction-contract.md`: los dueños literales de la marca (`boot`, `projection-`) pasan a la prosa, citables por un guard. - `src/uix/eidos/components/README.md`: regla 8 — un bindable se reenvía con `bind:`, nunca por el spread del resto (el proxy de rest props no lleva `set`, así que el tipo promete lo que no ata). Con el `ref` en la superficie del `Button` y el gap RESUELTO en `cookie-consent`. `docs/canon/vocabularies.md` y `src/libs/emoji/data.ts` aparecen por fin como artefactos generados, con su comando. Ledger: L-133 · L-142 · L-143 · L-154 a ARREGLADO; L-152 conserva los 532 ficheros pero ya con doctrina escrita; nuevas L-161…L-165 (la última, DIFERIDA: nadie obliga aún a que un guard de texto falle con el corpus vacío). Verificación: `npm run gate` exit 0 en 542 s — lint limpio · check:gate OK (89 de web/ en el ledger) · docs:check 0/0 en 822 docs · suite 466/466 ficheros, 5445/5445 tests · apps:check verde. `component:audit` exit 0 con PASS 161 / NEEDS-WORK 5, las cifras de antes. Co-Authored-By: Claude Opus 5 --- AGENTS.md | 29 ++++- README.md | 51 +++++---- docs/README.md | 26 +++-- docs/architecture/active-uix.md | 26 +++-- docs/architecture/morfo.md | 2 +- docs/canon/direction-contract.md | 9 +- docs/decisions/book-deviations.md | 4 +- docs/getting-started.md | 3 +- docs/process/LEDGER-cierre-2026-09.md | 17 ++- docs/repository.md | 79 +++++++++++++ docs/testing-and-tooling.md | 130 +++++++++++++++++++++- docs/theming/channels.md | 8 +- docs/theming/guide.md | 19 +++- src/uix/blocks/cookie-consent/README.md | 2 +- src/uix/eidos/components/README.md | 9 ++ src/uix/eidos/components/button/README.md | 15 +++ src/uix/sema/chans/announce.ts | 3 +- 17 files changed, 360 insertions(+), 72 deletions(-) create mode 100644 docs/repository.md diff --git a/AGENTS.md b/AGENTS.md index ae5f663e4..381897b84 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,9 +6,10 @@ This file provides guidance to agents when working with code in this repository. Before creating, porting, or modifying ANY UIX component, you MUST read: -1. **[`web/routes/uix/lib/COMPONENT_AUDIT_GUIDE.md`](web/routes/uix/lib/COMPONENT_AUDIT_GUIDE.md)** — pre-flight audit + reference library matrix + architectural rules + anti-patterns -2. **[`web/routes/uix/lib/DEMO_AUTHORING_GUIDE.md`](web/routes/uix/lib/DEMO_AUTHORING_GUIDE.md)** — locked demo template (6 tabs, stage, observer, snippets) +1. **[`docs/guides/component-audit.md`](docs/guides/component-audit.md)** — pre-flight audit + reference library matrix + architectural rules + anti-patterns +2. **[`docs/guides/demo-authoring.md`](docs/guides/demo-authoring.md)** — the demo template (the copies under `web/routes/uix/lib/` belong to the frozen tree; these are the canonical ones) 3. **[`src/uix/eidos/components/README.md`](src/uix/eidos/components/README.md)** — eidos contract +4. **[`docs/building-a-component.md`](docs/building-a-component.md)** — the one door: the phases and the guard for each Skipping these produced the broken Layout Batch 1 (commit `9ec2a57a`) that was reverted + redone. Don't repeat the mistake. @@ -19,13 +20,20 @@ npm run dev # Start dev server npm run build # Production build (static site) npm run test # Run all tests once npm run test:unit # Run tests in watch mode -npx vitest run src/lib/ling/test/ling.test.ts # Run single test file +npx vitest run src/uix/morfo/compile.test.ts # Run single test file npx vitest run -t "describe name" # Run tests matching pattern npm run check # Type check with svelte-check +npm run check:gate # ...with the policy: src/ and scripts/ owe ZERO npm run lint # Check formatting with Prettier npm run format # Auto-format with Prettier +npm run gate # Everything the pre-push hook runs (lint first, suite last) ``` +The zones this runs over — and the frozen `web/routes/` tree — are in +[`docs/repository.md`](docs/repository.md); the gate, the format policy and the +doctrine for guards that read source text are in +[`docs/testing-and-tooling.md`](docs/testing-and-tooling.md). + ## Critical Architecture ### One Alias Table @@ -43,11 +51,20 @@ Path aliases live in ONE module, [`uix.aliases.js`](uix.aliases.js), imported by - **client**: Browser tests via Playwright for `*.svelte.{test,spec}.{js,ts}` files - **server**: Node environment for `*.{test,spec}.{js,ts}` files (excludes svelte tests) -### Internal Library Pattern +### The Layers and the Artifacts -Each library (`ling`, `logr`, `glob`, `actx`) uses factory functions (`createLing`, `createLogr`, etc.) that return instances with internal state. The `logr` library depends on `ling` for localized messages. +`src/uix/{morfo,soma,sema,eidos}` are the layers; `src/arts/*` the runtime +artifacts (`Engine*` / `Active*`, one per concern: prefs, motion, langs, dom, +…), `src/libs/*` the zero-dependency helpers, `src/svrs/*` the +server-authoritative engines. A service is created by a composition root only +(`createActiveUix` / `attachActiveUix`, `Soma.create`, `ActiveEidos.create`). +The layer map with the hard rules is [`CLAUDE.md`](CLAUDE.md) → Architecture. +The legacy `ling` / `logr` / `glob` / `actx` libraries under `src/lib/` were +removed in the UIX refactor and must not reappear. ## Code Style - Tabs for indentation, single quotes, no trailing commas, 100 char print width -- Spanish comments in code are acceptable + (`.prettierrc`; `npm run lint` is the first member of the gate) +- Comments in code are **English**. Some legacy Spanish comments remain — don't + add new ones (`CLAUDE.md` → Code Style) diff --git a/README.md b/README.md index 2fde0da20..eac8b7db9 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,32 @@ -# Name - -### vicen - -# Synopsis - -# Description - -# Example - -# Install: - -`npm install vicen` - -# Test: - -`npm test` - -#License: +# UIX + +A Svelte 5 component system built around a **declarative contract**: a component +is declared once in `morfo` (parts, `data-*`/ARIA, keyboard, events with a +semantic family and intent), and the other layers read that declaration — +`soma` runs the behavior, `sema` projects the perceptual signal (sound, haptic, +live region), `eidos` paints against exactly those attributes. + +> Morfo declares · Soma transcribes · Sema projects · Eidos paints. + +This repository is **private and consumed as source**, not published to npm: an +app lives beside the framework as a workspace and imports it through the shared +alias table — [`docs/consuming.md`](docs/consuming.md). + +## Start here + +| If you want to… | Read | +| ------------------------------------ | ------------------------------------------------------------------- | +| Understand the corpus and find a doc | [`docs/README.md`](docs/README.md) — the map of everything | +| Run it and make a first change | [`docs/getting-started.md`](docs/getting-started.md) | +| Know what each zone of the tree is | [`docs/repository.md`](docs/repository.md) | +| Build an app on it | [`docs/consuming.md`](docs/consuming.md) · [`apps/base`](apps/base) | +| Build or finish a component | [`docs/building-a-component.md`](docs/building-a-component.md) | +| Test, validate, or reformat | [`docs/testing-and-tooling.md`](docs/testing-and-tooling.md) | + +```bash +npm install +npm run dev # the demo site (web/routes — frozen, read-only) +npm run gate # everything the pre-push hook runs +``` + +Agent rules: [`CLAUDE.md`](CLAUDE.md) and [`AGENTS.md`](AGENTS.md). diff --git a/docs/README.md b/docs/README.md index ca8b478ed..d7026145f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,7 +15,9 @@ links the real document. > **Want to run it and make a change, not just read?** → > [`docs/getting-started.md`](./getting-started.md) — clone, run, and your first -> change, in order. +> change, in order. The repository's own zones (what `src/`, `web/routes/`, +> `apps/*` and `scripts/` are, and who may write in each) → +> [`docs/repository.md`](./repository.md). ## What this framework is (60 seconds) @@ -34,15 +36,15 @@ architecture in [`architecture/active-architecture.md`](./architecture/active-ar The corpus is organized in layers of permanence, not by folder: -| Stratum | What it is | Where | -| ------------------------- | -------------------------------------------- | ---------------------------------------------------------------- | -| **E0 — orientation** | this file; the narrative entry; the glossary | `docs/README.md`, `architecture/overview.md`, `docs/glossary.md` | -| **E1 — architecture** | how the layers fit | `docs/architecture/` (the book chapters) + in-place stubs | -| **E2 — canon** | the fixed vocabulary & contracts | `CANON.md`, `canon/tsc.md`, `canon/recipe-contract.md` | -| **E3 — decisions / RFC** | _why_ it is built this way | `decisions.md` + the RFCs & decision logs | -| **E4 — guides** | how to do a thing | `guides/`, `theming/guide.md` | -| **E5 — module reference** | per-artifact docs | `arts/*/README`, `libs/*`, `svrs/*`, `packs/*/README` | -| **process** | ephemeral (hand-offs, snapshots, audits) | `docs/process/` — never a source of truth | +| Stratum | What it is | Where | +| ------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| **E0 — orientation** | this file; the narrative entry; the glossary; the repository's own map | `docs/README.md`, `architecture/overview.md`, `docs/glossary.md`, `docs/repository.md` | +| **E1 — architecture** | how the layers fit | `docs/architecture/` (the book chapters) + in-place stubs | +| **E2 — canon** | the fixed vocabulary & contracts | `CANON.md`, `canon/tsc.md`, `canon/recipe-contract.md` | +| **E3 — decisions / RFC** | _why_ it is built this way | `decisions.md` + the RFCs & decision logs | +| **E4 — guides** | how to do a thing | `guides/`, `theming/guide.md`, `docs/consuming.md`, `docs/testing-and-tooling.md` | +| **E5 — module reference** | per-artifact docs | `arts/*/README`, `libs/*`, `svrs/*`, `packs/*/README` | +| **process** | ephemeral (hand-offs, snapshots, audits) | `docs/process/` — never a source of truth | Writing or editing docs? The conventions that keep this corpus drift-free — link the canon, don't copy it; keep reference docs timeless; one source per @@ -141,7 +143,9 @@ and the server-authoritative engines in `src/svrs/`. | Understand why a decision was made | `docs/decisions.md` → the relevant RFC / [`decisions/book-deviations.md`](./decisions/book-deviations.md) | | Use a runtime artifact (auth, cache, http, …) | [`architecture/active-app.md`](./architecture/active-app.md) (the composition root) → [`arts/README.md`](../src/arts/README.md) (the map) → `src/arts/{name}/README.md` (per-artifact) | | **Write or edit documentation** | [`docs/authoring.md`](./authoring.md) — the authoring rules | -| Test or validate a change | [`docs/testing-and-tooling.md`](./testing-and-tooling.md) — tests, validators, codegen, SSR | +| Test or validate a change | [`docs/testing-and-tooling.md`](./testing-and-tooling.md) — tests, validators, codegen, format policy, the gate, SSR | +| Know what a zone of the repository is for (and what is frozen) | [`docs/repository.md`](./repository.md) — the zones, the single install, the one import map | +| Write a guard (or reformat the tree without blinding one) | [`docs/testing-and-tooling.md`](./testing-and-tooling.md) § Guards that read source text | ## Authoritative sources & rules diff --git a/docs/architecture/active-uix.md b/docs/architecture/active-uix.md index 73ba6305b..10cabc755 100644 --- a/docs/architecture/active-uix.md +++ b/docs/architecture/active-uix.md @@ -85,11 +85,17 @@ services they need — without components ever knowing `ActiveApp` directly. layer asks for it, and fails with an explicit error if it was not declared. 5. **`prefs.direction` is the effective direction preference**; `html[dir]` is only its DOM projection. -6. **`ActiveUix` does not auto-project preferences onto the DOM.** `arts/prefs` - projects the cross-modal attrs (`dir`, `data-motion`, `data-sound`, - `data-haptic`) via `createActivePrefsDomProjection`; `ActiveEidos` projects - the visual ones (`data-theme`, `data-mode`, `data-density`). The `frontend` - artifact was retired and must not reappear as a `locale` source. +6. **Standalone projects the cross-modal preferences by default.** + `createActiveUix` builds `createActivePrefsDomProjection` unless + `projectPrefs: false` (`types.ts` → `projectPrefs`, `@default true`), and it + owns `dir`, `lang`, `data-motion`, `data-sound` and `data-haptic`; components + stamp only ASSERTED directions, so the page must reflect the ambient + preference or nothing does. In **attach** it is the opposite: opt-in with + `projectPrefs: true`, because the host app may own ``. The visual axes + (`data-theme`, `data-mode`, `data-density`, `data-scaling`) are `ActiveEidos`' + job, never this one. The `frontend` artifact was retired and must not + reappear as a `locale` source. What UIX writes into `` carries an + ownership mark — [`canon/direction-contract.md` §6](../canon/direction-contract.md). 7. **`ActiveUix` neither creates nor knows `Soma` or `Eidos`.** `Soma.create(...)` creates its own scope and `Soma.runtime(...)`; `ActiveEidos.create(...)` creates the visual scope when the app needs runtime CSS. @@ -104,15 +110,13 @@ const uix = createActiveUix({ langs, prefs: { schema } }); setActiveUix(uix); Soma.create(); -const prefsProjection = createActivePrefsDomProjection({ - prefs: uix.prefs, - dom: uix.dom -}); - const eidos = ActiveEidos.create({ applyDom: true }); ``` -`prefsProjection` owns `dir`, `data-motion`, `data-sound` and `data-haptic`. +The cross-modal projection comes with the root (rule 6): `uix.prefsProjection` +owns `dir`, `lang`, `data-motion`, `data-sound` and `data-haptic`. Building +`createActivePrefsDomProjection` by hand here would be a SECOND projection over +the same attributes. `ActiveEidos` owns `data-theme`, `data-mode` and `data-density`. Light/dark mode is a prefs dimension like the rest (2026-09-14): the toggle writes `uix.prefs.setIntent('mode', 'dark')` and eidos, which reads the slot, diff --git a/docs/architecture/morfo.md b/docs/architecture/morfo.md index 38a016079..1f89c9d92 100644 --- a/docs/architecture/morfo.md +++ b/docs/architecture/morfo.md @@ -1255,7 +1255,7 @@ runtime that interprets a compiled morfo lives in soma — see | Command | Purpose | | ------------------------------ | ---------------------------------------------------------------------------------- | | `npm run check` | TypeScript type-check across the repo (catches shape errors in morfos). | -| `npm run check:gate` | `check` with policy: `src/` owes zero; `web/` vs the shrinking debt ledger. | +| `npm run check:gate` | `check` with policy: `src/` and `scripts/` owe zero; `web/` vs the ledger. | | `npm run gate` | The pre-push chain (validators + suite) — `docs/testing-and-tooling.md` §The gate. | | `npx vitest run src/uix/morfo` | Run morfo unit tests (schema invariants). | | `npm run smoke` | Playwright smoke over concrete `web/routes` pages (requires dev server). | diff --git a/docs/canon/direction-contract.md b/docs/canon/direction-contract.md index 1d8e990c6..f0b35aa6c 100644 --- a/docs/canon/direction-contract.md +++ b/docs/canon/direction-contract.md @@ -393,9 +393,12 @@ pre-hydration boot and every runtime projection — carries the ownership mark `dir` by presence; the author's `dir`, adopted, is never marked. A script that writes over a marked `dir` is not a source either: at run time a direction is asserted with `prefs.setIntent('direction', …)` or `options.prefs.environment`, -both of which beat the seed. The mark's value names its owner, and a -projection's `dispose` retires its attributes only while the mark still names -it — the next root is created before the old one is destroyed. +both of which beat the seed. The mark's value names its owner — +`boot` for the pre-hydration tag, `projection-` for the nth runtime +projection of the process (`PREFS_DIR_BOOT_OWNER`, `arts/prefs/dom-attrs.ts`) — +and a projection's `dispose` retires its attributes only while the mark still +names it: the next root is created before the old one is destroyed, so a blind +`dispose` would strip the live root's `dir`. That projection is the reason the common case needs no per-component assertion at all: the page declares its direction once at the root, every diff --git a/docs/decisions/book-deviations.md b/docs/decisions/book-deviations.md index ef3ce2313..3a846805f 100644 --- a/docs/decisions/book-deviations.md +++ b/docs/decisions/book-deviations.md @@ -505,7 +505,9 @@ intentGuidance: 'expected' | 'contextual' | 'discouraged'; - **Announce SÍ es canal desde el 2026-07-04 — y esta entrada lo había previsto.** Su propio cierre decía: «si en el futuro `Announce` necesita ser pluggable … entonces vale convertirlo en canal formal. Hoy no». Ese futuro llegó: el - `AnnounceChannel` es built-in, opt-in y exportado + `AnnounceChannel` es built-in y exportado; opt-in en el MOTOR desnudo, y las + raíces de composición lo cablean por defecto (ver `architecture/sema.md` + §Announce channel) (`src/uix/sema/chans/announce.ts`), y [`sema.md` §Announce channel](../architecture/sema.md) lo declara supersedente. No fue una deriva: fue la puerta que esta entrada dejó abierta, cruzada sin volver a escribirlo aquí. Canales runtime reales: 4 diff --git a/docs/getting-started.md b/docs/getting-started.md index 9245f67b0..4ca75b67b 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -69,7 +69,7 @@ sema stamps the event, eidos paints — no layer reaches into another's job. ## 4. The verification loop ```bash -npm run check:gate # types WITH policy — src/ owes zero; web/ vs the ledger +npm run check:gate # types WITH policy — src/ and scripts/ owe zero; web/ vs the ledger npm run check # raw svelte-check (web/ carries frozen ledger errors) npm run test # vitest suite (two projects: browser client + node server) npx vitest run # one file @@ -105,6 +105,7 @@ npm run perm:check # re-validate morfo across state transitions ## 6. Where to go next - The map of all docs → [`docs/README.md`](./README.md) +- What each zone of the repository is (and what is frozen) → [`docs/repository.md`](./repository.md) - The invented vocabulary → [`docs/glossary.md`](./glossary.md) - The semantic canon → [`docs/CANON.md`](./CANON.md) - Writing or editing docs → [`docs/authoring.md`](./authoring.md) diff --git a/docs/process/LEDGER-cierre-2026-09.md b/docs/process/LEDGER-cierre-2026-09.md index 5854d2537..006dce468 100644 --- a/docs/process/LEDGER-cierre-2026-09.md +++ b/docs/process/LEDGER-cierre-2026-09.md @@ -21,7 +21,7 @@ cierre quedó definido por los siete criterios de abajo. `PENDIENTE` (acción concreta con dueño) · `CONFIRMADO` (medido y cierto, sin arreglar) · `REFUTADO` (medido y falso) · `DATO` (no es defecto) · `ARREGLADO` -(con hash) · `DIFERIDO` (fuera del cierre, con razón). Hoy: ARREGLADO 16 · CONFIRMADO 14 · DATO 7 · DIFERIDO 120 · PENDIENTE 1 · REFUTADO 2. +(con hash) · `DIFERIDO` (fuera del cierre, con razón). Hoy: ARREGLADO 24 · CONFIRMADO 11 · DATO 7 · DIFERIDO 120 · PENDIENTE 1 · REFUTADO 2. ## Definición de hecho del cierre @@ -173,7 +173,7 @@ Queda una condición del autor: el push (L-123). | L-130 | Doble foundation en prerender: index.css + uix-eidos-static inyectado por applyDom | plan F2/F3 | — | medio | CONFIRMADO | medido en apps/base (834e6c49a): CSS enlazado 482 535 B + `style#uix-eidos-static` 425 591 B + `#uix-eidos-theme` 31 780 B; los 5 153 tokens del estático están TODOS en el enlazado | | L-131 | 14 scripts/\_\_\* siguen trackeados (destrackear = borrado del índice) | F0 | — | trivial | DIFERIDO | decisión del autor | | L-132 | routes/web/marketing.md suelto en la raíz; scripts worker/worker:daemon apuntan a worker.js gitignorado | exploración | — | trivial | DIFERIDO | decisión del autor | -| L-133 | AGENTS.md conserva prosa caduca fuera de la sección de alias (librerías ling/logr/glob/actx que no existen) | F2 | — | pequeño | DIFERIDO | | +| L-133 | AGENTS.md conserva prosa caduca fuera de la sección de alias (librerías ling/logr/glob/actx que no existen) | F2 | — | pequeño | ARREGLADO | pasada de documentación 2026-09-17: AGENTS.md deja de mandar leer el árbol congelado y de describir librerías borradas (`ling`/`logr`/`glob`/`actx`), y su ejemplo de test apunta a un fichero que existe | | L-134 | Rama `claude/cranky-cori-b70afe` (`6d5a8a19a`, 2026-07-04, 2 ficheros): aria-valuetext del knob formateado por Intl; HEAD ya tiene `formatValue()` (knob-provider) — puede estar superada | L-124 | — | pequeño | DIFERIDO | F5 formateó sin fusionar ramas (decisión del autor 2026-09-17); rescatar exige resolver espacios contra 468a9d113 | | L-135 | Rama `claude/strange-thompson-58746c` (`2f43d7eb8`, 2026-08-25, 18 READMEs de theming): 13 filas de adjudicación re-derivadas; HEAD evolucionó esos ficheros después (`ef47f3447`) | L-124 | — | pequeño | DIFERIDO | ídem L-134 | | L-136 | Worktree `.claude/worktrees/agent-aaaa749f035d12f18` con 9 ficheros SIN COMMITEAR de soma (color-field, color-picker, date-field, date-picker, date-range-picker, picker-shell ×2, time-field, time-picker) | `git -C … status` 2026-09-17 | — | medio | DIFERIDO | decisión del autor: commitear o descartar | @@ -182,8 +182,8 @@ Queda una condición del autor: el push (L-123). | L-139 | La foundation no trae reset ni tipografía en body y las recetas asumen border-box: sin reset el Dialog mide 409 px en 375 | F3 H2/D3 | — | pequeño | DIFERIDO | la app lleva su reset (apps/base/src/lib/reset.css, doctrina vigente F18); si la fundación lo asume, se borra ese fichero y se reescribe consuming §5 | | L-140 | `Select.Value` muestra el value crudo (`es`) hasta abrir el popup: labelRegistry vacío con los ítems en portal cerrado; select-value.svelte no pinta `children` | F3 H3 | — | pequeño | CONFIRMADO | 3 motores | | L-141 | Los catálogos de componentes no tienen `ar` (skip link en castellano bajo lang=ar); en producción sin aviso | F3 H4 | — | medio | CONFIRMADO | | -| L-142 | `docs/theming/guide.md` (:326, :531, :621, :634) pone `themeIds: eidos.listThemes()` dentro del hook, donde no hay instancia | F3 H5 | — | trivial | CONFIRMADO | consuming §6 da la forma correcta (listEidosThemes(config)) | -| L-143 | `docs/architecture/active-uix.md` regla 6 y snippet :101-113 caducados: `projectPrefs` es `@default true` | F3 H6 | — | trivial | CONFIRMADO | | +| L-142 | `docs/theming/guide.md` (:326, :531, :621, :634) pone `themeIds: eidos.listThemes()` dentro del hook, donde no hay instancia | F3 H5 | — | trivial | ARREGLADO | pasada de documentación 2026-09-17: los cuatro sitios de la guía usan `THEME_IDS` derivado con `listEidosThemes(config)` y la guía explica que el hook no tiene instancia | +| L-143 | `docs/architecture/active-uix.md` regla 6 y snippet :101-113 caducados: `projectPrefs` es `@default true` | F3 H6 | — | trivial | ARREGLADO | pasada de documentación 2026-09-17: la regla 6 dice que standalone proyecta por defecto y attach es opt-in, y el ejemplo de arranque ya no monta una segunda proyección a mano | | L-144 | `uix.langs` no lleva el tipo del catálogo de la app: un `t('ruta')` mal escrito compila y se prerenderiza en crudo; producción no emite KEY_NOT_FOUND (solo DEV) | F3 D1 | — | medio | CONFIRMADO | apps/base usa langs.ts(record) tipado con APP_LANGUAGES | | L-145 | `docs:check` I8 trata todo `docs/**` como voz del framework y prohíbe `$lib` también en código de app (consuming §4 lo lleva comentado) | F3 ronda 2 | — | pequeño | DIFERIDO | | | L-146 | El HTML prerenderizado lleva el texto en el idioma por defecto aunque el boot ponga lang=ar: el boot corrige atributos, no texto | F3 ronda 2 | — | — | DATO | inherente a prerender de una sola página | @@ -192,12 +192,17 @@ Queda una condición del autor: el push (L-123). | L-149 | `node_modules/@vicen/base` es un junction a apps/base: un borrado recursivo que siga junctions borra la app | F3 | — | — | DATO | | | L-150 | El build de Kit no es reproducible: el hash de su script inline de arranque cambia entre builds idénticos | F3 | — | — | DATO | no afecta al hash del boot | | L-151 | `docs/consuming.md` incumple Prettier desde HEAD 4f3425783 (`;` del snippet de runas) | F3 H8 | — | trivial | ARREGLADO | 468a9d113 | -| L-152 | El defecto de A-106 en masa: wrappers de eidos pasan `...rest` a una parte de soma con `ref = $bindable` sin `bind:ref` ⇒ `bind:ref` del consumidor recibe null | F4 censo estático (adb680899) | — | medio | CONFIRMADO | 532 ficheros en 99 componentes (top: media-player 24, sidebar 17, color-picker 16); muestra runtime 3/3 (checkbox-group-label, field-required-indicator, proof-of-human); 27 ficheros sin resolver por el censo (chronos, date-range-picker…) | +| L-152 | El defecto de A-106 en masa: wrappers de eidos pasan `...rest` a una parte de soma con `ref = $bindable` sin `bind:ref` ⇒ `bind:ref` del consumidor recibe null | F4 censo estático (adb680899) | — | medio | CONFIRMADO | 532 ficheros en 99 componentes (top: media-player 24, sidebar 17, color-picker 16); muestra runtime 3/3. La DOCTRINA ya está escrita (`eidos/components/README.md` §Reglas duras, regla 8) y el Button reparado; los otros wrappers siguen sin reenviar | | L-153 | `applyOverride` (resolver.ts:343-346) clona un DeltaOp LITERAL como firma cuando la familia no tiene slice haptic base (`emerge` + `channels:['haptic']` + `{ intensity: { op: 'add' } }`) ⇒ HapticChannel.handle lanza `TypeError … 'pattern'`, 0 vibraciones | F4 adversarial L-ADV-3 | — | pequeño | CONFIRMADO | bug de runtime de sema; fuera del lote | -| L-154 | «opt-in» sin calificar para announce en `docs/decisions/book-deviations.md:508` y `src/uix/sema/chans/announce.ts:13` (cierto solo para el motor desnudo) | F4 adversarial L-ADV-4 | — | trivial | CONFIRMADO | | +| L-154 | «opt-in» sin calificar para announce en `docs/decisions/book-deviations.md:508` y `src/uix/sema/chans/announce.ts:13` (cierto solo para el motor desnudo) | F4 adversarial L-ADV-4 | — | trivial | ARREGLADO | pasada de documentación 2026-09-17: `book-deviations.md`, `announce.ts` y `theming/channels.md:58` califican el opt-in (motor desnudo) frente al cableado por defecto de las raíces | | L-155 | Announce sin pin directo en attach (default ON) ni en motor desnudo (opt-in); la viñeta de attach de sema.md nombra solo `defineEngineSemantic` y omite `defineUixServices({ events: { announce: false } })`, que también sirve | F4 adversarial L-ADV-5/6 | — | pequeño | DIFERIDO | hoy ciertas (medido); solo contracts.test.ts:670-702 pinea standalone | | L-156 | Siete handoffs de julio en minúscula (`docs/process/continue-*.md`: agente, arts-docs-reconciliation, audit-fixes, cleanroom-fixes, proof-of-human, runed-tabbable-port, with-stumbles-s2-s6) quedaron fuera del censo del plan | F6 | — | — | DATO | no llevan cabecera CONGELADO ni están transcritos aquí: son históricos (varios se declaran cerrados en su cabecera) | | L-157 | `docs-check` I10 cosecha los identificadores del código LÍNEA A LÍNEA (`identifiersOf`), así que el conjunto de verdad sigue a cómo prettier parte o junta las listas de import/export: 219 de 267 READMEs cambiaron su conjunto (55 crecen, 76 encogen) | F5 medición de guards (8a7a31fb9) | — | pequeño | CONFIRMADO | ceguera LATENTE, no realizada: las 979 filas de prop evaluadas dan el mismo veredicto en ambas revisiones (0 volteos). Cerrarla exige cosechar del AST, no por línea | | L-158 | `pack-census.test.ts` detecta emisiones con `source.includes("'evento'")`: solo la comilla SIMPLE. Una emisión escrita con comilla doble o backtick produce un falso «nadie lo emite» | F5 medición de guards | — | trivial | CONFIRMADO | medido: dos emisiones de chronos estaban con comilla doble y el formateo las normalizó. Solo puede dar rojo falso, nunca tapar | | L-159 | `recipe-css-contract.test.ts` delimita el lado derecho de un `export type` con el PRIMER `;`: un infractor repartido en ≥2 miembros de un tipo objeto queda fuera del barrido | F5 medición de guards | — | trivial | CONFIRMADO | hipotético hoy: 0 fugas medidas en el árbol (ningún literal de rol vive en el cuerpo de un `export type` fuera del span) | | L-160 | `.prettierrc` usa `endOfLine: "auto"`: un CR huérfano en un fichero hace que Prettier lo reescriba ENTERO en CR, y entonces el fichero es UNA sola línea para cualquier guard que parta por saltos de línea | F5 (8a7a31fb9) | — | pequeño | DIFERIDO | pasó en `REVIEW-theming-2026-08-24.md` (normalizado a LF ahí mismo); fijar `endOfLine: "lf"` reescribiría el árbol entero: fila aparte | +| L-161 | El `README.md` de la raíz era una plantilla npm vacía que mandaba `npm install vicen`, con el repositorio `private: true` y sin paquete publicado | pasada de documentación 2026-09-17 | — | trivial | ARREGLADO | reescrito como puerta: la tesis en cuatro líneas, «se consume como fuente», y la tabla de a dónde ir | +| L-162 | No existía ninguna página que describiera el repositorio como objeto: sus zonas, la ley de congelación de `web/routes/`, `scripts/` como zona tipada, el workspace único | pasada de documentación 2026-09-17 | — | pequeño | ARREGLADO | NUEVA `docs/repository.md` (E0), enlazada desde el mapa, el README de la raíz, getting-started y AGENTS.md | +| L-163 | No había doctrina escrita sobre los guards que leen TEXTO fuente: que dependen del formateador y que un guard negativo puede quedarse ciego EN VERDE | pasada de documentación 2026-09-17 | — | pequeño | ARREGLADO | `testing-and-tooling.md` §Guards that read source text: los cinco síntomas medidos, cómo escribirlos y los cuatro pasos antes de un formateo masivo | +| L-164 | El inventario de codegen tenía una fila de seis y la política de formato no estaba escrita (contenido de `.prettierignore`, commit único, `git config blame.ignoreRevsFile`) | pasada de documentación 2026-09-17 | — | trivial | ARREGLADO | `testing-and-tooling.md` §Codegen (los seis artefactos con su generador y sus dos invariantes) y §Format policy | +| L-165 | Ningún guard exige que un guard de texto falle cuando su corpus queda VACÍO; la regla está escrita, pero solo `check-gate.ts` la aplica (marcador `COMPLETED`) | pasada de documentación 2026-09-17 | — | medio | DIFERIDO | la doctrina queda en `testing-and-tooling.md`; hacerla cumplir exige tocar ~30 guards, fuera del cierre | diff --git a/docs/repository.md b/docs/repository.md new file mode 100644 index 000000000..7df1d2c65 --- /dev/null +++ b/docs/repository.md @@ -0,0 +1,79 @@ +--- +title: The Repository +type: reference +audience: human + agent +authority: reference — the zones of this repository, who may write in each, and the invariants that hold them apart +status: current +--- + +# The Repository + +One tree, one install, one gate. [`docs/README.md`](./README.md) is the map of +the **docs**; this is the map of the **repository**: what each zone is, who may +write in it, and the few invariants that keep the zones from bleeding into each +other. The path from clone to first change is +[`getting-started.md`](./getting-started.md). + +## The zones + +| Zone | What it is | Who writes it | +| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- | +| `src/uix/` | The layers — `morfo` (contract), `soma` (behavior), `sema` (perceptual engine), `eidos` (CSS) — plus the `active-uix` composition root and the `blocks` tier | the framework | +| `src/arts/` · `src/libs/` · `src/svrs/` · `src/packs/` | Runtime artifacts, zero-dependency helpers, server-authoritative engines, the opt-in pack tier | the framework | +| `web/routes/` | The demo site Kit serves (`kit.files.routes`). **FROZEN** — see below | nobody | +| `apps/*` | npm workspaces that consume the framework as source; `apps/base` is the reference consumer | app authors | +| `scripts/` | The tooling: validators, generators, the pre-push hook. Type-checked and owing zero, like `src/` | whoever adds or fixes a guard | +| `docs/` | The doc corpus (strata E0–E5), plus `docs/process/` for the ephemeral record | doc authors | +| `static/` | Fonts, sounds, images served by URL; an app copies what it needs into its own `static/` | the framework | + +Two zones are NOT source: `src/docs/` holds the book manuscripts the canon comes +from, and `src/lib/_demo` holds the frozen demo site's own widgets (the `$demo` +alias). + +## `web/routes/` is frozen + +The demo tree is being rebuilt from scratch, so it is **read-only**: read it as +a reference for composition, never edit it. Two consequences that bite: + +- the browser-driven validators (`morfo:check`, `perm:check`, `smoke`, + `layer:check`) and the demo rules of `component:audit` validate AGAINST that + frozen tree, so they stay manual until an app tree replaces them; +- `web/` is in `.prettierignore`, so the format policy does not reach it. + +## One install, one workspace + +The root `package.json` declares `workspaces: ["apps/*"]`: one `node_modules`, +one lockfile, one copy of `svelte`, `vite` and `@sveltejs/kit` for the framework +and every app. Two copies of Svelte would be two runes runtimes, and the +framework's `.svelte.ts` modules would not share state with an app's components. +Vite derives the workspace root from that field, which is what lets an app's dev +server read files under `src/` with no `server.fs.allow`. The contract an app +follows — aliases, runes, composition root, CSS and assets, the pre-hydration +boot — is [`consuming.md`](./consuming.md). + +## One import map + +Every `$…` / `@/` specifier resolves through ONE module, +[`uix.aliases.js`](../uix.aliases.js), imported by `vite.config.ts`, +`svelte.config.js`, `scripts/generate-boot.ts`, `scripts/docs-check.ts` and each +app's `svelte.config.js`. Order is load-bearing — Vite and esbuild match a +string alias as a PREFIX, so a longer specifier must precede any key that is its +prefix — and `src/uix/aliases.test.ts` fails on a second copy, a broken order, +or a target that is not on disk. The table summary lives in +[`CLAUDE.md`](../CLAUDE.md) → Path Aliases. + +## The framework owes zero, the demo tree owes a shrinking ledger + +`npm run check:gate` fails on any type error under `src/` or `scripts/`; errors +in the frozen `web/` tree are measured against a per-file ledger that may only +shrink. That policy, the whole gate chain and the pre-push hook are in +[`testing-and-tooling.md`](./testing-and-tooling.md) § The gate — with the +format policy and the inventory of generated artifacts in the same page. + +## What is open + +The framework's open rows live in one place, by id: +[`docs/process/LEDGER-cierre-2026-09.md`](./process/LEDGER-cierre-2026-09.md). +It is process — a record of what happened, never a source of truth for how the +framework works — but it is the only place that knows what was deferred and why. +The `CONTINUE-*.md` hand-offs beside it are frozen and point at it. diff --git a/docs/testing-and-tooling.md b/docs/testing-and-tooling.md index 950a130ed..f6e212068 100644 --- a/docs/testing-and-tooling.md +++ b/docs/testing-and-tooling.md @@ -74,9 +74,29 @@ These catch classes of bug that `check` (types) and an HTTP 200 (SSR) miss. Some surfaces are produced from a source of truth, not hand-maintained. Don't edit the output; edit the source and regenerate. -| Command | Generates from | -| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| `npm run generate:eidos-css` | the eidos foundation/recipe CSS, from `EidosConfig.recipes`. (`generated/base.css` is output — regenerate, don't hand-edit.) | +| Command | Output | From | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------ | +| `npm run generate:eidos-css` (+ `npm run eidos:purge`) | `src/uix/eidos/generated/base.css` (and the purged variant) | `EidosConfig.recipes` | +| `npm run generate:boot` | `src/uix/active-uix/generated/boot.js` — the framework's pre-hydration boot + its CSP hash | `src/uix/active-uix/boot/*`, compiled with esbuild | +| `npm run boot -w apps/` | `apps//src/generated/boot.js` — that site's own boot, its own hash | the app's `prefs-schema.ts` (`--schema` / `--out`) | +| `npm run docs:vocabularies` | [`canon/vocabularies.md`](./canon/vocabularies.md) — the closed sets | the code consts; `docs:check` (I7) compares it byte for byte | +| `npm run generate:emoji-data` | `src/libs/emoji/data.ts` | `static/emoji/*.json` (emojibase), pruned | +| `node --import tsx/esm scripts/theming-census.ts --report` | `docs/audit/theming/` | the theming census | + +Two invariants for all of them: + +- **The generator owns the file.** Edit the source and regenerate; a hand edit + is lost on the next run, and `docs:check` fails outright for the generated + vocabularies. +- **They are all in `.prettierignore`.** A formatter that reformats generated + output turns every regeneration into a diff — and, for a byte-compared + artifact, into a red gate. (Measured: the repo-wide format pass reformatted + `canon/vocabularies.md` and broke `docs:check` until both were excluded.) + +An app's copy of the framework's fonts and sounds +(`apps/*/static/{fonts,sounds}`) is generated the same way: `assets:sync` +refreshes it before `dev` and `build`, git ignores it, and `--check` fails when +it drifted. The `data-*` contract is **not** a generated doc — it is the morfo itself, validated live by `npm run morfo:check`. (A legacy `generate:contracts-docs` script and its @@ -129,7 +149,12 @@ The drift-defense scripts stopped being advisory in P0 fase B (audit SHRINKS (the theming-census-debt discipline — a file above its entry, or an erroring file the ledger does not name, fails). The run must end with the `COMPLETED` marker and the parsed count must match it: a gate that - inspected nothing fails, never passes. + inspected nothing fails, never passes. The mechanism for `scripts/`: Kit's + generated tsconfig lists routes, lib and src, so `svelte.config.js` pushes + `../scripts/**/*.ts` (and `../uix.aliases.js`) into the include via + `kit.typescript.config`, and the root `tsconfig.json` excludes the probes — + its own `exclude` overrides the generated one, which is why the entry lives + there and not in the config callback. - **`npm run gate`** — the chain the pre-push hook runs: `lint` first (`prettier --check`; the repo-wide one-shot landed in the closure plan's F5, and its commit is listed in `.git-blame-ignore-revs`) → `check:gate` → the @@ -144,8 +169,105 @@ The drift-defense scripts stopped being advisory in P0 fase B (audit validators (`morfo:check`, `perm:check`, `smoke`, `layer:check`) stay manual — they need a dev server. +## Format policy + +Prettier is the only formatter, and `npm run lint` (`prettier --check .`) is the +FIRST member of the gate: the tree is either formatted or the push is rejected. +Style values (tabs, single quotes, no trailing commas, 100 columns) live in +`.prettierrc` and are summarized in [`CLAUDE.md`](../CLAUDE.md) → Code Style. + +`.prettierignore` holds five kinds of exception, and nothing else: + +| Excluded | Why | +| ----------------------------------------------------------------------- | --------------------------------------------------------- | +| lockfiles | the package manager owns them | +| `/static/` | assets, not source | +| the generated artifacts (§ Codegen) — including `canon/vocabularies.md` | the generator owns the bytes; see the invariants above | +| `web/` | the frozen demo tree ([`repository.md`](./repository.md)) | +| `.claude/` | tool configuration, including the author's local settings | + +The historical repo-wide debt was paid in a single formatting-only commit, and +that commit is listed in `.git-blame-ignore-revs`. `git blame` ignores it only +once per clone, on request: + +```bash +git config blame.ignoreRevsFile .git-blame-ignore-revs +``` + +(GitHub honours the file by name; the Gitea remote this repo pushes to does +not — the mitigation is the config above, plus keeping formatting commits pure.) + +**A formatting pass is a contract change for every guard that reads source +text** — the next section says why, and what to measure before committing one. + +## Guards that read source text + +A large family of guards here does not run the code: it READS it. A test or a +script opens `.ts` / `.svelte` / `.css` / `.md` files and asserts on the text — +`contracts.test.ts`, the censuses (`focus-census`, `source-census`, +`pack-census`, `opts-census`), `recipe-css-contract`, `value-channels`, +`docs-check`, `component-audit`, `eidos:lint`, `rtl-check`. They catch what +types cannot: a declaration nobody wired, a token spelled by hand, a doc that +copied a canonical list instead of linking it. + +The price is a coupling nothing declares: **a text guard is coupled to the +formatter.** Reformat the tree and some of them change what they inspect. One +direction is loud, the other is silent: + +- a **positive** guard (this must be PRESENT) that stops matching goes RED + against correct code — noisy, but you find out; +- a **negative** guard (this must be ABSENT; the offender list must stay empty) + that stops matching goes GREEN while inspecting nothing. The gate says OK and + the contract is gone. It is the same failure as a guard whose corpus is empty: + `check-gate.ts` refuses to judge without the `COMPLETED` marker for exactly + this reason. + +Measured on the repo-wide pass, one of each: + +| Symptom | Cause | +| ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- | +| A capture ran past its own declaration into the next one, so a field living in the WRONG type satisfied the assert | the delimiter was `\}>;`, and a long declaration prints as `}` then `>;` on the next line | +| `/policy:.*runtime\.focus/` went red against correct code | `.` does not cross a newline, and the value wrapped | +| Disposition markers in a `## Gaps` section dropped to zero | `*diferir*` normalizes to `_diferir_`, and `_` is a word character, so `\b` stops matching | +| Six false offenders in the CSS typography rule | a per-line matcher read `calc(` as the whole value, and the `/* literal: … */` valve fell outside the match | +| A whole chronicle became ONE line for anything splitting on `\n` | `endOfLine: "auto"` amplified a single stray CR into a CR-only file | + +### Writing one that does not depend on the formatter + +- **Anchor on tokens, not on line shape.** Let whitespace be whitespace: + `\}\s*>;` instead of `\}>;`, `[^;{}]+` instead of `[^\n]+`. +- **Bound the scan by the construct.** A lazy `[\s\S]*?` needs a delimiter that + can only appear where the thing you are matching ends. +- **In markdown, `\b` is not a word boundary** — emphasis markers are word + characters. Use letter boundaries: `(? **Don't get confused**: **motion · depth · shape · color are NOT "eidos diff --git a/docs/theming/guide.md b/docs/theming/guide.md index b5fdf14cb..4cb5b29a8 100644 --- a/docs/theming/guide.md +++ b/docs/theming/guide.md @@ -332,6 +332,7 @@ arrives as a header. ```ts import { renderUixBootScript } from '$active-uix/boot/render'; +import { THEME_IDS } from './lib/eidos-config'; export const handle = ({ event, resolve }) => resolve(event, { @@ -339,7 +340,7 @@ export const handle = ({ event, resolve }) => html.replace('%uix.boot%', () => renderUixBootScript({ defaultLocale: 'es', - themeIds: eidos.listThemes() + themeIds: THEME_IDS }) ) }); @@ -348,6 +349,14 @@ export const handle = ({ event, resolve }) => `renderUixBootScript` returns a string and nothing else — the framework does not own `app.html` and does not install a hook. +**`themeIds` has no instance to ask.** The hook runs on the server, where no +`ActiveEidos` exists, so it cannot call `eidos.listThemes()`. Keep the config in +ONE module both sides import — the root passes it to +`ActiveEidos.create({ config })`, the hook derives the list with +`listEidosThemes(config)` (`$uix/eidos/lib/config`) — and the list the boot +receives is the list eidos registers, by construction. Worked example: +[`consuming.md` §6](../consuming.md). + **The replacement is a FUNCTION, always.** Given a string, `String.prototype.replace` expands `$&`, `` $` `` and `$'` inside it, and those two characters can appear in a theme id or a storage key: `$'` splices in @@ -544,7 +553,7 @@ import { UIX_BOOT_SCRIPT, UIX_BOOT_CSP_HASH } from './generated/boot.js'; renderUixBootScript({ defaultLocale: 'es', - themeIds: eidos.listThemes(), + themeIds: THEME_IDS, artifact: { script: UIX_BOOT_SCRIPT, hash: UIX_BOOT_CSP_HASH } }); ``` @@ -634,7 +643,9 @@ throws `ActiveUixInvalidBootNonceError` instead of writing it into the tag. - `defaultLocale` — the locale you passed to `createActiveUix({ langs })`. The preference schema is built around it, so a boot given a different one resolves a different `lang` than the runtime will. -- `themeIds` — `eidos.listThemes()`. It feeds the "is this family already a +- `themeIds` — the ids eidos registers, derived on the server with + `listEidosThemes(config)` from the config module the root also imports (the + hook has no `ActiveEidos` to ask). It feeds the "is this family already a complete theme id?" branch; without it every family gets a `-light` / `-dark` suffix appended and `data-theme` disagrees with the runtime. - `pins` — the axes your ROOT `ActiveEidos` nailed, if any @@ -647,7 +658,7 @@ throws `ActiveUixInvalidBootNonceError` instead of writing it into the tag. ```ts renderUixBootScript({ defaultLocale: 'es', - themeIds: eidos.listThemes(), + themeIds: THEME_IDS, pins: { mode: 'dark' } }); ``` diff --git a/src/uix/blocks/cookie-consent/README.md b/src/uix/blocks/cookie-consent/README.md index 8c0c65832..e4da7d823 100644 --- a/src/uix/blocks/cookie-consent/README.md +++ b/src/uix/blocks/cookie-consent/README.md @@ -99,7 +99,7 @@ NO entra en esta tabla**._ | **Persistencia** (la cookie, su caducidad) | **app-land** — por diseño (D-BLK.6) | | **Bloqueo de scripts** hasta el sí | **app-land** — es del cargador de la app | | **Esquina flotante** | **deferred** — otro `placement`; entra como prop cuando una demo lo pida | -| **`Button` acepta `ref` y no lo reenvía** | **canon** — `bind:ref` tipa bien y no ata nada (forma de A-94); aquí el disparador se enfoca por `id` | +| **`Button` acepta `ref` y no lo reenvía** | **RESUELTO** — el wrapper lo reenvía con `bind:ref` (A-106); el block sigue enfocando por `id` | | **`Switch` no tiene parte de etiqueta** | **canon** — la etiqueta visible es hermana y no es diana de clic; se ata por `aria-labelledby` | | **El `Dialog` restaura el foco a un nodo muerto** | **canon** — cuando el disparador se desmonta, el foco acaba en el `body`; el block lo devuelve él mismo | diff --git a/src/uix/eidos/components/README.md b/src/uix/eidos/components/README.md index cadfe7c5b..7c41348c7 100644 --- a/src/uix/eidos/components/README.md +++ b/src/uix/eidos/components/README.md @@ -61,6 +61,15 @@ accesibles como propiedades — ``, ``, etc. Drawer.Content = Content; // ... ``` +8. **Un bindable se REENVÍA con `bind:`, nunca por el spread del resto.** Si la + part de Soma declara `ref = $bindable(null)` —o cualquier otro bindable—, el + wrapper lo desestructura y lo ata: `ref = $bindable(null)` en sus `$props()` + y `bind:ref` en la etiqueta de Soma. El spread (`{...rest}`) pasa el VALOR, + no el enlace: el proxy de rest props de Svelte describe cada clave sin + `set`, así que el `bind:ref` del consumidor nunca recibe el elemento y el + tipo lo acepta igual. Medido en el `Button` (fila A-106) y en 532 ficheros + más de este árbol (ledger L-152): al reenviar, comprobarlo con un test + client que afirme que el nodo recibido es el que pinta el componente. ### Estructura de directorio diff --git a/src/uix/eidos/components/button/README.md b/src/uix/eidos/components/button/README.md index 339c49679..51535fb82 100644 --- a/src/uix/eidos/components/button/README.md +++ b/src/uix/eidos/components/button/README.md @@ -44,6 +44,21 @@ Cambios sobre ese baseline: ``` +`ref` llega hasta el ` +``` + - Props visuales (eidos): `variant` (6), `size` (`xs`–`xl`, responsive), `rounded` (`sm`–`full`, independiente de `size`), `shape` (familia de esquina), `block`, `iconOnly`, `icon`/`endIcon`/`spinner` (snippets), diff --git a/src/uix/sema/chans/announce.ts b/src/uix/sema/chans/announce.ts index b26f80b09..d0da8938b 100644 --- a/src/uix/sema/chans/announce.ts +++ b/src/uix/sema/chans/announce.ts @@ -10,7 +10,8 @@ import type { Channel } from './types'; * signal must reach a screen-reader user "con la prioridad adecuada, pero sin * crear ruido continuo". Historically this lived as a runtime concern * (`ActiveUix.announce`) or an ad-hoc app cascade example; this makes it a - * first-class, opt-in channel like sound / haptic. + * first-class channel — opt-in on a bare engine like sound / haptic, and wired + * by DEFAULT by the composition roots (`docs/architecture/sema.md` §Announce). * * What it does on `handle`: * - reads the human-facing text from `signal.message` (the runtime never