docs(book): F7.3 (2/3) — theming/ satellites: guide + notes + channels + motion-guide + changelog
Five theming docs into the book tree:
- docs/theming/guide.md — THEMING_GUIDE (241 L, Spanish -> English): add a
component's recipe step-by-step + the three theme-definition modes.
- docs/theming/notes.md — THEMING_NOTES (168 L, Spanish -> English): the
bundle/feature comparison + the controversial-decisions FAQ (in-page
anchor to s1.bis fixed to a real THEMING link).
- docs/theming/channels.md — CHANNELS_SYNTHESIS (115 L, Spanish ->
English): the per-channel-RFC capstone (two moments, 8 expression vs 3
runtime channels, the builder sextet + applyTheme).
- docs/theming/motion-guide.md — MOTION_GUIDE (213 L, already English):
moved with frontmatter + links repointed.
- docs/theming/changelog.md — THEMING_CHANGELOG (1310 L): chronicle,
moved VERBATIM (recorded history is not translated), links repointed.
Thin stubs at all five old paths; corpus links swept (docs map E3/E4
rows, building-a-component phase 5, decisions umbrella, getting-started,
eidos chapter). docs:check 0 errors (258 docs).
Remaining in F7.3: THEMING.md itself (1499 L) + eidos-motion.md (698 L).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
---
|
|
|
|
|
|
title: Eidos Theming — Authoring guides
|
|
|
|
|
|
type: guide
|
|
|
|
|
|
audience: human + agent
|
|
|
|
|
|
status: current
|
|
|
|
|
|
source: migrated from src/uix/eidos/THEMING_GUIDE.md (2026-07-02, docs-book F7.3; originally extracted from THEMING §8 + §9)
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# Eidos Theming — Authoring guides
|
|
|
|
|
|
|
|
|
|
|
|
> The two theming how-tos: add a new component and define a theme. Extracted
|
docs(book): F7.3 (9/9) — THEMING reference translated; theming/ + canon/ batch COMPLETE
src/uix/eidos/THEMING.md (1499 L, Spanish) translated to English as
docs/theming/reference.md, same s1-s38 numbering: the mental model,
s1.bis (theming lives in eidos — the 2-of-3 derivation and the canonical
split), the CSS layers, the 7 token layers, the 9 roles, the size canon
(universal 1:1 + label step-down + container cap + the icon scale), the
naming conventions, the s7/s8/s9/s13/s15/s17/s18 stubs repointed into the
book (canon/tsc, theming/guide, theming/notes, theming/changelog,
theming/motion), runtime overrides + builders, bundle/purge, validation
tooling, s14 motion (the pickup lift), s16 anti-patterns, s19 variants
canon, and the s20-s38 standing-decision stubs. One internal
reconciliation applied: the s35 stub cited the control/compact/dense size
archetypes that s5 declares superseded by the universal 1:1 — aligned.
One stale v1 DEMO_AUTHORING s12.8 citation in s5 replaced by the v2 s6
parity rule. Stub at the old path carries the full s1-s38 map (the most
sN-cited doc in the repo: code comments, CLAUDE.md, RFCs). Corpus links
swept (CANON, docs map, comparison, glossary, eidos chapter, canon/tsc,
theming/guide + notes). docs:check 0 errors (260 docs).
F7.3 complete: docs/canon/ (tsc, recipe-contract) + docs/theming/
(reference, guide, notes, channels, motion, motion-guide, changelog).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
> from [`THEMING.md`](./reference.md) (the E1 reference) to
|
docs(book): F7.3 (2/3) — theming/ satellites: guide + notes + channels + motion-guide + changelog
Five theming docs into the book tree:
- docs/theming/guide.md — THEMING_GUIDE (241 L, Spanish -> English): add a
component's recipe step-by-step + the three theme-definition modes.
- docs/theming/notes.md — THEMING_NOTES (168 L, Spanish -> English): the
bundle/feature comparison + the controversial-decisions FAQ (in-page
anchor to s1.bis fixed to a real THEMING link).
- docs/theming/channels.md — CHANNELS_SYNTHESIS (115 L, Spanish ->
English): the per-channel-RFC capstone (two moments, 8 expression vs 3
runtime channels, the builder sextet + applyTheme).
- docs/theming/motion-guide.md — MOTION_GUIDE (213 L, already English):
moved with frontmatter + links repointed.
- docs/theming/changelog.md — THEMING_CHANGELOG (1310 L): chronicle,
moved VERBATIM (recorded history is not translated), links repointed.
Thin stubs at all five old paths; corpus links swept (docs map E3/E4
rows, building-a-component phase 5, decisions umbrella, getting-started,
eidos chapter). docs:check 0 errors (258 docs).
Remaining in F7.3: THEMING.md itself (1499 L) + eidos-motion.md (698 L).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
> live as an E4 guide. The token contract holding all of this together is
|
|
|
|
|
|
> [`canon/tsc.md`](../canon/tsc.md); the transversal systems every recipe
|
|
|
|
|
|
> must consume are [`canon/recipe-contract.md`](../canon/recipe-contract.md).
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## How to add a new component
|
|
|
|
|
|
|
|
|
|
|
|
Assuming you already have the morfo, the soma and the eidos wrapper
|
|
|
|
|
|
scaffolding (`src/uix/eidos/components/{name}/`):
|
|
|
|
|
|
|
|
|
|
|
|
### Step 1 — Decide which tokens you need
|
|
|
|
|
|
|
|
|
|
|
|
Look at similar components (`button`, `toggle`, `switch`). Identify which
|
|
|
|
|
|
dimensions your component exposes:
|
|
|
|
|
|
|
|
|
|
|
|
- Does it have `data-color`? → palette tokens.
|
|
|
|
|
|
- Does it have `data-variant`? → variant tokens.
|
|
|
|
|
|
- Does it have `data-size`? → size tokens.
|
|
|
|
|
|
- How many parts does it have? → per-part tokens.
|
|
|
|
|
|
|
|
|
|
|
|
### Step 2 — Add the recipe in `lib/recipes/base.ts`
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
// Within THEME_BASE_RECIPE_TOKENS:
|
|
|
|
|
|
'my-component': {
|
|
|
|
|
|
// size tokens — scope 'root' (stable)
|
|
|
|
|
|
'height-md': '36px',
|
|
|
|
|
|
'padding-inline-md': 'var(--space-3)',
|
|
|
|
|
|
'gap': 'var(--space-2)',
|
|
|
|
|
|
'radius': 'var(--radius-md)',
|
|
|
|
|
|
|
|
|
|
|
|
// per-color literal definitions — scope 'root'
|
|
|
|
|
|
'primary-solid': 'var(--color-primary-solid)',
|
|
|
|
|
|
'affirm-solid': 'var(--color-affirm-solid)',
|
|
|
|
|
|
'threat-solid': 'var(--color-threat-solid)',
|
|
|
|
|
|
|
|
|
|
|
|
// dynamic palette — 'host' default + per-color overrides
|
|
|
|
|
|
'palette-solid': {
|
|
|
|
|
|
declarations: [
|
|
|
|
|
|
{ value: 'var(--my-component-primary-solid)', scope: 'host' },
|
|
|
|
|
|
{ value: 'var(--my-component-affirm-solid)', scope: 'color:affirm' },
|
|
|
|
|
|
{ value: 'var(--my-component-threat-solid)', scope: 'color:threat' }
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// derived tokens — scope 'host' (deps inferred)
|
|
|
|
|
|
'solid-bg': {
|
|
|
|
|
|
value: 'var(--my-component-palette-solid)',
|
|
|
|
|
|
scope: 'host'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Step 3 — Regenerate
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
npm run generate:eidos-css
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
If your config violates the TSC, the regeneration tells you:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Eidos recipe scope contract violations:
|
|
|
|
|
|
- my-component.solid-bg (scope root): dependency 'palette-solid' is only
|
|
|
|
|
|
declared at scopes [host], none of which is reachable from the
|
|
|
|
|
|
consumer's scope.
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Step 4 — Write the recipe CSS
|
|
|
|
|
|
|
|
|
|
|
|
`src/uix/eidos/components/my-component/my-component.css`:
|
|
|
|
|
|
|
|
|
|
|
|
```css
|
|
|
|
|
|
[data-my-component] {
|
|
|
|
|
|
/* Private tokens — only this recipe reads them */
|
|
|
|
|
|
--_my-component-bg: var(--my-component-solid-bg);
|
|
|
|
|
|
--_my-component-radius: var(--my-component-radius);
|
|
|
|
|
|
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding-inline: var(--my-component-padding-inline-md);
|
|
|
|
|
|
height: var(--my-component-height-md);
|
|
|
|
|
|
border-radius: var(--_my-component-radius);
|
|
|
|
|
|
background: var(--_my-component-bg);
|
|
|
|
|
|
gap: var(--my-component-gap);
|
docs(book): F7.3 (2/3) — theming/ satellites: guide + notes + channels + motion-guide + changelog
Five theming docs into the book tree:
- docs/theming/guide.md — THEMING_GUIDE (241 L, Spanish -> English): add a
component's recipe step-by-step + the three theme-definition modes.
- docs/theming/notes.md — THEMING_NOTES (168 L, Spanish -> English): the
bundle/feature comparison + the controversial-decisions FAQ (in-page
anchor to s1.bis fixed to a real THEMING link).
- docs/theming/channels.md — CHANNELS_SYNTHESIS (115 L, Spanish ->
English): the per-channel-RFC capstone (two moments, 8 expression vs 3
runtime channels, the builder sextet + applyTheme).
- docs/theming/motion-guide.md — MOTION_GUIDE (213 L, already English):
moved with frontmatter + links repointed.
- docs/theming/changelog.md — THEMING_CHANGELOG (1310 L): chronicle,
moved VERBATIM (recorded history is not translated), links repointed.
Thin stubs at all five old paths; corpus links swept (docs map E3/E4
rows, building-a-component phase 5, decisions umbrella, getting-started,
eidos chapter). docs:check 0 errors (258 docs).
Remaining in F7.3: THEMING.md itself (1499 L) + eidos-motion.md (698 L).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[data-my-component][data-disabled] {
|
|
|
|
|
|
opacity: var(--opacity-disabled);
|
|
|
|
|
|
pointer-events: none;
|
docs(book): F7.3 (2/3) — theming/ satellites: guide + notes + channels + motion-guide + changelog
Five theming docs into the book tree:
- docs/theming/guide.md — THEMING_GUIDE (241 L, Spanish -> English): add a
component's recipe step-by-step + the three theme-definition modes.
- docs/theming/notes.md — THEMING_NOTES (168 L, Spanish -> English): the
bundle/feature comparison + the controversial-decisions FAQ (in-page
anchor to s1.bis fixed to a real THEMING link).
- docs/theming/channels.md — CHANNELS_SYNTHESIS (115 L, Spanish ->
English): the per-channel-RFC capstone (two moments, 8 expression vs 3
runtime channels, the builder sextet + applyTheme).
- docs/theming/motion-guide.md — MOTION_GUIDE (213 L, already English):
moved with frontmatter + links repointed.
- docs/theming/changelog.md — THEMING_CHANGELOG (1310 L): chronicle,
moved VERBATIM (recorded history is not translated), links repointed.
Thin stubs at all five old paths; corpus links swept (docs map E3/E4
rows, building-a-component phase 5, decisions umbrella, getting-started,
eidos chapter). docs:check 0 errors (258 docs).
Remaining in F7.3: THEMING.md itself (1499 L) + eidos-motion.md (698 L).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Step 5 — Wire the CSS
|
|
|
|
|
|
|
|
|
|
|
|
Code-split components import their own CSS from the wrapper `.svelte`;
|
|
|
|
|
|
layout primitives and shared visuals aggregate in `index.css`:
|
|
|
|
|
|
|
|
|
|
|
|
```css
|
|
|
|
|
|
@import './components/my-component/my-component.css';
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Step 6 — Verify
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
npm run generate:eidos-css
|
|
|
|
|
|
npm test -- src/uix/eidos
|
|
|
|
|
|
npm run morfo:check
|
|
|
|
|
|
node --import tsx/esm scripts/eidos-lint.ts my-component
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Common anti-pattern: declaring composed tokens at `:root`
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
// ❌ WRONG — the TSC will fail
|
|
|
|
|
|
'my-component': {
|
|
|
|
|
|
'palette-solid': { value: 'var(--color-neutral-solid)', scope: 'host' },
|
|
|
|
|
|
'solid-bg': 'var(--my-component-palette-solid)' // ← implicit 'root' scope, deps live in 'host'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✓ CORRECT
|
|
|
|
|
|
'my-component': {
|
|
|
|
|
|
'palette-solid': { value: 'var(--color-neutral-solid)', scope: 'host' },
|
|
|
|
|
|
'solid-bg': {
|
|
|
|
|
|
value: 'var(--my-component-palette-solid)',
|
|
|
|
|
|
scope: 'host'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## How to define a theme
|
|
|
|
|
|
|
feat(eidos)!: un tema DICE lo que es — appearance obligatorio en ThemeDefinition, una sola fuente de verdad
39edbc8db dejaba la apariencia como OPCION del que renderiza
(RenderThemeCssOptions.appearance, pasada a mano por generated-css.ts):
apply() y renderCss() salian mudos, y habia DOS fuentes de verdad porque
la apariencia ya vivia implicita en el sufijo del id (/-(light|dark)$/).
Forma firmada por el autor:
- ThemeDefinition.appearance: ThemeEffective (el tipo de $libs/theme, sin
union nueva), OBLIGATORIO. Tres palabras: mode = la preferencia
(data-mode) · theme = lo pintado (data-theme) · appearance = que ES el
tema. No `colorScheme`: ese nombre es de la paleta derivada de seed.
- renderThemeCss lo lee del TEMA y lo emite SIEMPRE como primera
declaracion; la opcion desaparece sin shim. apply(), renderCss() y el
generador lo emiten gratis.
- El sufijo del id baja a convencion de BUSQUEDA del resolver: el
validador exige el campo (gate de forma, el que protege documentos) y
cierra la contradiccion sufijo<->apariencia (gate semantico). Un tema
nuevo por EidosConfigPatch sin el campo cae en el primero.
- Todo bloque que pinta una apariencia la declara: el bloque del seed
(applyColorScheme, que admite forzar el donante) emite color-scheme
desde la apariencia del tema DONANTE resuelto, no del mode crudo;
helper #resolveSchemeDonorThemeId compartido para resolverlo una vez.
- Documento persistido v1 -> v2, sin migracion: nadie puede adivinar la
apariencia de un tema viejo.
generated/ no cambia ni un byte (las dos lineas ya estaban; ahora vienen
del tema): renderGeneratedBaseEidosCss() sigue en 5751 nombres unicos /
8034 ocurrencias, contrato y censo quietos. Suite eidos + value-channels
460/460 (8 tests nuevos, 4 mutaciones probadas). check 71 -> 73: los dos
son web/routes/alpha/lib/docs-theme.ts sin appearance — el arbol
congelado pierde el tipo por diseño y entra en el ledger con la unica
excepcion escrita (crece SOLO cuando el framework se mueve bajo el arbol
provisional); grafito.ts y theme-variants.ts ya fallaban por literal y no
suben. src/ a cero. check:gate OK, eidos:lint 0.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
Every theme declares **what it is** — `appearance: 'light' | 'dark'`. It is
|
|
|
|
|
|
mandatory, never inferred: it governs `color-scheme`, the first declaration
|
|
|
|
|
|
of the theme block, which rules the surface the UA paints and eidos cannot
|
|
|
|
|
|
style (the native `<select>` option popup, form controls, scrollbars).
|
|
|
|
|
|
|
|
|
|
|
|
Three words, three meanings, no overlap:
|
|
|
|
|
|
|
|
|
|
|
|
| Word | Means | Lives in |
|
|
|
|
|
|
| ------------ | --------------------------- | ---------------------------- |
|
|
|
|
|
|
| `mode` | the PREFERENCE the user set | `data-mode` |
|
|
|
|
|
|
| `theme` | what is PAINTED | `data-theme` |
|
|
|
|
|
|
| `appearance` | what the theme IS | `ThemeDefinition.appearance` |
|
|
|
|
|
|
|
|
|
|
|
|
A `-light` / `-dark` suffix on a theme id is the default resolver's **lookup**
|
|
|
|
|
|
convention (`${theme}-${mode}`), not a source of truth; the config validator
|
|
|
|
|
|
refuses a theme whose suffix contradicts its declared `appearance`. (The name
|
|
|
|
|
|
`colorScheme` is taken in eidos by the seed-derived palette,
|
|
|
|
|
|
`applyColorScheme` — hence `appearance`.)
|
|
|
|
|
|
|
docs(book): F7.3 (2/3) — theming/ satellites: guide + notes + channels + motion-guide + changelog
Five theming docs into the book tree:
- docs/theming/guide.md — THEMING_GUIDE (241 L, Spanish -> English): add a
component's recipe step-by-step + the three theme-definition modes.
- docs/theming/notes.md — THEMING_NOTES (168 L, Spanish -> English): the
bundle/feature comparison + the controversial-decisions FAQ (in-page
anchor to s1.bis fixed to a real THEMING link).
- docs/theming/channels.md — CHANNELS_SYNTHESIS (115 L, Spanish ->
English): the per-channel-RFC capstone (two moments, 8 expression vs 3
runtime channels, the builder sextet + applyTheme).
- docs/theming/motion-guide.md — MOTION_GUIDE (213 L, already English):
moved with frontmatter + links repointed.
- docs/theming/changelog.md — THEMING_CHANGELOG (1310 L): chronicle,
moved VERBATIM (recorded history is not translated), links repointed.
Thin stubs at all five old paths; corpus links swept (docs map E3/E4
rows, building-a-component phase 5, decisions umbrella, getting-started,
eidos chapter). docs:check 0 errors (258 docs).
Remaining in F7.3: THEMING.md itself (1499 L) + eidos-motion.md (698 L).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
Eidos supports **3 modes** of theme definition:
|
|
|
|
|
|
|
|
|
|
|
|
### Mode 1 — A patch over the base theme (recommended)
|
|
|
|
|
|
|
|
|
|
|
|
Change only what you need; everything else follows the base:
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
import { ActiveEidos } from '$uix/eidos';
|
|
|
|
|
|
|
|
|
|
|
|
ActiveEidos.create({
|
|
|
|
|
|
themeBase: {
|
|
|
|
|
|
semantics: {
|
|
|
|
|
|
color: {
|
|
|
|
|
|
roles: {
|
|
|
|
|
|
primary: 'blue', // primary uses the Radix blue scale
|
|
|
|
|
|
secondary: 'plum'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
primitives: {
|
|
|
|
|
|
typography: {
|
|
|
|
|
|
families: {
|
|
|
|
|
|
primary: { family: 'Inter' }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
applyDom: true
|
docs(book): F7.3 (2/3) — theming/ satellites: guide + notes + channels + motion-guide + changelog
Five theming docs into the book tree:
- docs/theming/guide.md — THEMING_GUIDE (241 L, Spanish -> English): add a
component's recipe step-by-step + the three theme-definition modes.
- docs/theming/notes.md — THEMING_NOTES (168 L, Spanish -> English): the
bundle/feature comparison + the controversial-decisions FAQ (in-page
anchor to s1.bis fixed to a real THEMING link).
- docs/theming/channels.md — CHANNELS_SYNTHESIS (115 L, Spanish ->
English): the per-channel-RFC capstone (two moments, 8 expression vs 3
runtime channels, the builder sextet + applyTheme).
- docs/theming/motion-guide.md — MOTION_GUIDE (213 L, already English):
moved with frontmatter + links repointed.
- docs/theming/changelog.md — THEMING_CHANGELOG (1310 L): chronicle,
moved VERBATIM (recorded history is not translated), links repointed.
Thin stubs at all five old paths; corpus links swept (docs map E3/E4
rows, building-a-component phase 5, decisions umbrella, getting-started,
eidos chapter). docs:check 0 errors (258 docs).
Remaining in F7.3: THEMING.md itself (1499 L) + eidos-motion.md (698 L).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Mode 2 — A full config
|
|
|
|
|
|
|
|
|
|
|
|
For authoring from scratch:
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
import { ActiveEidos, defineEidosConfig } from '$uix/eidos';
|
|
|
|
|
|
|
|
|
|
|
|
const config = defineEidosConfig({
|
|
|
|
|
|
primitives: {
|
|
|
|
|
|
/* … */
|
|
|
|
|
|
},
|
|
|
|
|
|
semantics: {
|
|
|
|
|
|
color: {
|
|
|
|
|
|
/* … */
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
themes: {
|
|
|
|
|
|
'acme-light': {
|
|
|
|
|
|
appearance: 'light',
|
|
|
|
|
|
color: {
|
|
|
|
|
|
/* … */
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
'acme-dark': {
|
|
|
|
|
|
appearance: 'dark',
|
|
|
|
|
|
color: {
|
|
|
|
|
|
/* … */
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
docs(book): F7.3 (2/3) — theming/ satellites: guide + notes + channels + motion-guide + changelog
Five theming docs into the book tree:
- docs/theming/guide.md — THEMING_GUIDE (241 L, Spanish -> English): add a
component's recipe step-by-step + the three theme-definition modes.
- docs/theming/notes.md — THEMING_NOTES (168 L, Spanish -> English): the
bundle/feature comparison + the controversial-decisions FAQ (in-page
anchor to s1.bis fixed to a real THEMING link).
- docs/theming/channels.md — CHANNELS_SYNTHESIS (115 L, Spanish ->
English): the per-channel-RFC capstone (two moments, 8 expression vs 3
runtime channels, the builder sextet + applyTheme).
- docs/theming/motion-guide.md — MOTION_GUIDE (213 L, already English):
moved with frontmatter + links repointed.
- docs/theming/changelog.md — THEMING_CHANGELOG (1310 L): chronicle,
moved VERBATIM (recorded history is not translated), links repointed.
Thin stubs at all five old paths; corpus links swept (docs map E3/E4
rows, building-a-component phase 5, decisions umbrella, getting-started,
eidos chapter). docs:check 0 errors (258 docs).
Remaining in F7.3: THEMING.md itself (1499 L) + eidos-motion.md (698 L).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
ActiveEidos.create({ config, applyDom: true });
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Mode 3 — A CSS-only theme (no TypeScript)
|
|
|
|
|
|
|
|
|
|
|
|
Eidos publishes the contract as empty CSS for externals to fill:
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
const contract = activeEidos.renderContractCss({
|
|
|
|
|
|
themeSelector: "[data-theme='acme-light']"
|
docs(book): F7.3 (2/3) — theming/ satellites: guide + notes + channels + motion-guide + changelog
Five theming docs into the book tree:
- docs/theming/guide.md — THEMING_GUIDE (241 L, Spanish -> English): add a
component's recipe step-by-step + the three theme-definition modes.
- docs/theming/notes.md — THEMING_NOTES (168 L, Spanish -> English): the
bundle/feature comparison + the controversial-decisions FAQ (in-page
anchor to s1.bis fixed to a real THEMING link).
- docs/theming/channels.md — CHANNELS_SYNTHESIS (115 L, Spanish ->
English): the per-channel-RFC capstone (two moments, 8 expression vs 3
runtime channels, the builder sextet + applyTheme).
- docs/theming/motion-guide.md — MOTION_GUIDE (213 L, already English):
moved with frontmatter + links repointed.
- docs/theming/changelog.md — THEMING_CHANGELOG (1310 L): chronicle,
moved VERBATIM (recorded history is not translated), links repointed.
Thin stubs at all five old paths; corpus links swept (docs map E3/E4
rows, building-a-component phase 5, decisions umbrella, getting-started,
eidos chapter). docs:check 0 errors (258 docs).
Remaining in F7.3: THEMING.md itself (1499 L) + eidos-motion.md (698 L).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
});
|
|
|
|
|
|
// Output:
|
|
|
|
|
|
// [data-theme='acme-light'] {
|
|
|
|
|
|
// --scale-blue-9: ;
|
|
|
|
|
|
// --color-primary-solid: ;
|
|
|
|
|
|
// --size-md-control-height: ;
|
|
|
|
|
|
// ...
|
|
|
|
|
|
// }
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
The consumer fills in the values:
|
|
|
|
|
|
|
|
|
|
|
|
```css
|
|
|
|
|
|
[data-theme='acme-light'] {
|
|
|
|
|
|
--scale-blue-9: #006adc;
|
|
|
|
|
|
--color-primary-solid: var(--scale-blue-9);
|
|
|
|
|
|
--size-md-control-height: 38px;
|
docs(book): F7.3 (2/3) — theming/ satellites: guide + notes + channels + motion-guide + changelog
Five theming docs into the book tree:
- docs/theming/guide.md — THEMING_GUIDE (241 L, Spanish -> English): add a
component's recipe step-by-step + the three theme-definition modes.
- docs/theming/notes.md — THEMING_NOTES (168 L, Spanish -> English): the
bundle/feature comparison + the controversial-decisions FAQ (in-page
anchor to s1.bis fixed to a real THEMING link).
- docs/theming/channels.md — CHANNELS_SYNTHESIS (115 L, Spanish ->
English): the per-channel-RFC capstone (two moments, 8 expression vs 3
runtime channels, the builder sextet + applyTheme).
- docs/theming/motion-guide.md — MOTION_GUIDE (213 L, already English):
moved with frontmatter + links repointed.
- docs/theming/changelog.md — THEMING_CHANGELOG (1310 L): chronicle,
moved VERBATIM (recorded history is not translated), links repointed.
Thin stubs at all five old paths; corpus links swept (docs map E3/E4
rows, building-a-component phase 5, decisions umbrella, getting-started,
eidos chapter). docs:check 0 errors (258 docs).
Remaining in F7.3: THEMING.md itself (1499 L) + eidos-motion.md (698 L).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
And loads that CSS alongside Eidos's. With `themeSource: 'css'`,
|
|
|
|
|
|
`ActiveEidos` generates no theme of its own.
|
|
|
|
|
|
|
|
|
|
|
|
### Versioned persistence
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
const document = activeEidos.toDocument();
|
feat(eidos)!: un tema DICE lo que es — appearance obligatorio en ThemeDefinition, una sola fuente de verdad
39edbc8db dejaba la apariencia como OPCION del que renderiza
(RenderThemeCssOptions.appearance, pasada a mano por generated-css.ts):
apply() y renderCss() salian mudos, y habia DOS fuentes de verdad porque
la apariencia ya vivia implicita en el sufijo del id (/-(light|dark)$/).
Forma firmada por el autor:
- ThemeDefinition.appearance: ThemeEffective (el tipo de $libs/theme, sin
union nueva), OBLIGATORIO. Tres palabras: mode = la preferencia
(data-mode) · theme = lo pintado (data-theme) · appearance = que ES el
tema. No `colorScheme`: ese nombre es de la paleta derivada de seed.
- renderThemeCss lo lee del TEMA y lo emite SIEMPRE como primera
declaracion; la opcion desaparece sin shim. apply(), renderCss() y el
generador lo emiten gratis.
- El sufijo del id baja a convencion de BUSQUEDA del resolver: el
validador exige el campo (gate de forma, el que protege documentos) y
cierra la contradiccion sufijo<->apariencia (gate semantico). Un tema
nuevo por EidosConfigPatch sin el campo cae en el primero.
- Todo bloque que pinta una apariencia la declara: el bloque del seed
(applyColorScheme, que admite forzar el donante) emite color-scheme
desde la apariencia del tema DONANTE resuelto, no del mode crudo;
helper #resolveSchemeDonorThemeId compartido para resolverlo una vez.
- Documento persistido v1 -> v2, sin migracion: nadie puede adivinar la
apariencia de un tema viejo.
generated/ no cambia ni un byte (las dos lineas ya estaban; ahora vienen
del tema): renderGeneratedBaseEidosCss() sigue en 5751 nombres unicos /
8034 ocurrencias, contrato y censo quietos. Suite eidos + value-channels
460/460 (8 tests nuevos, 4 mutaciones probadas). check 71 -> 73: los dos
son web/routes/alpha/lib/docs-theme.ts sin appearance — el arbol
congelado pierde el tipo por diseño y entra en el ledger con la unica
excepcion escrita (crece SOLO cuando el framework se mueve bajo el arbol
provisional); grafito.ts y theme-variants.ts ya fallaban por literal y no
suben. src/ a cero. check:gate OK, eidos:lint 0.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
// → { kind: 'uix.eidos-config', version: 2, options: {...} }
|
docs(book): F7.3 (2/3) — theming/ satellites: guide + notes + channels + motion-guide + changelog
Five theming docs into the book tree:
- docs/theming/guide.md — THEMING_GUIDE (241 L, Spanish -> English): add a
component's recipe step-by-step + the three theme-definition modes.
- docs/theming/notes.md — THEMING_NOTES (168 L, Spanish -> English): the
bundle/feature comparison + the controversial-decisions FAQ (in-page
anchor to s1.bis fixed to a real THEMING link).
- docs/theming/channels.md — CHANNELS_SYNTHESIS (115 L, Spanish ->
English): the per-channel-RFC capstone (two moments, 8 expression vs 3
runtime channels, the builder sextet + applyTheme).
- docs/theming/motion-guide.md — MOTION_GUIDE (213 L, already English):
moved with frontmatter + links repointed.
- docs/theming/changelog.md — THEMING_CHANGELOG (1310 L): chronicle,
moved VERBATIM (recorded history is not translated), links repointed.
Thin stubs at all five old paths; corpus links swept (docs map E3/E4
rows, building-a-component phase 5, decisions umbrella, getting-started,
eidos chapter). docs:check 0 errors (258 docs).
Remaining in F7.3: THEMING.md itself (1499 L) + eidos-motion.md (698 L).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
|
|
|
|
|
|
const json = activeEidos.serialize();
|
|
|
|
|
|
localStorage.setItem('user-theme', json);
|
|
|
|
|
|
|
|
|
|
|
|
// Later
|
|
|
|
|
|
const hydrated = createActiveEidos({
|
|
|
|
|
|
config: JSON.parse(localStorage.getItem('user-theme')!),
|
|
|
|
|
|
prefs,
|
|
|
|
|
|
dom
|
docs(book): F7.3 (2/3) — theming/ satellites: guide + notes + channels + motion-guide + changelog
Five theming docs into the book tree:
- docs/theming/guide.md — THEMING_GUIDE (241 L, Spanish -> English): add a
component's recipe step-by-step + the three theme-definition modes.
- docs/theming/notes.md — THEMING_NOTES (168 L, Spanish -> English): the
bundle/feature comparison + the controversial-decisions FAQ (in-page
anchor to s1.bis fixed to a real THEMING link).
- docs/theming/channels.md — CHANNELS_SYNTHESIS (115 L, Spanish ->
English): the per-channel-RFC capstone (two moments, 8 expression vs 3
runtime channels, the builder sextet + applyTheme).
- docs/theming/motion-guide.md — MOTION_GUIDE (213 L, already English):
moved with frontmatter + links repointed.
- docs/theming/changelog.md — THEMING_CHANGELOG (1310 L): chronicle,
moved VERBATIM (recorded history is not translated), links repointed.
Thin stubs at all five old paths; corpus links swept (docs map E3/E4
rows, building-a-component phase 5, decisions umbrella, getting-started,
eidos chapter). docs:check 0 errors (258 docs).
Remaining in F7.3: THEMING.md itself (1499 L) + eidos-motion.md (698 L).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
feat(eidos)!: un tema DICE lo que es — appearance obligatorio en ThemeDefinition, una sola fuente de verdad
39edbc8db dejaba la apariencia como OPCION del que renderiza
(RenderThemeCssOptions.appearance, pasada a mano por generated-css.ts):
apply() y renderCss() salian mudos, y habia DOS fuentes de verdad porque
la apariencia ya vivia implicita en el sufijo del id (/-(light|dark)$/).
Forma firmada por el autor:
- ThemeDefinition.appearance: ThemeEffective (el tipo de $libs/theme, sin
union nueva), OBLIGATORIO. Tres palabras: mode = la preferencia
(data-mode) · theme = lo pintado (data-theme) · appearance = que ES el
tema. No `colorScheme`: ese nombre es de la paleta derivada de seed.
- renderThemeCss lo lee del TEMA y lo emite SIEMPRE como primera
declaracion; la opcion desaparece sin shim. apply(), renderCss() y el
generador lo emiten gratis.
- El sufijo del id baja a convencion de BUSQUEDA del resolver: el
validador exige el campo (gate de forma, el que protege documentos) y
cierra la contradiccion sufijo<->apariencia (gate semantico). Un tema
nuevo por EidosConfigPatch sin el campo cae en el primero.
- Todo bloque que pinta una apariencia la declara: el bloque del seed
(applyColorScheme, que admite forzar el donante) emite color-scheme
desde la apariencia del tema DONANTE resuelto, no del mode crudo;
helper #resolveSchemeDonorThemeId compartido para resolverlo una vez.
- Documento persistido v1 -> v2, sin migracion: nadie puede adivinar la
apariencia de un tema viejo.
generated/ no cambia ni un byte (las dos lineas ya estaban; ahora vienen
del tema): renderGeneratedBaseEidosCss() sigue en 5751 nombres unicos /
8034 ocurrencias, contrato y censo quietos. Suite eidos + value-channels
460/460 (8 tests nuevos, 4 mutaciones probadas). check 71 -> 73: los dos
son web/routes/alpha/lib/docs-theme.ts sin appearance — el arbol
congelado pierde el tipo por diseño y entra en el ledger con la unica
excepcion escrita (crece SOLO cuando el framework se mueve bajo el arbol
provisional); grafito.ts y theme-variants.ts ya fallaban por literal y no
suben. src/ a cero. check:gate OK, eidos:lint 0.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
The document envelope carries a `version` for future migrations. A v1
|
|
|
|
|
|
document (written before `ThemeDefinition.appearance` became mandatory) is
|
|
|
|
|
|
rejected, not migrated — nobody can guess whether an old theme was light or
|
|
|
|
|
|
dark.
|
feat(prefs)!: un motor, un sobre, un codigo — mode/theme/density/scaling son prefs, sobre canonico y boot compilado
Auditoria P2 #2: los attrs de preferencias se estampaban solo en cliente
tras hidratar (app.html sin script, build estatico sin servidor) — flash
claro para todo usuario dark — y no podia existir un script de arranque
porque no habia contrato de persistencia (PrefsIntentStorage lo aporta
cada app) y eidos llevaba un SEGUNDO motor de preferencias (theme /
modeSource / densitySource / scalingSource por opciones, sin persistir),
por doctrina escrita en arts/prefs/README.md («UIX does not turn it into
prefs.theme»). El autor REVOCA esa doctrina.
UN MOTOR. `mode` es una dimension de prefs junto a `motion` (mismo patron,
`resolveTheme` de $libs/theme, que ya existia como gemelo de
`resolveMotion`); `theme` (id de familia), `density` y `scaling` las
declara la RAIZ en un modulo PURO (src/uix/active-uix/prefs-schema.ts) —
arts/prefs no importa src/uix. Eidos lee `uix.prefs` por su puerto
`ActiveEidosPreferenceSource` (src/uix/eidos/prefs-source.ts), como ya
consume uix.langs / uix.dom / uix.motion. El resolver de tema baja a una
funcion pura `resolveThemeId(theme, mode, isRegistered)`
(src/uix/eidos/lib/theme-id.ts); los cuatro nombres de attr de eidos a
src/uix/eidos/lib/attrs.ts y los de la proyeccion a
src/arts/prefs/dom-attrs.ts: modulos sin Svelte, porque el boot los
comparte.
UN SOBRE. src/libs/prefs/document.ts: `uix.prefs-intent` v1 bajo la clave
`uix.prefs`, espejo del documento de eidos (kind + version estrictos,
version desconocida se RECHAZA). Solo intencion — nunca efectivo ni
entorno. Adaptador localStorage canonico creado en la raiz standalone
(`prefs.storage`: propio gana, `false` desactiva). HIDRATACION SINCRONA:
el bridge awaitea `load()` aunque sea sincrono y produciria una primera
resolucion con defaults y un salto un tick despues — el flash movido de
sitio; la raiz lee el sobre ANTES de crear el motor y lo pasa como
`intent`; el bridge queda solo para persistir (skipHydrate).
UN CODIGO. El boot no se escribe: se COMPILA con esbuild desde los mismos
modulos puros (src/uix/active-uix/boot/boot.ts → generated/boot.js,
15.009 bytes / 5.625 gzip, IIFE sin globales, cero Svelte; test de
sincronia, precedente generated/base.css). `renderUixBootScript(params)`
(boot/render.ts) es Kit-agnostico: el framework entrega el string; el
hook de Kit son tres lineas de la app (receta en docs/theming/guide.md,
NO cableada: el layout congelado lleva persistencia propia). Estampa los
NUEVE attrs (dir lang data-motion data-sound data-haptic data-theme
data-mode data-density data-scaling) → DELTA CERO al hidratar, probado
por integracion con la raiz REAL (boot/boot-delta.test.ts, tres casos)
y por tres mutaciones (sin hidratacion sincrona → rojo; constante de
attr cambiada en un solo lado → rojo; boot compilado con `data-modo` →
los tres casos rojos).
Hallazgo del test de delta cero: la raiz no aplicaba el entorno del
navegador (esperaba que el app llamara detectBrowserEnvironment en
onMount) — ahora lo siembra ella (guardado por `document`) y observa sus
cambios (watchBrowserEnvironment → patchEnvironment), sustituyendo el
listener que eidos tenia en createSystemColorSchemeSource.
Cifras: vitest prefs+libs/prefs+active-uix+eidos+value-channels 57/593 ·
suite completa 453/5306 · check 73 (src/ a cero; ledger intacto) ·
check:gate OK · eidos:lint 0 · docs:check 0/0 · morfo:vocabulary,
arts/blocks/packs/rtl/translations/agent:check OK.
ESTADO INTERINO, firmado para manana (acta en CONTINUE-audit-p0.md):
`resolvePreferences` LANZA con `uix` + escalar/source (forma firmada
originalmente); medido despues, `ActiveEidos.create()` inyecta `uix`
SIEMPRE y todas las demos pasan escalares o sources (>=12 sitios,
incluido web/routes/uix/+layout@.svelte:511) → el sitio de demos lanza al
arrancar hasta la forma (c'): pines si (escalar explicito gana, precedente
`options.dom ?? options.uix?.dom`), sources reactivas por eje FUERA, sin
throw, pines en el boot. Pendientes anotados: contracts.test.ts:1270-1276
codifica la doctrina revocada; esbuild no es dependencia declarada
(transitiva de vite) — decision del autor con servidores parados.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## How to kill the dark-mode flash
|
|
|
|
|
|
|
|
|
|
|
|
`data-theme` / `data-mode` and the rest of the preference attributes are
|
|
|
|
|
|
written by `ActiveEidos.apply()` and by the prefs DOM projection — that is,
|
|
|
|
|
|
after hydration. On a static build (no server, no hooks, `src/app.html` with
|
|
|
|
|
|
no script) that is several frames after first paint, and a dark-mode user
|
|
|
|
|
|
watches a light page turn dark.
|
|
|
|
|
|
|
|
|
|
|
|
The fix is a tiny script in `<head>` that stamps the SAME attributes before
|
|
|
|
|
|
paint. It must not be hand-written: a second, hand-rolled copy of the
|
|
|
|
|
|
resolution cascade drifts from the real one on the first rename. UIX compiles
|
|
|
|
|
|
it instead, from the runtime's own modules.
|
|
|
|
|
|
|
feat(active-uix)!: el boot viaja como CUERPO CONSTANTE con los parámetros en un atributo — un hash de CSP fijo, sin node:fs, y la delta deja de confirmarse a sí misma (fila 1)
La receta documentada del boot pre-hidratación no funcionaba en ningún despliegue
real, y el panel de diseño de nueve agentes que el autor pidió lo confirmó
midiendo: con el `render.ts` anterior, un build REAL de SvelteKit con
adapter-static revienta el prerender con `ENOENT …/output/server/generated/boot.js`
y devuelve un 500. La causa: `renderUixBootScript` leía el bundle DEL DISCO junto
a su propio módulo, y una vez empaquetado ese fichero no está ahí.
FORMA NUEVA. El generador compila una entrada propia (`boot/entry.ts`), sin
`globalName`, y emite el MISMO fichero de siempre (`generated/boot.js`, sin
borrar nada) como módulo con dos constantes: el texto exacto del script y su
`sha256-…`. `renderUixBootScript` pierde `node:fs`, `node:path`, `node:url`, la
caché y el envoltorio que estaba ESCRITO A MANO: al no haber `globalName`, la
llamada la hace la propia entrada compilada. «Compilado, no escrito» sale
reforzado, no rebajado.
LOS PARÁMETROS VIAJAN EN UN ATRIBUTO del mismo `<script>`, no dentro del cuerpo.
Consecuencia, que es el punto entero de la fila: el cuerpo es CONSTANTE, así que
su hash es constante por versión del framework, igual para todos los sitios y
todos los `themeIds`. La guía pasa a tener UNA sola receta, por hash, que cubre
prerender, SSR y hasta `mode: 'nonce'`; el nonce baja a salida de emergencia. Un
bloque `application/json` hermano se descartó con razón medida: rompería la
invariante de que el tag parsea a exactamente un `<script>` y un minificador de
HTML podría borrarlo sin que nadie se entere.
TRES MENTIRAS DE LA GUÍA, CORREGIDAS. `event.locals.nonce` no existe en Kit
2.55.0 (cero ocurrencias en su código; `respond.js:168` es `locals: {}`), así que
la receta anterior pasaba `undefined`, el navegador bloqueaba el script y volvía
el flash sin un solo error. El placeholder estaba ENCIMA de `%sveltekit.head%`,
donde la política de prerender de Kit no lo alcanza: eso es evadir la CSP, no
cumplirla, y no vale cuando la política llega por cabecera. Y el `html.replace`
con cadena interpreta `$'`, así que ahora la receta usa reemplazo FUNCIÓN. Se
añade el aviso que puede tumbarle la página al consumidor: añadir un hash a un
`script-src` que lleva `'unsafe-inline'` hace que el navegador IGNORE
`unsafe-inline` y bloquee los demás scripts inline del sitio, incluido el
arranque de Kit.
EL CRITERIO SE CORRIGE. `boot-delta.test.ts` gana un tercer snapshot contra un
documento LIMPIO. El anterior arrancaba el runtime sobre el documento ya sellado,
y como `directionDimension.derive` devuelve `env.direction` antes de derivar del
idioma y el entorno se siembra leyendo el `<html dir>` que el boot acaba de
escribir, la columna `dir` se confirmaba a sí misma. Medido con el boot clavado a
`rtl`: los cinco rojos caen todos en la lectura nueva y ninguno en la anterior.
AUDIT DEV. La raíz compara en desarrollo su primera proyección con lo que ya hay
en `<html>` y NOMBRA los ejes que discrepan. Convierte en ruidosos cinco fallos
que hoy son idénticos y mudos: pin olvidado, `themeIds` incompleto, `storageKey`
distinto, artefacto rancio y script bloqueado por la CSP.
Artefacto 14 666 B, `sha256-5fulJ/2Q6DttR258KqIhWSwfm5g7973BEzzzq14fapY=`,
generación determinista. Verificación: suite entera 461 ficheros / 5404 tests, exit 0 · check con
0 errores bajo `src/` y bajo `scripts/` (89 en `web/`, ledger intacto) ·
docs:check 0/0/819 · `npx vitest run src/uix/active-uix` 9 ficheros / 97 tests.
El test nuevo `boot/pack.test.ts` empaqueta `render.ts` y lo ejecuta: sale ROJO
contra el código anterior con el ENOENT literal, y es lo único que impide que
esta clase vuelva.
Adversarial Opus independiente (árbol byte-idéntico al terminar, 14 mutaciones
revertidas): midió el hash bajo CSP real contra un servidor HTTP en Chromium 145,
Firefox 146 y WebKit 26, con control negativo bloqueado; montó una app SvelteKit
real siguiendo la guía al pie de la letra y la vio construir, prerenderizar y
arrancar sin violaciones; y probó el ENOENT con un build real. Declaró 5 defectos
del lote, ninguno bloqueante, cerrados en una segunda ronda y re-verificados uno
a uno con su reproducción original: un universal falso en un comentario, la
puerta del artefacto sin validar el cuerpo (ahora con guard propio importable),
la constante del hash sin comillas para quien escriba su propia cabecera, el
audit que SÍ viaja al bundle de producción (1 428 B medidos, no un supuesto; no
CORRE, gracias a un ternario que el bundler no pliega) y el eje que se nombraba a
profundidad 2 y ahora va en el mensaje. La redacción del guard del artefacto,
último defecto de la segunda pasada, la corregí yo: corre en cada render, 76 us
con artefacto frente a 1,49 us sin él, medido.
Filas que deja para el autor: los 1 428 B del audit en producción, si se gatean
con una bandera que el bundler pliegue · el audit queda mudo si el runtime se
desecha en el mismo turno · la fila 2 debe decidir si la puerta verifica que el
hash describa al cuerpo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
### Step 1 — Generate the artifact (once, and after touching the schema)
|
feat(prefs)!: un motor, un sobre, un codigo — mode/theme/density/scaling son prefs, sobre canonico y boot compilado
Auditoria P2 #2: los attrs de preferencias se estampaban solo en cliente
tras hidratar (app.html sin script, build estatico sin servidor) — flash
claro para todo usuario dark — y no podia existir un script de arranque
porque no habia contrato de persistencia (PrefsIntentStorage lo aporta
cada app) y eidos llevaba un SEGUNDO motor de preferencias (theme /
modeSource / densitySource / scalingSource por opciones, sin persistir),
por doctrina escrita en arts/prefs/README.md («UIX does not turn it into
prefs.theme»). El autor REVOCA esa doctrina.
UN MOTOR. `mode` es una dimension de prefs junto a `motion` (mismo patron,
`resolveTheme` de $libs/theme, que ya existia como gemelo de
`resolveMotion`); `theme` (id de familia), `density` y `scaling` las
declara la RAIZ en un modulo PURO (src/uix/active-uix/prefs-schema.ts) —
arts/prefs no importa src/uix. Eidos lee `uix.prefs` por su puerto
`ActiveEidosPreferenceSource` (src/uix/eidos/prefs-source.ts), como ya
consume uix.langs / uix.dom / uix.motion. El resolver de tema baja a una
funcion pura `resolveThemeId(theme, mode, isRegistered)`
(src/uix/eidos/lib/theme-id.ts); los cuatro nombres de attr de eidos a
src/uix/eidos/lib/attrs.ts y los de la proyeccion a
src/arts/prefs/dom-attrs.ts: modulos sin Svelte, porque el boot los
comparte.
UN SOBRE. src/libs/prefs/document.ts: `uix.prefs-intent` v1 bajo la clave
`uix.prefs`, espejo del documento de eidos (kind + version estrictos,
version desconocida se RECHAZA). Solo intencion — nunca efectivo ni
entorno. Adaptador localStorage canonico creado en la raiz standalone
(`prefs.storage`: propio gana, `false` desactiva). HIDRATACION SINCRONA:
el bridge awaitea `load()` aunque sea sincrono y produciria una primera
resolucion con defaults y un salto un tick despues — el flash movido de
sitio; la raiz lee el sobre ANTES de crear el motor y lo pasa como
`intent`; el bridge queda solo para persistir (skipHydrate).
UN CODIGO. El boot no se escribe: se COMPILA con esbuild desde los mismos
modulos puros (src/uix/active-uix/boot/boot.ts → generated/boot.js,
15.009 bytes / 5.625 gzip, IIFE sin globales, cero Svelte; test de
sincronia, precedente generated/base.css). `renderUixBootScript(params)`
(boot/render.ts) es Kit-agnostico: el framework entrega el string; el
hook de Kit son tres lineas de la app (receta en docs/theming/guide.md,
NO cableada: el layout congelado lleva persistencia propia). Estampa los
NUEVE attrs (dir lang data-motion data-sound data-haptic data-theme
data-mode data-density data-scaling) → DELTA CERO al hidratar, probado
por integracion con la raiz REAL (boot/boot-delta.test.ts, tres casos)
y por tres mutaciones (sin hidratacion sincrona → rojo; constante de
attr cambiada en un solo lado → rojo; boot compilado con `data-modo` →
los tres casos rojos).
Hallazgo del test de delta cero: la raiz no aplicaba el entorno del
navegador (esperaba que el app llamara detectBrowserEnvironment en
onMount) — ahora lo siembra ella (guardado por `document`) y observa sus
cambios (watchBrowserEnvironment → patchEnvironment), sustituyendo el
listener que eidos tenia en createSystemColorSchemeSource.
Cifras: vitest prefs+libs/prefs+active-uix+eidos+value-channels 57/593 ·
suite completa 453/5306 · check 73 (src/ a cero; ledger intacto) ·
check:gate OK · eidos:lint 0 · docs:check 0/0 · morfo:vocabulary,
arts/blocks/packs/rtl/translations/agent:check OK.
ESTADO INTERINO, firmado para manana (acta en CONTINUE-audit-p0.md):
`resolvePreferences` LANZA con `uix` + escalar/source (forma firmada
originalmente); medido despues, `ActiveEidos.create()` inyecta `uix`
SIEMPRE y todas las demos pasan escalares o sources (>=12 sitios,
incluido web/routes/uix/+layout@.svelte:511) → el sitio de demos lanza al
arrancar hasta la forma (c'): pines si (escalar explicito gana, precedente
`options.dom ?? options.uix?.dom`), sources reactivas por eje FUERA, sin
throw, pines en el boot. Pendientes anotados: contracts.test.ts:1270-1276
codifica la doctrina revocada; esbuild no es dependencia declarada
(transitiva de vite) — decision del autor con servidores parados.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
npm run generate:boot
|
|
|
|
|
|
```
|
|
|
|
|
|
|
feat(active-uix)!: el boot viaja como CUERPO CONSTANTE con los parámetros en un atributo — un hash de CSP fijo, sin node:fs, y la delta deja de confirmarse a sí misma (fila 1)
La receta documentada del boot pre-hidratación no funcionaba en ningún despliegue
real, y el panel de diseño de nueve agentes que el autor pidió lo confirmó
midiendo: con el `render.ts` anterior, un build REAL de SvelteKit con
adapter-static revienta el prerender con `ENOENT …/output/server/generated/boot.js`
y devuelve un 500. La causa: `renderUixBootScript` leía el bundle DEL DISCO junto
a su propio módulo, y una vez empaquetado ese fichero no está ahí.
FORMA NUEVA. El generador compila una entrada propia (`boot/entry.ts`), sin
`globalName`, y emite el MISMO fichero de siempre (`generated/boot.js`, sin
borrar nada) como módulo con dos constantes: el texto exacto del script y su
`sha256-…`. `renderUixBootScript` pierde `node:fs`, `node:path`, `node:url`, la
caché y el envoltorio que estaba ESCRITO A MANO: al no haber `globalName`, la
llamada la hace la propia entrada compilada. «Compilado, no escrito» sale
reforzado, no rebajado.
LOS PARÁMETROS VIAJAN EN UN ATRIBUTO del mismo `<script>`, no dentro del cuerpo.
Consecuencia, que es el punto entero de la fila: el cuerpo es CONSTANTE, así que
su hash es constante por versión del framework, igual para todos los sitios y
todos los `themeIds`. La guía pasa a tener UNA sola receta, por hash, que cubre
prerender, SSR y hasta `mode: 'nonce'`; el nonce baja a salida de emergencia. Un
bloque `application/json` hermano se descartó con razón medida: rompería la
invariante de que el tag parsea a exactamente un `<script>` y un minificador de
HTML podría borrarlo sin que nadie se entere.
TRES MENTIRAS DE LA GUÍA, CORREGIDAS. `event.locals.nonce` no existe en Kit
2.55.0 (cero ocurrencias en su código; `respond.js:168` es `locals: {}`), así que
la receta anterior pasaba `undefined`, el navegador bloqueaba el script y volvía
el flash sin un solo error. El placeholder estaba ENCIMA de `%sveltekit.head%`,
donde la política de prerender de Kit no lo alcanza: eso es evadir la CSP, no
cumplirla, y no vale cuando la política llega por cabecera. Y el `html.replace`
con cadena interpreta `$'`, así que ahora la receta usa reemplazo FUNCIÓN. Se
añade el aviso que puede tumbarle la página al consumidor: añadir un hash a un
`script-src` que lleva `'unsafe-inline'` hace que el navegador IGNORE
`unsafe-inline` y bloquee los demás scripts inline del sitio, incluido el
arranque de Kit.
EL CRITERIO SE CORRIGE. `boot-delta.test.ts` gana un tercer snapshot contra un
documento LIMPIO. El anterior arrancaba el runtime sobre el documento ya sellado,
y como `directionDimension.derive` devuelve `env.direction` antes de derivar del
idioma y el entorno se siembra leyendo el `<html dir>` que el boot acaba de
escribir, la columna `dir` se confirmaba a sí misma. Medido con el boot clavado a
`rtl`: los cinco rojos caen todos en la lectura nueva y ninguno en la anterior.
AUDIT DEV. La raíz compara en desarrollo su primera proyección con lo que ya hay
en `<html>` y NOMBRA los ejes que discrepan. Convierte en ruidosos cinco fallos
que hoy son idénticos y mudos: pin olvidado, `themeIds` incompleto, `storageKey`
distinto, artefacto rancio y script bloqueado por la CSP.
Artefacto 14 666 B, `sha256-5fulJ/2Q6DttR258KqIhWSwfm5g7973BEzzzq14fapY=`,
generación determinista. Verificación: suite entera 461 ficheros / 5404 tests, exit 0 · check con
0 errores bajo `src/` y bajo `scripts/` (89 en `web/`, ledger intacto) ·
docs:check 0/0/819 · `npx vitest run src/uix/active-uix` 9 ficheros / 97 tests.
El test nuevo `boot/pack.test.ts` empaqueta `render.ts` y lo ejecuta: sale ROJO
contra el código anterior con el ENOENT literal, y es lo único que impide que
esta clase vuelva.
Adversarial Opus independiente (árbol byte-idéntico al terminar, 14 mutaciones
revertidas): midió el hash bajo CSP real contra un servidor HTTP en Chromium 145,
Firefox 146 y WebKit 26, con control negativo bloqueado; montó una app SvelteKit
real siguiendo la guía al pie de la letra y la vio construir, prerenderizar y
arrancar sin violaciones; y probó el ENOENT con un build real. Declaró 5 defectos
del lote, ninguno bloqueante, cerrados en una segunda ronda y re-verificados uno
a uno con su reproducción original: un universal falso en un comentario, la
puerta del artefacto sin validar el cuerpo (ahora con guard propio importable),
la constante del hash sin comillas para quien escriba su propia cabecera, el
audit que SÍ viaja al bundle de producción (1 428 B medidos, no un supuesto; no
CORRE, gracias a un ternario que el bundler no pliega) y el eje que se nombraba a
profundidad 2 y ahora va en el mensaje. La redacción del guard del artefacto,
último defecto de la segunda pasada, la corregí yo: corre en cada render, 76 us
con artefacto frente a 1,49 us sin él, medido.
Filas que deja para el autor: los 1 428 B del audit en producción, si se gatean
con una bandera que el bundler pliegue · el audit queda mudo si el runtime se
desecha en el mismo turno · la fila 2 debe decidir si la puerta verifica que el
hash describa al cuerpo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
Writes `src/uix/active-uix/generated/boot.js`, checked in. It is not a bundle
|
|
|
|
|
|
you have to place: it is two constants — `UIX_BOOT_SCRIPT`, the exact text that
|
|
|
|
|
|
goes inside the `<script>` tag, and `UIX_BOOT_CSP_HASH`, the `script-src` hash
|
|
|
|
|
|
of that text. A vitest keeps both in sync with a live compile, so a stale
|
|
|
|
|
|
artifact fails the gate rather than shipping.
|
|
|
|
|
|
|
|
|
|
|
|
The body never carries your parameters — they ride a `data-uix-boot` attribute
|
|
|
|
|
|
of the same tag — so the hash is constant per framework version. That is what
|
|
|
|
|
|
makes step 4 possible.
|
feat(prefs)!: un motor, un sobre, un codigo — mode/theme/density/scaling son prefs, sobre canonico y boot compilado
Auditoria P2 #2: los attrs de preferencias se estampaban solo en cliente
tras hidratar (app.html sin script, build estatico sin servidor) — flash
claro para todo usuario dark — y no podia existir un script de arranque
porque no habia contrato de persistencia (PrefsIntentStorage lo aporta
cada app) y eidos llevaba un SEGUNDO motor de preferencias (theme /
modeSource / densitySource / scalingSource por opciones, sin persistir),
por doctrina escrita en arts/prefs/README.md («UIX does not turn it into
prefs.theme»). El autor REVOCA esa doctrina.
UN MOTOR. `mode` es una dimension de prefs junto a `motion` (mismo patron,
`resolveTheme` de $libs/theme, que ya existia como gemelo de
`resolveMotion`); `theme` (id de familia), `density` y `scaling` las
declara la RAIZ en un modulo PURO (src/uix/active-uix/prefs-schema.ts) —
arts/prefs no importa src/uix. Eidos lee `uix.prefs` por su puerto
`ActiveEidosPreferenceSource` (src/uix/eidos/prefs-source.ts), como ya
consume uix.langs / uix.dom / uix.motion. El resolver de tema baja a una
funcion pura `resolveThemeId(theme, mode, isRegistered)`
(src/uix/eidos/lib/theme-id.ts); los cuatro nombres de attr de eidos a
src/uix/eidos/lib/attrs.ts y los de la proyeccion a
src/arts/prefs/dom-attrs.ts: modulos sin Svelte, porque el boot los
comparte.
UN SOBRE. src/libs/prefs/document.ts: `uix.prefs-intent` v1 bajo la clave
`uix.prefs`, espejo del documento de eidos (kind + version estrictos,
version desconocida se RECHAZA). Solo intencion — nunca efectivo ni
entorno. Adaptador localStorage canonico creado en la raiz standalone
(`prefs.storage`: propio gana, `false` desactiva). HIDRATACION SINCRONA:
el bridge awaitea `load()` aunque sea sincrono y produciria una primera
resolucion con defaults y un salto un tick despues — el flash movido de
sitio; la raiz lee el sobre ANTES de crear el motor y lo pasa como
`intent`; el bridge queda solo para persistir (skipHydrate).
UN CODIGO. El boot no se escribe: se COMPILA con esbuild desde los mismos
modulos puros (src/uix/active-uix/boot/boot.ts → generated/boot.js,
15.009 bytes / 5.625 gzip, IIFE sin globales, cero Svelte; test de
sincronia, precedente generated/base.css). `renderUixBootScript(params)`
(boot/render.ts) es Kit-agnostico: el framework entrega el string; el
hook de Kit son tres lineas de la app (receta en docs/theming/guide.md,
NO cableada: el layout congelado lleva persistencia propia). Estampa los
NUEVE attrs (dir lang data-motion data-sound data-haptic data-theme
data-mode data-density data-scaling) → DELTA CERO al hidratar, probado
por integracion con la raiz REAL (boot/boot-delta.test.ts, tres casos)
y por tres mutaciones (sin hidratacion sincrona → rojo; constante de
attr cambiada en un solo lado → rojo; boot compilado con `data-modo` →
los tres casos rojos).
Hallazgo del test de delta cero: la raiz no aplicaba el entorno del
navegador (esperaba que el app llamara detectBrowserEnvironment en
onMount) — ahora lo siembra ella (guardado por `document`) y observa sus
cambios (watchBrowserEnvironment → patchEnvironment), sustituyendo el
listener que eidos tenia en createSystemColorSchemeSource.
Cifras: vitest prefs+libs/prefs+active-uix+eidos+value-channels 57/593 ·
suite completa 453/5306 · check 73 (src/ a cero; ledger intacto) ·
check:gate OK · eidos:lint 0 · docs:check 0/0 · morfo:vocabulary,
arts/blocks/packs/rtl/translations/agent:check OK.
ESTADO INTERINO, firmado para manana (acta en CONTINUE-audit-p0.md):
`resolvePreferences` LANZA con `uix` + escalar/source (forma firmada
originalmente); medido despues, `ActiveEidos.create()` inyecta `uix`
SIEMPRE y todas las demos pasan escalares o sources (>=12 sitios,
incluido web/routes/uix/+layout@.svelte:511) → el sitio de demos lanza al
arrancar hasta la forma (c'): pines si (escalar explicito gana, precedente
`options.dom ?? options.uix?.dom`), sources reactivas por eje FUERA, sin
throw, pines en el boot. Pendientes anotados: contracts.test.ts:1270-1276
codifica la doctrina revocada; esbuild no es dependencia declarada
(transitiva de vite) — decision del autor con servidores parados.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
|
|
|
|
|
|
### Step 2 — A placeholder in `src/app.html`
|
|
|
|
|
|
|
|
|
|
|
|
```html
|
|
|
|
|
|
<head>
|
|
|
|
|
|
%sveltekit.head% %uix.boot%
|
feat(prefs)!: un motor, un sobre, un codigo — mode/theme/density/scaling son prefs, sobre canonico y boot compilado
Auditoria P2 #2: los attrs de preferencias se estampaban solo en cliente
tras hidratar (app.html sin script, build estatico sin servidor) — flash
claro para todo usuario dark — y no podia existir un script de arranque
porque no habia contrato de persistencia (PrefsIntentStorage lo aporta
cada app) y eidos llevaba un SEGUNDO motor de preferencias (theme /
modeSource / densitySource / scalingSource por opciones, sin persistir),
por doctrina escrita en arts/prefs/README.md («UIX does not turn it into
prefs.theme»). El autor REVOCA esa doctrina.
UN MOTOR. `mode` es una dimension de prefs junto a `motion` (mismo patron,
`resolveTheme` de $libs/theme, que ya existia como gemelo de
`resolveMotion`); `theme` (id de familia), `density` y `scaling` las
declara la RAIZ en un modulo PURO (src/uix/active-uix/prefs-schema.ts) —
arts/prefs no importa src/uix. Eidos lee `uix.prefs` por su puerto
`ActiveEidosPreferenceSource` (src/uix/eidos/prefs-source.ts), como ya
consume uix.langs / uix.dom / uix.motion. El resolver de tema baja a una
funcion pura `resolveThemeId(theme, mode, isRegistered)`
(src/uix/eidos/lib/theme-id.ts); los cuatro nombres de attr de eidos a
src/uix/eidos/lib/attrs.ts y los de la proyeccion a
src/arts/prefs/dom-attrs.ts: modulos sin Svelte, porque el boot los
comparte.
UN SOBRE. src/libs/prefs/document.ts: `uix.prefs-intent` v1 bajo la clave
`uix.prefs`, espejo del documento de eidos (kind + version estrictos,
version desconocida se RECHAZA). Solo intencion — nunca efectivo ni
entorno. Adaptador localStorage canonico creado en la raiz standalone
(`prefs.storage`: propio gana, `false` desactiva). HIDRATACION SINCRONA:
el bridge awaitea `load()` aunque sea sincrono y produciria una primera
resolucion con defaults y un salto un tick despues — el flash movido de
sitio; la raiz lee el sobre ANTES de crear el motor y lo pasa como
`intent`; el bridge queda solo para persistir (skipHydrate).
UN CODIGO. El boot no se escribe: se COMPILA con esbuild desde los mismos
modulos puros (src/uix/active-uix/boot/boot.ts → generated/boot.js,
15.009 bytes / 5.625 gzip, IIFE sin globales, cero Svelte; test de
sincronia, precedente generated/base.css). `renderUixBootScript(params)`
(boot/render.ts) es Kit-agnostico: el framework entrega el string; el
hook de Kit son tres lineas de la app (receta en docs/theming/guide.md,
NO cableada: el layout congelado lleva persistencia propia). Estampa los
NUEVE attrs (dir lang data-motion data-sound data-haptic data-theme
data-mode data-density data-scaling) → DELTA CERO al hidratar, probado
por integracion con la raiz REAL (boot/boot-delta.test.ts, tres casos)
y por tres mutaciones (sin hidratacion sincrona → rojo; constante de
attr cambiada en un solo lado → rojo; boot compilado con `data-modo` →
los tres casos rojos).
Hallazgo del test de delta cero: la raiz no aplicaba el entorno del
navegador (esperaba que el app llamara detectBrowserEnvironment en
onMount) — ahora lo siembra ella (guardado por `document`) y observa sus
cambios (watchBrowserEnvironment → patchEnvironment), sustituyendo el
listener que eidos tenia en createSystemColorSchemeSource.
Cifras: vitest prefs+libs/prefs+active-uix+eidos+value-channels 57/593 ·
suite completa 453/5306 · check 73 (src/ a cero; ledger intacto) ·
check:gate OK · eidos:lint 0 · docs:check 0/0 · morfo:vocabulary,
arts/blocks/packs/rtl/translations/agent:check OK.
ESTADO INTERINO, firmado para manana (acta en CONTINUE-audit-p0.md):
`resolvePreferences` LANZA con `uix` + escalar/source (forma firmada
originalmente); medido despues, `ActiveEidos.create()` inyecta `uix`
SIEMPRE y todas las demos pasan escalares o sources (>=12 sitios,
incluido web/routes/uix/+layout@.svelte:511) → el sitio de demos lanza al
arrancar hasta la forma (c'): pines si (escalar explicito gana, precedente
`options.dom ?? options.uix?.dom`), sources reactivas por eje FUERA, sin
throw, pines en el boot. Pendientes anotados: contracts.test.ts:1270-1276
codifica la doctrina revocada; esbuild no es dependencia declarada
(transitiva de vite) — decision del autor con servidores parados.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
</head>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
feat(active-uix)!: el boot viaja como CUERPO CONSTANTE con los parámetros en un atributo — un hash de CSP fijo, sin node:fs, y la delta deja de confirmarse a sí misma (fila 1)
La receta documentada del boot pre-hidratación no funcionaba en ningún despliegue
real, y el panel de diseño de nueve agentes que el autor pidió lo confirmó
midiendo: con el `render.ts` anterior, un build REAL de SvelteKit con
adapter-static revienta el prerender con `ENOENT …/output/server/generated/boot.js`
y devuelve un 500. La causa: `renderUixBootScript` leía el bundle DEL DISCO junto
a su propio módulo, y una vez empaquetado ese fichero no está ahí.
FORMA NUEVA. El generador compila una entrada propia (`boot/entry.ts`), sin
`globalName`, y emite el MISMO fichero de siempre (`generated/boot.js`, sin
borrar nada) como módulo con dos constantes: el texto exacto del script y su
`sha256-…`. `renderUixBootScript` pierde `node:fs`, `node:path`, `node:url`, la
caché y el envoltorio que estaba ESCRITO A MANO: al no haber `globalName`, la
llamada la hace la propia entrada compilada. «Compilado, no escrito» sale
reforzado, no rebajado.
LOS PARÁMETROS VIAJAN EN UN ATRIBUTO del mismo `<script>`, no dentro del cuerpo.
Consecuencia, que es el punto entero de la fila: el cuerpo es CONSTANTE, así que
su hash es constante por versión del framework, igual para todos los sitios y
todos los `themeIds`. La guía pasa a tener UNA sola receta, por hash, que cubre
prerender, SSR y hasta `mode: 'nonce'`; el nonce baja a salida de emergencia. Un
bloque `application/json` hermano se descartó con razón medida: rompería la
invariante de que el tag parsea a exactamente un `<script>` y un minificador de
HTML podría borrarlo sin que nadie se entere.
TRES MENTIRAS DE LA GUÍA, CORREGIDAS. `event.locals.nonce` no existe en Kit
2.55.0 (cero ocurrencias en su código; `respond.js:168` es `locals: {}`), así que
la receta anterior pasaba `undefined`, el navegador bloqueaba el script y volvía
el flash sin un solo error. El placeholder estaba ENCIMA de `%sveltekit.head%`,
donde la política de prerender de Kit no lo alcanza: eso es evadir la CSP, no
cumplirla, y no vale cuando la política llega por cabecera. Y el `html.replace`
con cadena interpreta `$'`, así que ahora la receta usa reemplazo FUNCIÓN. Se
añade el aviso que puede tumbarle la página al consumidor: añadir un hash a un
`script-src` que lleva `'unsafe-inline'` hace que el navegador IGNORE
`unsafe-inline` y bloquee los demás scripts inline del sitio, incluido el
arranque de Kit.
EL CRITERIO SE CORRIGE. `boot-delta.test.ts` gana un tercer snapshot contra un
documento LIMPIO. El anterior arrancaba el runtime sobre el documento ya sellado,
y como `directionDimension.derive` devuelve `env.direction` antes de derivar del
idioma y el entorno se siembra leyendo el `<html dir>` que el boot acaba de
escribir, la columna `dir` se confirmaba a sí misma. Medido con el boot clavado a
`rtl`: los cinco rojos caen todos en la lectura nueva y ninguno en la anterior.
AUDIT DEV. La raíz compara en desarrollo su primera proyección con lo que ya hay
en `<html>` y NOMBRA los ejes que discrepan. Convierte en ruidosos cinco fallos
que hoy son idénticos y mudos: pin olvidado, `themeIds` incompleto, `storageKey`
distinto, artefacto rancio y script bloqueado por la CSP.
Artefacto 14 666 B, `sha256-5fulJ/2Q6DttR258KqIhWSwfm5g7973BEzzzq14fapY=`,
generación determinista. Verificación: suite entera 461 ficheros / 5404 tests, exit 0 · check con
0 errores bajo `src/` y bajo `scripts/` (89 en `web/`, ledger intacto) ·
docs:check 0/0/819 · `npx vitest run src/uix/active-uix` 9 ficheros / 97 tests.
El test nuevo `boot/pack.test.ts` empaqueta `render.ts` y lo ejecuta: sale ROJO
contra el código anterior con el ENOENT literal, y es lo único que impide que
esta clase vuelva.
Adversarial Opus independiente (árbol byte-idéntico al terminar, 14 mutaciones
revertidas): midió el hash bajo CSP real contra un servidor HTTP en Chromium 145,
Firefox 146 y WebKit 26, con control negativo bloqueado; montó una app SvelteKit
real siguiendo la guía al pie de la letra y la vio construir, prerenderizar y
arrancar sin violaciones; y probó el ENOENT con un build real. Declaró 5 defectos
del lote, ninguno bloqueante, cerrados en una segunda ronda y re-verificados uno
a uno con su reproducción original: un universal falso en un comentario, la
puerta del artefacto sin validar el cuerpo (ahora con guard propio importable),
la constante del hash sin comillas para quien escriba su propia cabecera, el
audit que SÍ viaja al bundle de producción (1 428 B medidos, no un supuesto; no
CORRE, gracias a un ternario que el bundler no pliega) y el eje que se nombraba a
profundidad 2 y ahora va en el mensaje. La redacción del guard del artefacto,
último defecto de la segunda pasada, la corregí yo: corre en cada render, 76 us
con artefacto frente a 1,49 us sin él, medido.
Filas que deja para el autor: los 1 428 B del audit en producción, si se gatean
con una bandera que el bundler pliegue · el audit queda mudo si el runtime se
desecha en el mismo turno · la fila 2 debe decidir si la puerta verifica que el
hash describa al cuerpo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
**Below `%sveltekit.head%`**, and the order is load-bearing. On a prerendered
|
|
|
|
|
|
site Kit publishes the policy as a `<meta http-equiv="content-security-policy">`
|
|
|
|
|
|
inside `%sveltekit.head%`, and a `<meta>` policy governs only what comes after
|
|
|
|
|
|
it. A boot placed above it is outside the policy — which is evading the CSP,
|
|
|
|
|
|
not satisfying it — and the trick buys nothing the moment the same policy
|
|
|
|
|
|
arrives as a header.
|
|
|
|
|
|
|
feat(prefs)!: un motor, un sobre, un codigo — mode/theme/density/scaling son prefs, sobre canonico y boot compilado
Auditoria P2 #2: los attrs de preferencias se estampaban solo en cliente
tras hidratar (app.html sin script, build estatico sin servidor) — flash
claro para todo usuario dark — y no podia existir un script de arranque
porque no habia contrato de persistencia (PrefsIntentStorage lo aporta
cada app) y eidos llevaba un SEGUNDO motor de preferencias (theme /
modeSource / densitySource / scalingSource por opciones, sin persistir),
por doctrina escrita en arts/prefs/README.md («UIX does not turn it into
prefs.theme»). El autor REVOCA esa doctrina.
UN MOTOR. `mode` es una dimension de prefs junto a `motion` (mismo patron,
`resolveTheme` de $libs/theme, que ya existia como gemelo de
`resolveMotion`); `theme` (id de familia), `density` y `scaling` las
declara la RAIZ en un modulo PURO (src/uix/active-uix/prefs-schema.ts) —
arts/prefs no importa src/uix. Eidos lee `uix.prefs` por su puerto
`ActiveEidosPreferenceSource` (src/uix/eidos/prefs-source.ts), como ya
consume uix.langs / uix.dom / uix.motion. El resolver de tema baja a una
funcion pura `resolveThemeId(theme, mode, isRegistered)`
(src/uix/eidos/lib/theme-id.ts); los cuatro nombres de attr de eidos a
src/uix/eidos/lib/attrs.ts y los de la proyeccion a
src/arts/prefs/dom-attrs.ts: modulos sin Svelte, porque el boot los
comparte.
UN SOBRE. src/libs/prefs/document.ts: `uix.prefs-intent` v1 bajo la clave
`uix.prefs`, espejo del documento de eidos (kind + version estrictos,
version desconocida se RECHAZA). Solo intencion — nunca efectivo ni
entorno. Adaptador localStorage canonico creado en la raiz standalone
(`prefs.storage`: propio gana, `false` desactiva). HIDRATACION SINCRONA:
el bridge awaitea `load()` aunque sea sincrono y produciria una primera
resolucion con defaults y un salto un tick despues — el flash movido de
sitio; la raiz lee el sobre ANTES de crear el motor y lo pasa como
`intent`; el bridge queda solo para persistir (skipHydrate).
UN CODIGO. El boot no se escribe: se COMPILA con esbuild desde los mismos
modulos puros (src/uix/active-uix/boot/boot.ts → generated/boot.js,
15.009 bytes / 5.625 gzip, IIFE sin globales, cero Svelte; test de
sincronia, precedente generated/base.css). `renderUixBootScript(params)`
(boot/render.ts) es Kit-agnostico: el framework entrega el string; el
hook de Kit son tres lineas de la app (receta en docs/theming/guide.md,
NO cableada: el layout congelado lleva persistencia propia). Estampa los
NUEVE attrs (dir lang data-motion data-sound data-haptic data-theme
data-mode data-density data-scaling) → DELTA CERO al hidratar, probado
por integracion con la raiz REAL (boot/boot-delta.test.ts, tres casos)
y por tres mutaciones (sin hidratacion sincrona → rojo; constante de
attr cambiada en un solo lado → rojo; boot compilado con `data-modo` →
los tres casos rojos).
Hallazgo del test de delta cero: la raiz no aplicaba el entorno del
navegador (esperaba que el app llamara detectBrowserEnvironment en
onMount) — ahora lo siembra ella (guardado por `document`) y observa sus
cambios (watchBrowserEnvironment → patchEnvironment), sustituyendo el
listener que eidos tenia en createSystemColorSchemeSource.
Cifras: vitest prefs+libs/prefs+active-uix+eidos+value-channels 57/593 ·
suite completa 453/5306 · check 73 (src/ a cero; ledger intacto) ·
check:gate OK · eidos:lint 0 · docs:check 0/0 · morfo:vocabulary,
arts/blocks/packs/rtl/translations/agent:check OK.
ESTADO INTERINO, firmado para manana (acta en CONTINUE-audit-p0.md):
`resolvePreferences` LANZA con `uix` + escalar/source (forma firmada
originalmente); medido despues, `ActiveEidos.create()` inyecta `uix`
SIEMPRE y todas las demos pasan escalares o sources (>=12 sitios,
incluido web/routes/uix/+layout@.svelte:511) → el sitio de demos lanza al
arrancar hasta la forma (c'): pines si (escalar explicito gana, precedente
`options.dom ?? options.uix?.dom`), sources reactivas por eje FUERA, sin
throw, pines en el boot. Pendientes anotados: contracts.test.ts:1270-1276
codifica la doctrina revocada; esbuild no es dependencia declarada
(transitiva de vite) — decision del autor con servidores parados.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
### Step 3 — Three lines in `src/hooks.server.ts`
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
import { renderUixBootScript } from '$active-uix/boot/render';
|
docs: el repositorio, el formato y los guards de texto entran en el corpus; cuatro afirmaciones caducas mueren (documentación del cierre)
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-<n>`) 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 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
import { THEME_IDS } from './lib/eidos-config';
|
feat(prefs)!: un motor, un sobre, un codigo — mode/theme/density/scaling son prefs, sobre canonico y boot compilado
Auditoria P2 #2: los attrs de preferencias se estampaban solo en cliente
tras hidratar (app.html sin script, build estatico sin servidor) — flash
claro para todo usuario dark — y no podia existir un script de arranque
porque no habia contrato de persistencia (PrefsIntentStorage lo aporta
cada app) y eidos llevaba un SEGUNDO motor de preferencias (theme /
modeSource / densitySource / scalingSource por opciones, sin persistir),
por doctrina escrita en arts/prefs/README.md («UIX does not turn it into
prefs.theme»). El autor REVOCA esa doctrina.
UN MOTOR. `mode` es una dimension de prefs junto a `motion` (mismo patron,
`resolveTheme` de $libs/theme, que ya existia como gemelo de
`resolveMotion`); `theme` (id de familia), `density` y `scaling` las
declara la RAIZ en un modulo PURO (src/uix/active-uix/prefs-schema.ts) —
arts/prefs no importa src/uix. Eidos lee `uix.prefs` por su puerto
`ActiveEidosPreferenceSource` (src/uix/eidos/prefs-source.ts), como ya
consume uix.langs / uix.dom / uix.motion. El resolver de tema baja a una
funcion pura `resolveThemeId(theme, mode, isRegistered)`
(src/uix/eidos/lib/theme-id.ts); los cuatro nombres de attr de eidos a
src/uix/eidos/lib/attrs.ts y los de la proyeccion a
src/arts/prefs/dom-attrs.ts: modulos sin Svelte, porque el boot los
comparte.
UN SOBRE. src/libs/prefs/document.ts: `uix.prefs-intent` v1 bajo la clave
`uix.prefs`, espejo del documento de eidos (kind + version estrictos,
version desconocida se RECHAZA). Solo intencion — nunca efectivo ni
entorno. Adaptador localStorage canonico creado en la raiz standalone
(`prefs.storage`: propio gana, `false` desactiva). HIDRATACION SINCRONA:
el bridge awaitea `load()` aunque sea sincrono y produciria una primera
resolucion con defaults y un salto un tick despues — el flash movido de
sitio; la raiz lee el sobre ANTES de crear el motor y lo pasa como
`intent`; el bridge queda solo para persistir (skipHydrate).
UN CODIGO. El boot no se escribe: se COMPILA con esbuild desde los mismos
modulos puros (src/uix/active-uix/boot/boot.ts → generated/boot.js,
15.009 bytes / 5.625 gzip, IIFE sin globales, cero Svelte; test de
sincronia, precedente generated/base.css). `renderUixBootScript(params)`
(boot/render.ts) es Kit-agnostico: el framework entrega el string; el
hook de Kit son tres lineas de la app (receta en docs/theming/guide.md,
NO cableada: el layout congelado lleva persistencia propia). Estampa los
NUEVE attrs (dir lang data-motion data-sound data-haptic data-theme
data-mode data-density data-scaling) → DELTA CERO al hidratar, probado
por integracion con la raiz REAL (boot/boot-delta.test.ts, tres casos)
y por tres mutaciones (sin hidratacion sincrona → rojo; constante de
attr cambiada en un solo lado → rojo; boot compilado con `data-modo` →
los tres casos rojos).
Hallazgo del test de delta cero: la raiz no aplicaba el entorno del
navegador (esperaba que el app llamara detectBrowserEnvironment en
onMount) — ahora lo siembra ella (guardado por `document`) y observa sus
cambios (watchBrowserEnvironment → patchEnvironment), sustituyendo el
listener que eidos tenia en createSystemColorSchemeSource.
Cifras: vitest prefs+libs/prefs+active-uix+eidos+value-channels 57/593 ·
suite completa 453/5306 · check 73 (src/ a cero; ledger intacto) ·
check:gate OK · eidos:lint 0 · docs:check 0/0 · morfo:vocabulary,
arts/blocks/packs/rtl/translations/agent:check OK.
ESTADO INTERINO, firmado para manana (acta en CONTINUE-audit-p0.md):
`resolvePreferences` LANZA con `uix` + escalar/source (forma firmada
originalmente); medido despues, `ActiveEidos.create()` inyecta `uix`
SIEMPRE y todas las demos pasan escalares o sources (>=12 sitios,
incluido web/routes/uix/+layout@.svelte:511) → el sitio de demos lanza al
arrancar hasta la forma (c'): pines si (escalar explicito gana, precedente
`options.dom ?? options.uix?.dom`), sources reactivas por eje FUERA, sin
throw, pines en el boot. Pendientes anotados: contracts.test.ts:1270-1276
codifica la doctrina revocada; esbuild no es dependencia declarada
(transitiva de vite) — decision del autor con servidores parados.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
|
|
|
|
|
|
export const handle = ({ event, resolve }) =>
|
|
|
|
|
|
resolve(event, {
|
|
|
|
|
|
transformPageChunk: ({ html }) =>
|
feat(active-uix)!: el boot viaja como CUERPO CONSTANTE con los parámetros en un atributo — un hash de CSP fijo, sin node:fs, y la delta deja de confirmarse a sí misma (fila 1)
La receta documentada del boot pre-hidratación no funcionaba en ningún despliegue
real, y el panel de diseño de nueve agentes que el autor pidió lo confirmó
midiendo: con el `render.ts` anterior, un build REAL de SvelteKit con
adapter-static revienta el prerender con `ENOENT …/output/server/generated/boot.js`
y devuelve un 500. La causa: `renderUixBootScript` leía el bundle DEL DISCO junto
a su propio módulo, y una vez empaquetado ese fichero no está ahí.
FORMA NUEVA. El generador compila una entrada propia (`boot/entry.ts`), sin
`globalName`, y emite el MISMO fichero de siempre (`generated/boot.js`, sin
borrar nada) como módulo con dos constantes: el texto exacto del script y su
`sha256-…`. `renderUixBootScript` pierde `node:fs`, `node:path`, `node:url`, la
caché y el envoltorio que estaba ESCRITO A MANO: al no haber `globalName`, la
llamada la hace la propia entrada compilada. «Compilado, no escrito» sale
reforzado, no rebajado.
LOS PARÁMETROS VIAJAN EN UN ATRIBUTO del mismo `<script>`, no dentro del cuerpo.
Consecuencia, que es el punto entero de la fila: el cuerpo es CONSTANTE, así que
su hash es constante por versión del framework, igual para todos los sitios y
todos los `themeIds`. La guía pasa a tener UNA sola receta, por hash, que cubre
prerender, SSR y hasta `mode: 'nonce'`; el nonce baja a salida de emergencia. Un
bloque `application/json` hermano se descartó con razón medida: rompería la
invariante de que el tag parsea a exactamente un `<script>` y un minificador de
HTML podría borrarlo sin que nadie se entere.
TRES MENTIRAS DE LA GUÍA, CORREGIDAS. `event.locals.nonce` no existe en Kit
2.55.0 (cero ocurrencias en su código; `respond.js:168` es `locals: {}`), así que
la receta anterior pasaba `undefined`, el navegador bloqueaba el script y volvía
el flash sin un solo error. El placeholder estaba ENCIMA de `%sveltekit.head%`,
donde la política de prerender de Kit no lo alcanza: eso es evadir la CSP, no
cumplirla, y no vale cuando la política llega por cabecera. Y el `html.replace`
con cadena interpreta `$'`, así que ahora la receta usa reemplazo FUNCIÓN. Se
añade el aviso que puede tumbarle la página al consumidor: añadir un hash a un
`script-src` que lleva `'unsafe-inline'` hace que el navegador IGNORE
`unsafe-inline` y bloquee los demás scripts inline del sitio, incluido el
arranque de Kit.
EL CRITERIO SE CORRIGE. `boot-delta.test.ts` gana un tercer snapshot contra un
documento LIMPIO. El anterior arrancaba el runtime sobre el documento ya sellado,
y como `directionDimension.derive` devuelve `env.direction` antes de derivar del
idioma y el entorno se siembra leyendo el `<html dir>` que el boot acaba de
escribir, la columna `dir` se confirmaba a sí misma. Medido con el boot clavado a
`rtl`: los cinco rojos caen todos en la lectura nueva y ninguno en la anterior.
AUDIT DEV. La raíz compara en desarrollo su primera proyección con lo que ya hay
en `<html>` y NOMBRA los ejes que discrepan. Convierte en ruidosos cinco fallos
que hoy son idénticos y mudos: pin olvidado, `themeIds` incompleto, `storageKey`
distinto, artefacto rancio y script bloqueado por la CSP.
Artefacto 14 666 B, `sha256-5fulJ/2Q6DttR258KqIhWSwfm5g7973BEzzzq14fapY=`,
generación determinista. Verificación: suite entera 461 ficheros / 5404 tests, exit 0 · check con
0 errores bajo `src/` y bajo `scripts/` (89 en `web/`, ledger intacto) ·
docs:check 0/0/819 · `npx vitest run src/uix/active-uix` 9 ficheros / 97 tests.
El test nuevo `boot/pack.test.ts` empaqueta `render.ts` y lo ejecuta: sale ROJO
contra el código anterior con el ENOENT literal, y es lo único que impide que
esta clase vuelva.
Adversarial Opus independiente (árbol byte-idéntico al terminar, 14 mutaciones
revertidas): midió el hash bajo CSP real contra un servidor HTTP en Chromium 145,
Firefox 146 y WebKit 26, con control negativo bloqueado; montó una app SvelteKit
real siguiendo la guía al pie de la letra y la vio construir, prerenderizar y
arrancar sin violaciones; y probó el ENOENT con un build real. Declaró 5 defectos
del lote, ninguno bloqueante, cerrados en una segunda ronda y re-verificados uno
a uno con su reproducción original: un universal falso en un comentario, la
puerta del artefacto sin validar el cuerpo (ahora con guard propio importable),
la constante del hash sin comillas para quien escriba su propia cabecera, el
audit que SÍ viaja al bundle de producción (1 428 B medidos, no un supuesto; no
CORRE, gracias a un ternario que el bundler no pliega) y el eje que se nombraba a
profundidad 2 y ahora va en el mensaje. La redacción del guard del artefacto,
último defecto de la segunda pasada, la corregí yo: corre en cada render, 76 us
con artefacto frente a 1,49 us sin él, medido.
Filas que deja para el autor: los 1 428 B del audit en producción, si se gatean
con una bandera que el bundler pliegue · el audit queda mudo si el runtime se
desecha en el mismo turno · la fila 2 debe decidir si la puerta verifica que el
hash describa al cuerpo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
html.replace('%uix.boot%', () =>
|
feat(prefs)!: un motor, un sobre, un codigo — mode/theme/density/scaling son prefs, sobre canonico y boot compilado
Auditoria P2 #2: los attrs de preferencias se estampaban solo en cliente
tras hidratar (app.html sin script, build estatico sin servidor) — flash
claro para todo usuario dark — y no podia existir un script de arranque
porque no habia contrato de persistencia (PrefsIntentStorage lo aporta
cada app) y eidos llevaba un SEGUNDO motor de preferencias (theme /
modeSource / densitySource / scalingSource por opciones, sin persistir),
por doctrina escrita en arts/prefs/README.md («UIX does not turn it into
prefs.theme»). El autor REVOCA esa doctrina.
UN MOTOR. `mode` es una dimension de prefs junto a `motion` (mismo patron,
`resolveTheme` de $libs/theme, que ya existia como gemelo de
`resolveMotion`); `theme` (id de familia), `density` y `scaling` las
declara la RAIZ en un modulo PURO (src/uix/active-uix/prefs-schema.ts) —
arts/prefs no importa src/uix. Eidos lee `uix.prefs` por su puerto
`ActiveEidosPreferenceSource` (src/uix/eidos/prefs-source.ts), como ya
consume uix.langs / uix.dom / uix.motion. El resolver de tema baja a una
funcion pura `resolveThemeId(theme, mode, isRegistered)`
(src/uix/eidos/lib/theme-id.ts); los cuatro nombres de attr de eidos a
src/uix/eidos/lib/attrs.ts y los de la proyeccion a
src/arts/prefs/dom-attrs.ts: modulos sin Svelte, porque el boot los
comparte.
UN SOBRE. src/libs/prefs/document.ts: `uix.prefs-intent` v1 bajo la clave
`uix.prefs`, espejo del documento de eidos (kind + version estrictos,
version desconocida se RECHAZA). Solo intencion — nunca efectivo ni
entorno. Adaptador localStorage canonico creado en la raiz standalone
(`prefs.storage`: propio gana, `false` desactiva). HIDRATACION SINCRONA:
el bridge awaitea `load()` aunque sea sincrono y produciria una primera
resolucion con defaults y un salto un tick despues — el flash movido de
sitio; la raiz lee el sobre ANTES de crear el motor y lo pasa como
`intent`; el bridge queda solo para persistir (skipHydrate).
UN CODIGO. El boot no se escribe: se COMPILA con esbuild desde los mismos
modulos puros (src/uix/active-uix/boot/boot.ts → generated/boot.js,
15.009 bytes / 5.625 gzip, IIFE sin globales, cero Svelte; test de
sincronia, precedente generated/base.css). `renderUixBootScript(params)`
(boot/render.ts) es Kit-agnostico: el framework entrega el string; el
hook de Kit son tres lineas de la app (receta en docs/theming/guide.md,
NO cableada: el layout congelado lleva persistencia propia). Estampa los
NUEVE attrs (dir lang data-motion data-sound data-haptic data-theme
data-mode data-density data-scaling) → DELTA CERO al hidratar, probado
por integracion con la raiz REAL (boot/boot-delta.test.ts, tres casos)
y por tres mutaciones (sin hidratacion sincrona → rojo; constante de
attr cambiada en un solo lado → rojo; boot compilado con `data-modo` →
los tres casos rojos).
Hallazgo del test de delta cero: la raiz no aplicaba el entorno del
navegador (esperaba que el app llamara detectBrowserEnvironment en
onMount) — ahora lo siembra ella (guardado por `document`) y observa sus
cambios (watchBrowserEnvironment → patchEnvironment), sustituyendo el
listener que eidos tenia en createSystemColorSchemeSource.
Cifras: vitest prefs+libs/prefs+active-uix+eidos+value-channels 57/593 ·
suite completa 453/5306 · check 73 (src/ a cero; ledger intacto) ·
check:gate OK · eidos:lint 0 · docs:check 0/0 · morfo:vocabulary,
arts/blocks/packs/rtl/translations/agent:check OK.
ESTADO INTERINO, firmado para manana (acta en CONTINUE-audit-p0.md):
`resolvePreferences` LANZA con `uix` + escalar/source (forma firmada
originalmente); medido despues, `ActiveEidos.create()` inyecta `uix`
SIEMPRE y todas las demos pasan escalares o sources (>=12 sitios,
incluido web/routes/uix/+layout@.svelte:511) → el sitio de demos lanza al
arrancar hasta la forma (c'): pines si (escalar explicito gana, precedente
`options.dom ?? options.uix?.dom`), sources reactivas por eje FUERA, sin
throw, pines en el boot. Pendientes anotados: contracts.test.ts:1270-1276
codifica la doctrina revocada; esbuild no es dependencia declarada
(transitiva de vite) — decision del autor con servidores parados.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
renderUixBootScript({
|
|
|
|
|
|
defaultLocale: 'es',
|
docs: el repositorio, el formato y los guards de texto entran en el corpus; cuatro afirmaciones caducas mueren (documentación del cierre)
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-<n>`) 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 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
themeIds: THEME_IDS
|
feat(prefs)!: un motor, un sobre, un codigo — mode/theme/density/scaling son prefs, sobre canonico y boot compilado
Auditoria P2 #2: los attrs de preferencias se estampaban solo en cliente
tras hidratar (app.html sin script, build estatico sin servidor) — flash
claro para todo usuario dark — y no podia existir un script de arranque
porque no habia contrato de persistencia (PrefsIntentStorage lo aporta
cada app) y eidos llevaba un SEGUNDO motor de preferencias (theme /
modeSource / densitySource / scalingSource por opciones, sin persistir),
por doctrina escrita en arts/prefs/README.md («UIX does not turn it into
prefs.theme»). El autor REVOCA esa doctrina.
UN MOTOR. `mode` es una dimension de prefs junto a `motion` (mismo patron,
`resolveTheme` de $libs/theme, que ya existia como gemelo de
`resolveMotion`); `theme` (id de familia), `density` y `scaling` las
declara la RAIZ en un modulo PURO (src/uix/active-uix/prefs-schema.ts) —
arts/prefs no importa src/uix. Eidos lee `uix.prefs` por su puerto
`ActiveEidosPreferenceSource` (src/uix/eidos/prefs-source.ts), como ya
consume uix.langs / uix.dom / uix.motion. El resolver de tema baja a una
funcion pura `resolveThemeId(theme, mode, isRegistered)`
(src/uix/eidos/lib/theme-id.ts); los cuatro nombres de attr de eidos a
src/uix/eidos/lib/attrs.ts y los de la proyeccion a
src/arts/prefs/dom-attrs.ts: modulos sin Svelte, porque el boot los
comparte.
UN SOBRE. src/libs/prefs/document.ts: `uix.prefs-intent` v1 bajo la clave
`uix.prefs`, espejo del documento de eidos (kind + version estrictos,
version desconocida se RECHAZA). Solo intencion — nunca efectivo ni
entorno. Adaptador localStorage canonico creado en la raiz standalone
(`prefs.storage`: propio gana, `false` desactiva). HIDRATACION SINCRONA:
el bridge awaitea `load()` aunque sea sincrono y produciria una primera
resolucion con defaults y un salto un tick despues — el flash movido de
sitio; la raiz lee el sobre ANTES de crear el motor y lo pasa como
`intent`; el bridge queda solo para persistir (skipHydrate).
UN CODIGO. El boot no se escribe: se COMPILA con esbuild desde los mismos
modulos puros (src/uix/active-uix/boot/boot.ts → generated/boot.js,
15.009 bytes / 5.625 gzip, IIFE sin globales, cero Svelte; test de
sincronia, precedente generated/base.css). `renderUixBootScript(params)`
(boot/render.ts) es Kit-agnostico: el framework entrega el string; el
hook de Kit son tres lineas de la app (receta en docs/theming/guide.md,
NO cableada: el layout congelado lleva persistencia propia). Estampa los
NUEVE attrs (dir lang data-motion data-sound data-haptic data-theme
data-mode data-density data-scaling) → DELTA CERO al hidratar, probado
por integracion con la raiz REAL (boot/boot-delta.test.ts, tres casos)
y por tres mutaciones (sin hidratacion sincrona → rojo; constante de
attr cambiada en un solo lado → rojo; boot compilado con `data-modo` →
los tres casos rojos).
Hallazgo del test de delta cero: la raiz no aplicaba el entorno del
navegador (esperaba que el app llamara detectBrowserEnvironment en
onMount) — ahora lo siembra ella (guardado por `document`) y observa sus
cambios (watchBrowserEnvironment → patchEnvironment), sustituyendo el
listener que eidos tenia en createSystemColorSchemeSource.
Cifras: vitest prefs+libs/prefs+active-uix+eidos+value-channels 57/593 ·
suite completa 453/5306 · check 73 (src/ a cero; ledger intacto) ·
check:gate OK · eidos:lint 0 · docs:check 0/0 · morfo:vocabulary,
arts/blocks/packs/rtl/translations/agent:check OK.
ESTADO INTERINO, firmado para manana (acta en CONTINUE-audit-p0.md):
`resolvePreferences` LANZA con `uix` + escalar/source (forma firmada
originalmente); medido despues, `ActiveEidos.create()` inyecta `uix`
SIEMPRE y todas las demos pasan escalares o sources (>=12 sitios,
incluido web/routes/uix/+layout@.svelte:511) → el sitio de demos lanza al
arrancar hasta la forma (c'): pines si (escalar explicito gana, precedente
`options.dom ?? options.uix?.dom`), sources reactivas por eje FUERA, sin
throw, pines en el boot. Pendientes anotados: contracts.test.ts:1270-1276
codifica la doctrina revocada; esbuild no es dependencia declarada
(transitiva de vite) — decision del autor con servidores parados.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
})
|
|
|
|
|
|
)
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`renderUixBootScript` returns a string and nothing else — the framework does
|
feat(active-uix)!: el boot viaja como CUERPO CONSTANTE con los parámetros en un atributo — un hash de CSP fijo, sin node:fs, y la delta deja de confirmarse a sí misma (fila 1)
La receta documentada del boot pre-hidratación no funcionaba en ningún despliegue
real, y el panel de diseño de nueve agentes que el autor pidió lo confirmó
midiendo: con el `render.ts` anterior, un build REAL de SvelteKit con
adapter-static revienta el prerender con `ENOENT …/output/server/generated/boot.js`
y devuelve un 500. La causa: `renderUixBootScript` leía el bundle DEL DISCO junto
a su propio módulo, y una vez empaquetado ese fichero no está ahí.
FORMA NUEVA. El generador compila una entrada propia (`boot/entry.ts`), sin
`globalName`, y emite el MISMO fichero de siempre (`generated/boot.js`, sin
borrar nada) como módulo con dos constantes: el texto exacto del script y su
`sha256-…`. `renderUixBootScript` pierde `node:fs`, `node:path`, `node:url`, la
caché y el envoltorio que estaba ESCRITO A MANO: al no haber `globalName`, la
llamada la hace la propia entrada compilada. «Compilado, no escrito» sale
reforzado, no rebajado.
LOS PARÁMETROS VIAJAN EN UN ATRIBUTO del mismo `<script>`, no dentro del cuerpo.
Consecuencia, que es el punto entero de la fila: el cuerpo es CONSTANTE, así que
su hash es constante por versión del framework, igual para todos los sitios y
todos los `themeIds`. La guía pasa a tener UNA sola receta, por hash, que cubre
prerender, SSR y hasta `mode: 'nonce'`; el nonce baja a salida de emergencia. Un
bloque `application/json` hermano se descartó con razón medida: rompería la
invariante de que el tag parsea a exactamente un `<script>` y un minificador de
HTML podría borrarlo sin que nadie se entere.
TRES MENTIRAS DE LA GUÍA, CORREGIDAS. `event.locals.nonce` no existe en Kit
2.55.0 (cero ocurrencias en su código; `respond.js:168` es `locals: {}`), así que
la receta anterior pasaba `undefined`, el navegador bloqueaba el script y volvía
el flash sin un solo error. El placeholder estaba ENCIMA de `%sveltekit.head%`,
donde la política de prerender de Kit no lo alcanza: eso es evadir la CSP, no
cumplirla, y no vale cuando la política llega por cabecera. Y el `html.replace`
con cadena interpreta `$'`, así que ahora la receta usa reemplazo FUNCIÓN. Se
añade el aviso que puede tumbarle la página al consumidor: añadir un hash a un
`script-src` que lleva `'unsafe-inline'` hace que el navegador IGNORE
`unsafe-inline` y bloquee los demás scripts inline del sitio, incluido el
arranque de Kit.
EL CRITERIO SE CORRIGE. `boot-delta.test.ts` gana un tercer snapshot contra un
documento LIMPIO. El anterior arrancaba el runtime sobre el documento ya sellado,
y como `directionDimension.derive` devuelve `env.direction` antes de derivar del
idioma y el entorno se siembra leyendo el `<html dir>` que el boot acaba de
escribir, la columna `dir` se confirmaba a sí misma. Medido con el boot clavado a
`rtl`: los cinco rojos caen todos en la lectura nueva y ninguno en la anterior.
AUDIT DEV. La raíz compara en desarrollo su primera proyección con lo que ya hay
en `<html>` y NOMBRA los ejes que discrepan. Convierte en ruidosos cinco fallos
que hoy son idénticos y mudos: pin olvidado, `themeIds` incompleto, `storageKey`
distinto, artefacto rancio y script bloqueado por la CSP.
Artefacto 14 666 B, `sha256-5fulJ/2Q6DttR258KqIhWSwfm5g7973BEzzzq14fapY=`,
generación determinista. Verificación: suite entera 461 ficheros / 5404 tests, exit 0 · check con
0 errores bajo `src/` y bajo `scripts/` (89 en `web/`, ledger intacto) ·
docs:check 0/0/819 · `npx vitest run src/uix/active-uix` 9 ficheros / 97 tests.
El test nuevo `boot/pack.test.ts` empaqueta `render.ts` y lo ejecuta: sale ROJO
contra el código anterior con el ENOENT literal, y es lo único que impide que
esta clase vuelva.
Adversarial Opus independiente (árbol byte-idéntico al terminar, 14 mutaciones
revertidas): midió el hash bajo CSP real contra un servidor HTTP en Chromium 145,
Firefox 146 y WebKit 26, con control negativo bloqueado; montó una app SvelteKit
real siguiendo la guía al pie de la letra y la vio construir, prerenderizar y
arrancar sin violaciones; y probó el ENOENT con un build real. Declaró 5 defectos
del lote, ninguno bloqueante, cerrados en una segunda ronda y re-verificados uno
a uno con su reproducción original: un universal falso en un comentario, la
puerta del artefacto sin validar el cuerpo (ahora con guard propio importable),
la constante del hash sin comillas para quien escriba su propia cabecera, el
audit que SÍ viaja al bundle de producción (1 428 B medidos, no un supuesto; no
CORRE, gracias a un ternario que el bundler no pliega) y el eje que se nombraba a
profundidad 2 y ahora va en el mensaje. La redacción del guard del artefacto,
último defecto de la segunda pasada, la corregí yo: corre en cada render, 76 us
con artefacto frente a 1,49 us sin él, medido.
Filas que deja para el autor: los 1 428 B del audit en producción, si se gatean
con una bandera que el bundler pliegue · el audit queda mudo si el runtime se
desecha en el mismo turno · la fila 2 debe decidir si la puerta verifica que el
hash describa al cuerpo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
not own `app.html` and does not install a hook.
|
|
|
|
|
|
|
docs: el repositorio, el formato y los guards de texto entran en el corpus; cuatro afirmaciones caducas mueren (documentación del cierre)
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-<n>`) 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 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
**`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).
|
|
|
|
|
|
|
feat(active-uix)!: el boot viaja como CUERPO CONSTANTE con los parámetros en un atributo — un hash de CSP fijo, sin node:fs, y la delta deja de confirmarse a sí misma (fila 1)
La receta documentada del boot pre-hidratación no funcionaba en ningún despliegue
real, y el panel de diseño de nueve agentes que el autor pidió lo confirmó
midiendo: con el `render.ts` anterior, un build REAL de SvelteKit con
adapter-static revienta el prerender con `ENOENT …/output/server/generated/boot.js`
y devuelve un 500. La causa: `renderUixBootScript` leía el bundle DEL DISCO junto
a su propio módulo, y una vez empaquetado ese fichero no está ahí.
FORMA NUEVA. El generador compila una entrada propia (`boot/entry.ts`), sin
`globalName`, y emite el MISMO fichero de siempre (`generated/boot.js`, sin
borrar nada) como módulo con dos constantes: el texto exacto del script y su
`sha256-…`. `renderUixBootScript` pierde `node:fs`, `node:path`, `node:url`, la
caché y el envoltorio que estaba ESCRITO A MANO: al no haber `globalName`, la
llamada la hace la propia entrada compilada. «Compilado, no escrito» sale
reforzado, no rebajado.
LOS PARÁMETROS VIAJAN EN UN ATRIBUTO del mismo `<script>`, no dentro del cuerpo.
Consecuencia, que es el punto entero de la fila: el cuerpo es CONSTANTE, así que
su hash es constante por versión del framework, igual para todos los sitios y
todos los `themeIds`. La guía pasa a tener UNA sola receta, por hash, que cubre
prerender, SSR y hasta `mode: 'nonce'`; el nonce baja a salida de emergencia. Un
bloque `application/json` hermano se descartó con razón medida: rompería la
invariante de que el tag parsea a exactamente un `<script>` y un minificador de
HTML podría borrarlo sin que nadie se entere.
TRES MENTIRAS DE LA GUÍA, CORREGIDAS. `event.locals.nonce` no existe en Kit
2.55.0 (cero ocurrencias en su código; `respond.js:168` es `locals: {}`), así que
la receta anterior pasaba `undefined`, el navegador bloqueaba el script y volvía
el flash sin un solo error. El placeholder estaba ENCIMA de `%sveltekit.head%`,
donde la política de prerender de Kit no lo alcanza: eso es evadir la CSP, no
cumplirla, y no vale cuando la política llega por cabecera. Y el `html.replace`
con cadena interpreta `$'`, así que ahora la receta usa reemplazo FUNCIÓN. Se
añade el aviso que puede tumbarle la página al consumidor: añadir un hash a un
`script-src` que lleva `'unsafe-inline'` hace que el navegador IGNORE
`unsafe-inline` y bloquee los demás scripts inline del sitio, incluido el
arranque de Kit.
EL CRITERIO SE CORRIGE. `boot-delta.test.ts` gana un tercer snapshot contra un
documento LIMPIO. El anterior arrancaba el runtime sobre el documento ya sellado,
y como `directionDimension.derive` devuelve `env.direction` antes de derivar del
idioma y el entorno se siembra leyendo el `<html dir>` que el boot acaba de
escribir, la columna `dir` se confirmaba a sí misma. Medido con el boot clavado a
`rtl`: los cinco rojos caen todos en la lectura nueva y ninguno en la anterior.
AUDIT DEV. La raíz compara en desarrollo su primera proyección con lo que ya hay
en `<html>` y NOMBRA los ejes que discrepan. Convierte en ruidosos cinco fallos
que hoy son idénticos y mudos: pin olvidado, `themeIds` incompleto, `storageKey`
distinto, artefacto rancio y script bloqueado por la CSP.
Artefacto 14 666 B, `sha256-5fulJ/2Q6DttR258KqIhWSwfm5g7973BEzzzq14fapY=`,
generación determinista. Verificación: suite entera 461 ficheros / 5404 tests, exit 0 · check con
0 errores bajo `src/` y bajo `scripts/` (89 en `web/`, ledger intacto) ·
docs:check 0/0/819 · `npx vitest run src/uix/active-uix` 9 ficheros / 97 tests.
El test nuevo `boot/pack.test.ts` empaqueta `render.ts` y lo ejecuta: sale ROJO
contra el código anterior con el ENOENT literal, y es lo único que impide que
esta clase vuelva.
Adversarial Opus independiente (árbol byte-idéntico al terminar, 14 mutaciones
revertidas): midió el hash bajo CSP real contra un servidor HTTP en Chromium 145,
Firefox 146 y WebKit 26, con control negativo bloqueado; montó una app SvelteKit
real siguiendo la guía al pie de la letra y la vio construir, prerenderizar y
arrancar sin violaciones; y probó el ENOENT con un build real. Declaró 5 defectos
del lote, ninguno bloqueante, cerrados en una segunda ronda y re-verificados uno
a uno con su reproducción original: un universal falso en un comentario, la
puerta del artefacto sin validar el cuerpo (ahora con guard propio importable),
la constante del hash sin comillas para quien escriba su propia cabecera, el
audit que SÍ viaja al bundle de producción (1 428 B medidos, no un supuesto; no
CORRE, gracias a un ternario que el bundler no pliega) y el eje que se nombraba a
profundidad 2 y ahora va en el mensaje. La redacción del guard del artefacto,
último defecto de la segunda pasada, la corregí yo: corre en cada render, 76 us
con artefacto frente a 1,49 us sin él, medido.
Filas que deja para el autor: los 1 428 B del audit en producción, si se gatean
con una bandera que el bundler pliegue · el audit queda mudo si el runtime se
desecha en el mismo turno · la fila 2 debe decidir si la puerta verifica que el
hash describa al cuerpo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
**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
|
|
|
|
|
|
everything that followed the placeholder and the `<script>` ends early. A
|
|
|
|
|
|
function replacement is inserted verbatim, which is the only form that keeps
|
|
|
|
|
|
the guarantee the tag's own tests make.
|
|
|
|
|
|
|
|
|
|
|
|
One hook covers a fully static build as well: `adapter-static` renders its
|
|
|
|
|
|
prerendered pages and its `200.html` fallback through `server.respond`, so the
|
|
|
|
|
|
transform runs for both.
|
|
|
|
|
|
|
|
|
|
|
|
### Step 4 — Name the hash in `svelte.config.js`
|
|
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
|
import { UIX_BOOT_CSP_HASH } from './src/uix/active-uix/generated/boot.js';
|
|
|
|
|
|
|
|
|
|
|
|
const config = {
|
|
|
|
|
|
kit: {
|
|
|
|
|
|
csp: {
|
|
|
|
|
|
mode: 'hash',
|
|
|
|
|
|
directives: { 'script-src': ['self', UIX_BOOT_CSP_HASH] }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
This is the canonical recipe. Kit computes hashes for the scripts IT emits and
|
|
|
|
|
|
fixes the policy before `transformPageChunk` runs, so the boot's own hash is
|
|
|
|
|
|
yours to declare — and it never changes until you upgrade UIX.
|
|
|
|
|
|
|
feat(active-uix)!: el boot lo compila el build del SITIO con su propio esquema, y lo que UIX escribe en <html dir> lleva marca de propiedad (fila 2 del boot, F1 del cierre)
F1 del plan de cierre del framework. Cierra el último trabajo del eje boot.
EL DEFECTO. El boot por defecto compilaba un catálogo de preferencias de UN
solo idioma (createDefaultUixPrefsSchema), y la raíz sembraba su entorno
leyendo el <html dir> que el boot acababa de escribir: un usuario árabe de
una app multiidioma no veía un parpadeo, se quedaba en LTR TODA la sesión,
porque el runtime tomaba la salida del boot como si la hubiera declarado la
página.
EL COMPILADOR POR SITIO. Una sola fusión, composeUixPrefsSchema, la consumen
createActiveUix y el boot: no puede derivar. El generador acepta --schema y
--out (el especificador boot/boot-schema.ts apunta al esquema del sitio o a
boot/default-schema.ts); los guards (sin runas, techo de tamaño, ASCII, sin
</script, valores string por atributo) son errores del COMPILADOR con mensajes
para el consumidor, porque ahora el artefacto lo produce el sitio. Un esquema
que importa el barrel $prefs se rechaza nombrándolo. scripts/uix-boot-check.ts
es el guard de rancidez: plugin de Vite que tumba el build con un artefacto
viejo (probado con vite build real) y función para CI. Delta cero por esquema:
por defecto, multiidioma con árabe, ejes redefinidos y esquema parcial.
LA MARCA DE PROPIEDAD, diseño firmado por el autor. El canon de dirección dice
que <html dir> es una proyección y nunca una fuente, con una sola excepción: el
dir del AUTOR en la plantilla o el servidor. El boot rompió la premisa de esa
excepción. Todo lo que UIX escribe en <html dir> lleva ahora
PREFS_DIR_PROJECTED_ATTR (el boot con valor «boot», cada proyección del runtime
con un token de instancia); la semilla solo adopta un dir SIN marca. Un dir
escrito por script no es una fuente: en ejecución la dirección se afirma con
prefs.setIntent('direction') o options.prefs.environment, que ganan a la
semilla. Se descartaron, midiendo, la marca con valor (cierra el script y
congela la dirección al navegar entre layouts) y la inferencia por valor.
dispose solo retira lo que todavía es SUYO: SvelteKit crea la raíz del layout
nuevo ANTES de destruir la vieja (medido), y un retiro a ciegas dejaba el
<html> de la raíz nueva sin dir, lang ni data-motion/sound/haptic (medido hoy:
los nueve atributos a null). Mismo principio que el unstamp de sema, que
comprueba data-event-id.
esbuild se declara como devDependency EXACTA 0.27.4: los bytes del boot y su
hash dependen del minificador, y una subida dentro de un rango rompería la
sincronía. Boot 15 198 B, sha256-hsqdGYcrRu3oEc0Q3G/A67ApQT3q9c/vT9zMDgxROg8=
(el hash se mueve: firmado por el autor).
VERIFICACIÓN. Constructor Opus en cuatro rondas y adversarial Opus en dos
pasadas independientes, con sus reproducciones repetidas tras cada cierre:
en Chromium, entrar en árabe y pasar a inglés y a español sigue al idioma, y al
revés también; el dir de plantilla gana antes y después de hidratar y en una
raíz recreada; una raíz recreada sobre una viva sigue al idioma con y sin boot;
tras create b → destroy a, <html> conserva lo que proyectó b. Diez defectos
declarados por el adversarial, cerrados (D1–D10): marca de propiedad, esquema
parcial que estampaba "undefined", vigilante de dev mudo, plugin sin test,
receta de CI que no cargaba en jsdom, cifras y prosa, y un vite build real que
se cae cuando el boot no compila. Mutaciones en rojo, restauradas byte a byte:
la proyección no marca · dispose sin guard de dueño (también repetida por el
coordinador: 3 rojos) · la semilla compara valor en vez de presencia · marcar
el dir del autor. Suite entera 463 ficheros / 5 437 tests, exit 0 · check 0 errores en src/ y en scripts/ (89 en web/, ledger intacto) ·
docs:check 0/0 · generate:boot dos veces byte-idéntico.
LO QUE NO CIERRA (al ledger de cierre): (1) PREEXISTENTE — en la misma
navegación entre layouts, el ActiveEidos.dispose de la raíz vieja sigue
retirando data-theme/mode/density/scaling de la raíz nueva; exige cambiar el
dispose de eidos, otra capa. (2) La propiedad de los atributos proyectados
cuelga de la marca de dir: si la plantilla declara la dirección, dispose deja
lang y data-motion puestos cuando la raíz se desmonta sin sustituta
(benigno). (3) Sin boot, un script que escribe dir antes de la primera raíz es
indistinguible de la plantilla y se adopta. (4) El vigilante de dev no
reacciona a ficheros nuevos ni a inputs fuera del root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
**The import must point at the artifact you actually SHIP.** The path above is
|
|
|
|
|
|
the framework's own, which is the right one as long as you ship the framework's
|
|
|
|
|
|
own body. Compile a boot of your own (next section) and this line has to move
|
|
|
|
|
|
with it: a hash that does not describe the body in the tag is a blocked script,
|
|
|
|
|
|
and nothing in the build will tell you.
|
|
|
|
|
|
|
feat(active-uix)!: el boot viaja como CUERPO CONSTANTE con los parámetros en un atributo — un hash de CSP fijo, sin node:fs, y la delta deja de confirmarse a sí misma (fila 1)
La receta documentada del boot pre-hidratación no funcionaba en ningún despliegue
real, y el panel de diseño de nueve agentes que el autor pidió lo confirmó
midiendo: con el `render.ts` anterior, un build REAL de SvelteKit con
adapter-static revienta el prerender con `ENOENT …/output/server/generated/boot.js`
y devuelve un 500. La causa: `renderUixBootScript` leía el bundle DEL DISCO junto
a su propio módulo, y una vez empaquetado ese fichero no está ahí.
FORMA NUEVA. El generador compila una entrada propia (`boot/entry.ts`), sin
`globalName`, y emite el MISMO fichero de siempre (`generated/boot.js`, sin
borrar nada) como módulo con dos constantes: el texto exacto del script y su
`sha256-…`. `renderUixBootScript` pierde `node:fs`, `node:path`, `node:url`, la
caché y el envoltorio que estaba ESCRITO A MANO: al no haber `globalName`, la
llamada la hace la propia entrada compilada. «Compilado, no escrito» sale
reforzado, no rebajado.
LOS PARÁMETROS VIAJAN EN UN ATRIBUTO del mismo `<script>`, no dentro del cuerpo.
Consecuencia, que es el punto entero de la fila: el cuerpo es CONSTANTE, así que
su hash es constante por versión del framework, igual para todos los sitios y
todos los `themeIds`. La guía pasa a tener UNA sola receta, por hash, que cubre
prerender, SSR y hasta `mode: 'nonce'`; el nonce baja a salida de emergencia. Un
bloque `application/json` hermano se descartó con razón medida: rompería la
invariante de que el tag parsea a exactamente un `<script>` y un minificador de
HTML podría borrarlo sin que nadie se entere.
TRES MENTIRAS DE LA GUÍA, CORREGIDAS. `event.locals.nonce` no existe en Kit
2.55.0 (cero ocurrencias en su código; `respond.js:168` es `locals: {}`), así que
la receta anterior pasaba `undefined`, el navegador bloqueaba el script y volvía
el flash sin un solo error. El placeholder estaba ENCIMA de `%sveltekit.head%`,
donde la política de prerender de Kit no lo alcanza: eso es evadir la CSP, no
cumplirla, y no vale cuando la política llega por cabecera. Y el `html.replace`
con cadena interpreta `$'`, así que ahora la receta usa reemplazo FUNCIÓN. Se
añade el aviso que puede tumbarle la página al consumidor: añadir un hash a un
`script-src` que lleva `'unsafe-inline'` hace que el navegador IGNORE
`unsafe-inline` y bloquee los demás scripts inline del sitio, incluido el
arranque de Kit.
EL CRITERIO SE CORRIGE. `boot-delta.test.ts` gana un tercer snapshot contra un
documento LIMPIO. El anterior arrancaba el runtime sobre el documento ya sellado,
y como `directionDimension.derive` devuelve `env.direction` antes de derivar del
idioma y el entorno se siembra leyendo el `<html dir>` que el boot acaba de
escribir, la columna `dir` se confirmaba a sí misma. Medido con el boot clavado a
`rtl`: los cinco rojos caen todos en la lectura nueva y ninguno en la anterior.
AUDIT DEV. La raíz compara en desarrollo su primera proyección con lo que ya hay
en `<html>` y NOMBRA los ejes que discrepan. Convierte en ruidosos cinco fallos
que hoy son idénticos y mudos: pin olvidado, `themeIds` incompleto, `storageKey`
distinto, artefacto rancio y script bloqueado por la CSP.
Artefacto 14 666 B, `sha256-5fulJ/2Q6DttR258KqIhWSwfm5g7973BEzzzq14fapY=`,
generación determinista. Verificación: suite entera 461 ficheros / 5404 tests, exit 0 · check con
0 errores bajo `src/` y bajo `scripts/` (89 en `web/`, ledger intacto) ·
docs:check 0/0/819 · `npx vitest run src/uix/active-uix` 9 ficheros / 97 tests.
El test nuevo `boot/pack.test.ts` empaqueta `render.ts` y lo ejecuta: sale ROJO
contra el código anterior con el ENOENT literal, y es lo único que impide que
esta clase vuelva.
Adversarial Opus independiente (árbol byte-idéntico al terminar, 14 mutaciones
revertidas): midió el hash bajo CSP real contra un servidor HTTP en Chromium 145,
Firefox 146 y WebKit 26, con control negativo bloqueado; montó una app SvelteKit
real siguiendo la guía al pie de la letra y la vio construir, prerenderizar y
arrancar sin violaciones; y probó el ENOENT con un build real. Declaró 5 defectos
del lote, ninguno bloqueante, cerrados en una segunda ronda y re-verificados uno
a uno con su reproducción original: un universal falso en un comentario, la
puerta del artefacto sin validar el cuerpo (ahora con guard propio importable),
la constante del hash sin comillas para quien escriba su propia cabecera, el
audit que SÍ viaja al bundle de producción (1 428 B medidos, no un supuesto; no
CORRE, gracias a un ternario que el bundler no pliega) y el eje que se nombraba a
profundidad 2 y ahora va en el mensaje. La redacción del guard del artefacto,
último defecto de la segunda pasada, la corregí yo: corre en cada render, 76 us
con artefacto frente a 1,49 us sin él, medido.
Filas que deja para el autor: los 1 428 B del audit en producción, si se gatean
con una bandera que el bundler pliegue · el audit queda mudo si el runtime se
desecha en el mismo turno · la fila 2 debe decidir si la puerta verifica que el
hash describa al cuerpo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
**The constant carries no quotes, because Kit adds them.** It recognises a
|
|
|
|
|
|
`sha256-…` source and writes `'sha256-…'` into the policy itself. A site that
|
|
|
|
|
|
sends its own `Content-Security-Policy` header instead of using `kit.csp` has
|
|
|
|
|
|
to quote it: `script-src 'self' 'sha256-…'`. Unquoted, the token is not a valid
|
|
|
|
|
|
source expression at all — the browser ignores it and blocks the script, in
|
|
|
|
|
|
every engine.
|
|
|
|
|
|
|
|
|
|
|
|
> **Adding a hash to a `script-src` that already carries `'unsafe-inline'`
|
|
|
|
|
|
> makes the browser IGNORE `'unsafe-inline'` and block every other inline
|
|
|
|
|
|
> script on the site, including Kit's own start-up.** That is how CSP Level 2
|
|
|
|
|
|
> and later are specified, not a UIX rule, and it is the one thing here that
|
|
|
|
|
|
> can take a page down while it fixes a flash. If your policy relies on
|
|
|
|
|
|
> `'unsafe-inline'` today, move every inline script to a hash (or a nonce) in
|
|
|
|
|
|
> the same change, or do not add this one.
|
|
|
|
|
|
|
|
|
|
|
|
Skip this step entirely when the site has no CSP.
|
|
|
|
|
|
|
feat(active-uix)!: el boot lo compila el build del SITIO con su propio esquema, y lo que UIX escribe en <html dir> lleva marca de propiedad (fila 2 del boot, F1 del cierre)
F1 del plan de cierre del framework. Cierra el último trabajo del eje boot.
EL DEFECTO. El boot por defecto compilaba un catálogo de preferencias de UN
solo idioma (createDefaultUixPrefsSchema), y la raíz sembraba su entorno
leyendo el <html dir> que el boot acababa de escribir: un usuario árabe de
una app multiidioma no veía un parpadeo, se quedaba en LTR TODA la sesión,
porque el runtime tomaba la salida del boot como si la hubiera declarado la
página.
EL COMPILADOR POR SITIO. Una sola fusión, composeUixPrefsSchema, la consumen
createActiveUix y el boot: no puede derivar. El generador acepta --schema y
--out (el especificador boot/boot-schema.ts apunta al esquema del sitio o a
boot/default-schema.ts); los guards (sin runas, techo de tamaño, ASCII, sin
</script, valores string por atributo) son errores del COMPILADOR con mensajes
para el consumidor, porque ahora el artefacto lo produce el sitio. Un esquema
que importa el barrel $prefs se rechaza nombrándolo. scripts/uix-boot-check.ts
es el guard de rancidez: plugin de Vite que tumba el build con un artefacto
viejo (probado con vite build real) y función para CI. Delta cero por esquema:
por defecto, multiidioma con árabe, ejes redefinidos y esquema parcial.
LA MARCA DE PROPIEDAD, diseño firmado por el autor. El canon de dirección dice
que <html dir> es una proyección y nunca una fuente, con una sola excepción: el
dir del AUTOR en la plantilla o el servidor. El boot rompió la premisa de esa
excepción. Todo lo que UIX escribe en <html dir> lleva ahora
PREFS_DIR_PROJECTED_ATTR (el boot con valor «boot», cada proyección del runtime
con un token de instancia); la semilla solo adopta un dir SIN marca. Un dir
escrito por script no es una fuente: en ejecución la dirección se afirma con
prefs.setIntent('direction') o options.prefs.environment, que ganan a la
semilla. Se descartaron, midiendo, la marca con valor (cierra el script y
congela la dirección al navegar entre layouts) y la inferencia por valor.
dispose solo retira lo que todavía es SUYO: SvelteKit crea la raíz del layout
nuevo ANTES de destruir la vieja (medido), y un retiro a ciegas dejaba el
<html> de la raíz nueva sin dir, lang ni data-motion/sound/haptic (medido hoy:
los nueve atributos a null). Mismo principio que el unstamp de sema, que
comprueba data-event-id.
esbuild se declara como devDependency EXACTA 0.27.4: los bytes del boot y su
hash dependen del minificador, y una subida dentro de un rango rompería la
sincronía. Boot 15 198 B, sha256-hsqdGYcrRu3oEc0Q3G/A67ApQT3q9c/vT9zMDgxROg8=
(el hash se mueve: firmado por el autor).
VERIFICACIÓN. Constructor Opus en cuatro rondas y adversarial Opus en dos
pasadas independientes, con sus reproducciones repetidas tras cada cierre:
en Chromium, entrar en árabe y pasar a inglés y a español sigue al idioma, y al
revés también; el dir de plantilla gana antes y después de hidratar y en una
raíz recreada; una raíz recreada sobre una viva sigue al idioma con y sin boot;
tras create b → destroy a, <html> conserva lo que proyectó b. Diez defectos
declarados por el adversarial, cerrados (D1–D10): marca de propiedad, esquema
parcial que estampaba "undefined", vigilante de dev mudo, plugin sin test,
receta de CI que no cargaba en jsdom, cifras y prosa, y un vite build real que
se cae cuando el boot no compila. Mutaciones en rojo, restauradas byte a byte:
la proyección no marca · dispose sin guard de dueño (también repetida por el
coordinador: 3 rojos) · la semilla compara valor en vez de presencia · marcar
el dir del autor. Suite entera 463 ficheros / 5 437 tests, exit 0 · check 0 errores en src/ y en scripts/ (89 en web/, ledger intacto) ·
docs:check 0/0 · generate:boot dos veces byte-idéntico.
LO QUE NO CIERRA (al ledger de cierre): (1) PREEXISTENTE — en la misma
navegación entre layouts, el ActiveEidos.dispose de la raíz vieja sigue
retirando data-theme/mode/density/scaling de la raíz nueva; exige cambiar el
dispose de eidos, otra capa. (2) La propiedad de los atributos proyectados
cuelga de la marca de dir: si la plantilla declara la dirección, dispose deja
lang y data-motion puestos cuando la raíz se desmonta sin sustituta
(benigno). (3) Sin boot, un script que escribe dir antes de la primera raíz es
indistinguible de la plantilla y se adopta. (4) El vigilante de dev no
reacciona a ficheros nuevos ni a inputs fuera del root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
### Your schema, your boot — the per-site compiler
|
|
|
|
|
|
|
|
|
|
|
|
Steps 1–4 ship the artifact UIX compiles for itself, and it is built around
|
|
|
|
|
|
UIX's DEFAULT preference schema: **one language**, one currency, the four
|
|
|
|
|
|
visual axes. If your app passes a schema of its own to
|
|
|
|
|
|
`createActiveUix({ prefs: { schema } })`, that boot is resolving somebody
|
|
|
|
|
|
else's preferences — and on a multi-language site the page paints in the wrong
|
|
|
|
|
|
language and the wrong direction.
|
|
|
|
|
|
|
|
|
|
|
|
Measured in Chromium, on a three-language site (`es` / `ar` / `en`) and a
|
|
|
|
|
|
browser asking for Arabic:
|
|
|
|
|
|
|
|
|
|
|
|
| reading | `dir` | `lang` |
|
|
|
|
|
|
| ------------------------------------------------------------- | --------- | -------- |
|
|
|
|
|
|
| first paint, with the boot compiled around the default schema | `ltr` | `es` |
|
|
|
|
|
|
| the same page after hydration, about half a second later | `rtl` | `ar` |
|
|
|
|
|
|
| first paint, with the boot compiled around the SITE's schema | **`rtl`** | **`ar`** |
|
feat(active-uix)!: el boot lo compila el build del SITIO con su propio esquema, y lo que UIX escribe en <html dir> lleva marca de propiedad (fila 2 del boot, F1 del cierre)
F1 del plan de cierre del framework. Cierra el último trabajo del eje boot.
EL DEFECTO. El boot por defecto compilaba un catálogo de preferencias de UN
solo idioma (createDefaultUixPrefsSchema), y la raíz sembraba su entorno
leyendo el <html dir> que el boot acababa de escribir: un usuario árabe de
una app multiidioma no veía un parpadeo, se quedaba en LTR TODA la sesión,
porque el runtime tomaba la salida del boot como si la hubiera declarado la
página.
EL COMPILADOR POR SITIO. Una sola fusión, composeUixPrefsSchema, la consumen
createActiveUix y el boot: no puede derivar. El generador acepta --schema y
--out (el especificador boot/boot-schema.ts apunta al esquema del sitio o a
boot/default-schema.ts); los guards (sin runas, techo de tamaño, ASCII, sin
</script, valores string por atributo) son errores del COMPILADOR con mensajes
para el consumidor, porque ahora el artefacto lo produce el sitio. Un esquema
que importa el barrel $prefs se rechaza nombrándolo. scripts/uix-boot-check.ts
es el guard de rancidez: plugin de Vite que tumba el build con un artefacto
viejo (probado con vite build real) y función para CI. Delta cero por esquema:
por defecto, multiidioma con árabe, ejes redefinidos y esquema parcial.
LA MARCA DE PROPIEDAD, diseño firmado por el autor. El canon de dirección dice
que <html dir> es una proyección y nunca una fuente, con una sola excepción: el
dir del AUTOR en la plantilla o el servidor. El boot rompió la premisa de esa
excepción. Todo lo que UIX escribe en <html dir> lleva ahora
PREFS_DIR_PROJECTED_ATTR (el boot con valor «boot», cada proyección del runtime
con un token de instancia); la semilla solo adopta un dir SIN marca. Un dir
escrito por script no es una fuente: en ejecución la dirección se afirma con
prefs.setIntent('direction') o options.prefs.environment, que ganan a la
semilla. Se descartaron, midiendo, la marca con valor (cierra el script y
congela la dirección al navegar entre layouts) y la inferencia por valor.
dispose solo retira lo que todavía es SUYO: SvelteKit crea la raíz del layout
nuevo ANTES de destruir la vieja (medido), y un retiro a ciegas dejaba el
<html> de la raíz nueva sin dir, lang ni data-motion/sound/haptic (medido hoy:
los nueve atributos a null). Mismo principio que el unstamp de sema, que
comprueba data-event-id.
esbuild se declara como devDependency EXACTA 0.27.4: los bytes del boot y su
hash dependen del minificador, y una subida dentro de un rango rompería la
sincronía. Boot 15 198 B, sha256-hsqdGYcrRu3oEc0Q3G/A67ApQT3q9c/vT9zMDgxROg8=
(el hash se mueve: firmado por el autor).
VERIFICACIÓN. Constructor Opus en cuatro rondas y adversarial Opus en dos
pasadas independientes, con sus reproducciones repetidas tras cada cierre:
en Chromium, entrar en árabe y pasar a inglés y a español sigue al idioma, y al
revés también; el dir de plantilla gana antes y después de hidratar y en una
raíz recreada; una raíz recreada sobre una viva sigue al idioma con y sin boot;
tras create b → destroy a, <html> conserva lo que proyectó b. Diez defectos
declarados por el adversarial, cerrados (D1–D10): marca de propiedad, esquema
parcial que estampaba "undefined", vigilante de dev mudo, plugin sin test,
receta de CI que no cargaba en jsdom, cifras y prosa, y un vite build real que
se cae cuando el boot no compila. Mutaciones en rojo, restauradas byte a byte:
la proyección no marca · dispose sin guard de dueño (también repetida por el
coordinador: 3 rojos) · la semilla compara valor en vez de presencia · marcar
el dir del autor. Suite entera 463 ficheros / 5 437 tests, exit 0 · check 0 errores en src/ y en scripts/ (89 en web/, ledger intacto) ·
docs:check 0/0 · generate:boot dos veces byte-idéntico.
LO QUE NO CIERRA (al ledger de cierre): (1) PREEXISTENTE — en la misma
navegación entre layouts, el ActiveEidos.dispose de la raíz vieja sigue
retirando data-theme/mode/density/scaling de la raíz nueva; exige cambiar el
dispose de eidos, otra capa. (2) La propiedad de los atributos proyectados
cuelga de la marca de dir: si la plantilla declara la dirección, dispose deja
lang y data-motion puestos cuando la raíz se desmonta sin sustituta
(benigno). (3) Sin boot, un script que escribe dir antes de la primera raíz es
indistinguible de la plantilla y se adopta. (4) El vigilante de dev no
reacciona a ficheros nuevos ni a inputs fuera del root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
|
|
|
|
|
|
The first row is what the user sees: an Arabic page laid out left to right
|
|
|
|
|
|
and announced in Spanish, until the framework hydrates and the whole page
|
|
|
|
|
|
flips. The dev audit names it (`the pre-hydration boot disagrees with the
|
|
|
|
|
|
runtime on dir, lang`); nothing in a production build does.
|
|
|
|
|
|
|
|
|
|
|
|
**Every `dir` UIX writes is marked as UIX's.** The runtime reads `<html dir>`
|
|
|
|
|
|
as an assertion only when it is YOURS — written in your page template or by
|
|
|
|
|
|
your server — and then it beats the language, before paint and after it. The
|
|
|
|
|
|
`dir` the boot derives, and every `dir` a root projects, carries
|
|
|
|
|
|
`data-dir-projected`, so a root skips it and keeps deriving: a language the
|
|
|
|
|
|
user picks later still moves the direction, on this root and on the next one a
|
|
|
|
|
|
layout mounts. A script that writes over it is not a source; at run time,
|
|
|
|
|
|
assert a direction with `prefs.setIntent('direction', …)` or
|
|
|
|
|
|
`prefs.environment`. The rule is the
|
|
|
|
|
|
[direction contract's](../canon/direction-contract.md), §6.
|
|
|
|
|
|
|
|
|
|
|
|
**What you write: one pure module, imported twice.**
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
// src/prefs-schema.ts
|
|
|
|
|
|
import { standardPrefsDimensions } from '$prefs/standard';
|
|
|
|
|
|
import type { SupportedLocale } from '$libs/langs';
|
|
|
|
|
|
import type { PrefsSchema } from '$libs/prefs';
|
|
|
|
|
|
|
|
|
|
|
|
export function bootPrefsSchema(defaultLocale: SupportedLocale): PrefsSchema {
|
|
|
|
|
|
return standardPrefsDimensions({
|
|
|
|
|
|
languages: [defaultLocale, 'ar', 'en'],
|
|
|
|
|
|
locales: [defaultLocale, 'ar', 'en'],
|
|
|
|
|
|
currencies: ['EUR', 'USD'],
|
|
|
|
|
|
defaults: { language: defaultLocale, locale: defaultLocale, currency: 'EUR' }
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
The export name is the contract — the compiler resolves `bootPrefsSchema` by
|
|
|
|
|
|
name. The same function is what your root composes:
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
createActiveUix({
|
|
|
|
|
|
langs: { schema: strings, defaultLocale: 'es' },
|
|
|
|
|
|
prefs: { schema: bootPrefsSchema('es') }
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
One module, two consumers. The merge with the four visual axes happens inside
|
|
|
|
|
|
both of them (`composeUixPrefsSchema`), so do not add the axes yourself: they
|
|
|
|
|
|
travel with the root and your schema goes ON TOP — it can redefine an axis,
|
|
|
|
|
|
never omit one.
|
|
|
|
|
|
|
|
|
|
|
|
**The same locale on both sides.** `defaultLocale` in the tag is the argument
|
|
|
|
|
|
your schema function receives before paint, and the one your root builds its
|
|
|
|
|
|
own schema around. Two different values are two different catalogues, which is
|
|
|
|
|
|
this section's defect with extra steps.
|
|
|
|
|
|
|
|
|
|
|
|
> **The module must be PURE, and it must import DEEP modules.** No runes, no
|
|
|
|
|
|
> `$app/*`, and never the `$prefs` barrel: the barrel re-exports a
|
|
|
|
|
|
> `.svelte.ts`, so it puts `$state(` in a plain `<script>` and the boot dies
|
|
|
|
|
|
> on line one — measured, and it takes the artifact from 15 KB to 56 KB on
|
|
|
|
|
|
> the way. The compiler refuses to write that artifact and says why.
|
|
|
|
|
|
|
|
|
|
|
|
**Compile it** — from YOUR directory, so the paths mean what they look like.
|
|
|
|
|
|
Until the `bin` entry below exists, the compiler is a script inside the
|
|
|
|
|
|
framework and you name it by path; `tsx` is what runs a `.ts` entry, so add it
|
|
|
|
|
|
to your own devDependencies:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
node --import tsx/esm <uix>/scripts/generate-boot.ts \
|
|
|
|
|
|
--schema src/prefs-schema.ts --out src/generated/boot.js
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**`--schema` and `--out` are resolved against the CURRENT directory**, and that
|
|
|
|
|
|
is the one thing to get right. Run the framework's own `npm run generate:boot`
|
|
|
|
|
|
and the current directory is the FRAMEWORK's, so `src/prefs-schema.ts` means a
|
|
|
|
|
|
file in UIX and `--out src/generated/boot.js` drops your artifact inside UIX's
|
|
|
|
|
|
checkout. From a framework checkout, pass both as absolute paths. A `--schema`
|
|
|
|
|
|
that resolves to nothing is an error that names the directory it tried.
|
|
|
|
|
|
|
|
|
|
|
|
`--out` decides where YOUR artifact lands; with neither flag the command
|
|
|
|
|
|
reproduces the framework's own, byte for byte. An unknown flag is an error,
|
|
|
|
|
|
never a shrug: a compiler that ignored `--schema` would hand you the default
|
|
|
|
|
|
boot while you believed you had compiled your own. And the compiler RUNS what it
|
|
|
|
|
|
wrote before writing it: a schema that throws, or that exports `bootPrefsSchema`
|
|
|
|
|
|
as anything but a function, fails the compile instead of producing an artifact
|
|
|
|
|
|
that stamps nothing in silence — and so does an attribute the boot would stamp
|
|
|
|
|
|
with something other than a string IN THAT RUN, which a browser would write on
|
|
|
|
|
|
`<html>` as the word `undefined`. It is one run: `defaultLocale` `'en'`, and no
|
|
|
|
|
|
`navigator`, `matchMedia` or `localStorage`. A `density` that answers
|
|
|
|
|
|
`comfortable` there and nothing for an Arabic browser compiles, and that browser
|
|
|
|
|
|
gets `data-density="undefined"`. A preference your schema does not declare at
|
|
|
|
|
|
all (no `language`, say), or one of `dir`, `lang`, `data-motion`, `data-sound`
|
|
|
|
|
|
and `data-haptic` that resolves to nothing, is left off `<html>`, as the runtime
|
|
|
|
|
|
leaves it.
|
|
|
|
|
|
|
|
|
|
|
|
Give it a name in your own `package.json`, because you will run it again on
|
|
|
|
|
|
every schema change and on every UIX upgrade:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"scripts": {
|
|
|
|
|
|
"boot": "node --import tsx/esm <uix>/scripts/generate-boot.ts --schema src/prefs-schema.ts --out src/generated/boot.js"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Then step 4 imports `UIX_BOOT_CSP_HASH` from **your** file instead of the
|
|
|
|
|
|
framework's — your body ships, so your hash is the one the policy must name.
|
|
|
|
|
|
|
|
|
|
|
|
> **Move that import, or the browser blocks your boot.** The two artifacts are
|
|
|
|
|
|
> both legitimate and nothing downstream compares them: the staleness guard
|
|
|
|
|
|
> below checks that YOUR artifact is fresh, and `renderUixBootScript` never
|
|
|
|
|
|
> reads `artifact.hash` at all. Ship your body under the framework's hash and
|
|
|
|
|
|
> the page is worse than it was before you started — the tag is blocked, the
|
|
|
|
|
|
> boot writes nothing, and hydration does the whole flash. Measured in
|
|
|
|
|
|
> Chromium: `Executing inline script violates … Content-Security-Policy`, then
|
|
|
|
|
|
> `dir=rtl lang=ar` arriving 400 ms late. One import, one artifact, in both
|
|
|
|
|
|
> places.
|
|
|
|
|
|
|
|
|
|
|
|
**The placeholder, the hook and the tag do not change.** The artifact enters
|
|
|
|
|
|
through the door `renderUixBootScript` already has:
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
import { UIX_BOOT_SCRIPT, UIX_BOOT_CSP_HASH } from './generated/boot.js';
|
|
|
|
|
|
|
|
|
|
|
|
renderUixBootScript({
|
|
|
|
|
|
defaultLocale: 'es',
|
docs: el repositorio, el formato y los guards de texto entran en el corpus; cuatro afirmaciones caducas mueren (documentación del cierre)
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-<n>`) 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 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
themeIds: THEME_IDS,
|
feat(active-uix)!: el boot lo compila el build del SITIO con su propio esquema, y lo que UIX escribe en <html dir> lleva marca de propiedad (fila 2 del boot, F1 del cierre)
F1 del plan de cierre del framework. Cierra el último trabajo del eje boot.
EL DEFECTO. El boot por defecto compilaba un catálogo de preferencias de UN
solo idioma (createDefaultUixPrefsSchema), y la raíz sembraba su entorno
leyendo el <html dir> que el boot acababa de escribir: un usuario árabe de
una app multiidioma no veía un parpadeo, se quedaba en LTR TODA la sesión,
porque el runtime tomaba la salida del boot como si la hubiera declarado la
página.
EL COMPILADOR POR SITIO. Una sola fusión, composeUixPrefsSchema, la consumen
createActiveUix y el boot: no puede derivar. El generador acepta --schema y
--out (el especificador boot/boot-schema.ts apunta al esquema del sitio o a
boot/default-schema.ts); los guards (sin runas, techo de tamaño, ASCII, sin
</script, valores string por atributo) son errores del COMPILADOR con mensajes
para el consumidor, porque ahora el artefacto lo produce el sitio. Un esquema
que importa el barrel $prefs se rechaza nombrándolo. scripts/uix-boot-check.ts
es el guard de rancidez: plugin de Vite que tumba el build con un artefacto
viejo (probado con vite build real) y función para CI. Delta cero por esquema:
por defecto, multiidioma con árabe, ejes redefinidos y esquema parcial.
LA MARCA DE PROPIEDAD, diseño firmado por el autor. El canon de dirección dice
que <html dir> es una proyección y nunca una fuente, con una sola excepción: el
dir del AUTOR en la plantilla o el servidor. El boot rompió la premisa de esa
excepción. Todo lo que UIX escribe en <html dir> lleva ahora
PREFS_DIR_PROJECTED_ATTR (el boot con valor «boot», cada proyección del runtime
con un token de instancia); la semilla solo adopta un dir SIN marca. Un dir
escrito por script no es una fuente: en ejecución la dirección se afirma con
prefs.setIntent('direction') o options.prefs.environment, que ganan a la
semilla. Se descartaron, midiendo, la marca con valor (cierra el script y
congela la dirección al navegar entre layouts) y la inferencia por valor.
dispose solo retira lo que todavía es SUYO: SvelteKit crea la raíz del layout
nuevo ANTES de destruir la vieja (medido), y un retiro a ciegas dejaba el
<html> de la raíz nueva sin dir, lang ni data-motion/sound/haptic (medido hoy:
los nueve atributos a null). Mismo principio que el unstamp de sema, que
comprueba data-event-id.
esbuild se declara como devDependency EXACTA 0.27.4: los bytes del boot y su
hash dependen del minificador, y una subida dentro de un rango rompería la
sincronía. Boot 15 198 B, sha256-hsqdGYcrRu3oEc0Q3G/A67ApQT3q9c/vT9zMDgxROg8=
(el hash se mueve: firmado por el autor).
VERIFICACIÓN. Constructor Opus en cuatro rondas y adversarial Opus en dos
pasadas independientes, con sus reproducciones repetidas tras cada cierre:
en Chromium, entrar en árabe y pasar a inglés y a español sigue al idioma, y al
revés también; el dir de plantilla gana antes y después de hidratar y en una
raíz recreada; una raíz recreada sobre una viva sigue al idioma con y sin boot;
tras create b → destroy a, <html> conserva lo que proyectó b. Diez defectos
declarados por el adversarial, cerrados (D1–D10): marca de propiedad, esquema
parcial que estampaba "undefined", vigilante de dev mudo, plugin sin test,
receta de CI que no cargaba en jsdom, cifras y prosa, y un vite build real que
se cae cuando el boot no compila. Mutaciones en rojo, restauradas byte a byte:
la proyección no marca · dispose sin guard de dueño (también repetida por el
coordinador: 3 rojos) · la semilla compara valor en vez de presencia · marcar
el dir del autor. Suite entera 463 ficheros / 5 437 tests, exit 0 · check 0 errores en src/ y en scripts/ (89 en web/, ledger intacto) ·
docs:check 0/0 · generate:boot dos veces byte-idéntico.
LO QUE NO CIERRA (al ledger de cierre): (1) PREEXISTENTE — en la misma
navegación entre layouts, el ActiveEidos.dispose de la raíz vieja sigue
retirando data-theme/mode/density/scaling de la raíz nueva; exige cambiar el
dispose de eidos, otra capa. (2) La propiedad de los atributos proyectados
cuelga de la marca de dir: si la plantilla declara la dirección, dispose deja
lang y data-motion puestos cuando la raíz se desmonta sin sustituta
(benigno). (3) Sin boot, un script que escribe dir antes de la primera raíz es
indistinguible de la plantilla y se adopta. (4) El vigilante de dev no
reacciona a ficheros nuevos ni a inputs fuera del root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
artifact: { script: UIX_BOOT_SCRIPT, hash: UIX_BOOT_CSP_HASH }
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### The staleness guard — not optional
|
|
|
|
|
|
|
|
|
|
|
|
A compiled artifact goes stale the moment you edit your schema, edit a
|
|
|
|
|
|
dimension, or upgrade UIX, and **every symptom of a stale boot is silent**:
|
|
|
|
|
|
the tag is wrapped in a mute `try`/`catch` by design, the values it stamps
|
|
|
|
|
|
are merely old rather than invalid, and nothing in the page says so. The only
|
|
|
|
|
|
guard worth having is one that stops the build.
|
|
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
|
// vite.config.js
|
|
|
|
|
|
import { uixBootCheck } from '<uix>/scripts/uix-boot-check.ts';
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
plugins: [uixBootCheck({ schema: 'src/prefs-schema.ts', out: 'src/generated/boot.js' })]
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
It recompiles in `buildStart` with your own configuration and fails the build
|
|
|
|
|
|
on any difference. In dev it WARNS instead — a dev server that refuses to
|
|
|
|
|
|
start over a stale cosmetic artifact helps nobody — and it registers every
|
|
|
|
|
|
module the compile READ, your schema and the dimensions included, so editing
|
|
|
|
|
|
one re-runs the check instead of waiting for a deploy. A schema module you are
|
|
|
|
|
|
halfway through typing is a warning too, and the next save of any module that
|
|
|
|
|
|
compile read runs the check again: the guard never takes the dev server
|
|
|
|
|
|
down with it.
|
|
|
|
|
|
|
|
|
|
|
|
With no Vite, the same check is a promise and a CI test is the whole wiring. The
|
|
|
|
|
|
first line is part of the recipe: the check runs esbuild, and esbuild refuses to
|
|
|
|
|
|
load in a `jsdom` environment (`Invariant violation: "new
|
|
|
|
|
|
TextEncoder().encode("") instanceof Uint8Array" is incorrectly false`), so in a
|
|
|
|
|
|
project whose tests default to `jsdom` the file fails before its first test.
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
// @vitest-environment node
|
|
|
|
|
|
import { expect, it } from 'vitest';
|
|
|
|
|
|
import { checkUixBootArtifact } from '<uix>/scripts/uix-boot-check.ts';
|
|
|
|
|
|
|
|
|
|
|
|
it('the compiled boot is not stale', async () => {
|
|
|
|
|
|
const result = await checkUixBootArtifact({
|
|
|
|
|
|
schema: 'src/prefs-schema.ts',
|
|
|
|
|
|
out: 'src/generated/boot.js'
|
|
|
|
|
|
});
|
|
|
|
|
|
expect(result).toMatchObject({ ok: 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
|
|
|
|
> **An app consumes UIX as source, from a workspace in this repository — not as
|
|
|
|
|
|
> a package.** Every `<uix>` above is the path from the app to the repository
|
|
|
|
|
|
> root (`../..` for `apps/<name>`), the compiler runs its `.ts` entry with `tsx`,
|
|
|
|
|
|
> and the aliases your schema module writes (`$prefs/standard`, `$libs/prefs`)
|
|
|
|
|
|
> resolve through `uix.aliases.js`, the one table every tool imports. There is
|
|
|
|
|
|
> no published `bin` and no npm package: the contract is
|
|
|
|
|
|
> [`docs/consuming.md`](../consuming.md).
|
feat(active-uix)!: el boot lo compila el build del SITIO con su propio esquema, y lo que UIX escribe en <html dir> lleva marca de propiedad (fila 2 del boot, F1 del cierre)
F1 del plan de cierre del framework. Cierra el último trabajo del eje boot.
EL DEFECTO. El boot por defecto compilaba un catálogo de preferencias de UN
solo idioma (createDefaultUixPrefsSchema), y la raíz sembraba su entorno
leyendo el <html dir> que el boot acababa de escribir: un usuario árabe de
una app multiidioma no veía un parpadeo, se quedaba en LTR TODA la sesión,
porque el runtime tomaba la salida del boot como si la hubiera declarado la
página.
EL COMPILADOR POR SITIO. Una sola fusión, composeUixPrefsSchema, la consumen
createActiveUix y el boot: no puede derivar. El generador acepta --schema y
--out (el especificador boot/boot-schema.ts apunta al esquema del sitio o a
boot/default-schema.ts); los guards (sin runas, techo de tamaño, ASCII, sin
</script, valores string por atributo) son errores del COMPILADOR con mensajes
para el consumidor, porque ahora el artefacto lo produce el sitio. Un esquema
que importa el barrel $prefs se rechaza nombrándolo. scripts/uix-boot-check.ts
es el guard de rancidez: plugin de Vite que tumba el build con un artefacto
viejo (probado con vite build real) y función para CI. Delta cero por esquema:
por defecto, multiidioma con árabe, ejes redefinidos y esquema parcial.
LA MARCA DE PROPIEDAD, diseño firmado por el autor. El canon de dirección dice
que <html dir> es una proyección y nunca una fuente, con una sola excepción: el
dir del AUTOR en la plantilla o el servidor. El boot rompió la premisa de esa
excepción. Todo lo que UIX escribe en <html dir> lleva ahora
PREFS_DIR_PROJECTED_ATTR (el boot con valor «boot», cada proyección del runtime
con un token de instancia); la semilla solo adopta un dir SIN marca. Un dir
escrito por script no es una fuente: en ejecución la dirección se afirma con
prefs.setIntent('direction') o options.prefs.environment, que ganan a la
semilla. Se descartaron, midiendo, la marca con valor (cierra el script y
congela la dirección al navegar entre layouts) y la inferencia por valor.
dispose solo retira lo que todavía es SUYO: SvelteKit crea la raíz del layout
nuevo ANTES de destruir la vieja (medido), y un retiro a ciegas dejaba el
<html> de la raíz nueva sin dir, lang ni data-motion/sound/haptic (medido hoy:
los nueve atributos a null). Mismo principio que el unstamp de sema, que
comprueba data-event-id.
esbuild se declara como devDependency EXACTA 0.27.4: los bytes del boot y su
hash dependen del minificador, y una subida dentro de un rango rompería la
sincronía. Boot 15 198 B, sha256-hsqdGYcrRu3oEc0Q3G/A67ApQT3q9c/vT9zMDgxROg8=
(el hash se mueve: firmado por el autor).
VERIFICACIÓN. Constructor Opus en cuatro rondas y adversarial Opus en dos
pasadas independientes, con sus reproducciones repetidas tras cada cierre:
en Chromium, entrar en árabe y pasar a inglés y a español sigue al idioma, y al
revés también; el dir de plantilla gana antes y después de hidratar y en una
raíz recreada; una raíz recreada sobre una viva sigue al idioma con y sin boot;
tras create b → destroy a, <html> conserva lo que proyectó b. Diez defectos
declarados por el adversarial, cerrados (D1–D10): marca de propiedad, esquema
parcial que estampaba "undefined", vigilante de dev mudo, plugin sin test,
receta de CI que no cargaba en jsdom, cifras y prosa, y un vite build real que
se cae cuando el boot no compila. Mutaciones en rojo, restauradas byte a byte:
la proyección no marca · dispose sin guard de dueño (también repetida por el
coordinador: 3 rojos) · la semilla compara valor en vez de presencia · marcar
el dir del autor. Suite entera 463 ficheros / 5 437 tests, exit 0 · check 0 errores en src/ y en scripts/ (89 en web/, ledger intacto) ·
docs:check 0/0 · generate:boot dos veces byte-idéntico.
LO QUE NO CIERRA (al ledger de cierre): (1) PREEXISTENTE — en la misma
navegación entre layouts, el ActiveEidos.dispose de la raíz vieja sigue
retirando data-theme/mode/density/scaling de la raíz nueva; exige cambiar el
dispose de eidos, otra capa. (2) La propiedad de los atributos proyectados
cuelga de la marca de dir: si la plantilla declara la dirección, dispose deja
lang y data-motion puestos cuando la raíz se desmonta sin sustituta
(benigno). (3) Sin boot, un script que escribe dir antes de la primera raíz es
indistinguible de la plantilla y se adopta. (4) El vigilante de dev no
reacciona a ficheros nuevos ni a inputs fuera del root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
|
feat(active-uix)!: el boot viaja como CUERPO CONSTANTE con los parámetros en un atributo — un hash de CSP fijo, sin node:fs, y la delta deja de confirmarse a sí misma (fila 1)
La receta documentada del boot pre-hidratación no funcionaba en ningún despliegue
real, y el panel de diseño de nueve agentes que el autor pidió lo confirmó
midiendo: con el `render.ts` anterior, un build REAL de SvelteKit con
adapter-static revienta el prerender con `ENOENT …/output/server/generated/boot.js`
y devuelve un 500. La causa: `renderUixBootScript` leía el bundle DEL DISCO junto
a su propio módulo, y una vez empaquetado ese fichero no está ahí.
FORMA NUEVA. El generador compila una entrada propia (`boot/entry.ts`), sin
`globalName`, y emite el MISMO fichero de siempre (`generated/boot.js`, sin
borrar nada) como módulo con dos constantes: el texto exacto del script y su
`sha256-…`. `renderUixBootScript` pierde `node:fs`, `node:path`, `node:url`, la
caché y el envoltorio que estaba ESCRITO A MANO: al no haber `globalName`, la
llamada la hace la propia entrada compilada. «Compilado, no escrito» sale
reforzado, no rebajado.
LOS PARÁMETROS VIAJAN EN UN ATRIBUTO del mismo `<script>`, no dentro del cuerpo.
Consecuencia, que es el punto entero de la fila: el cuerpo es CONSTANTE, así que
su hash es constante por versión del framework, igual para todos los sitios y
todos los `themeIds`. La guía pasa a tener UNA sola receta, por hash, que cubre
prerender, SSR y hasta `mode: 'nonce'`; el nonce baja a salida de emergencia. Un
bloque `application/json` hermano se descartó con razón medida: rompería la
invariante de que el tag parsea a exactamente un `<script>` y un minificador de
HTML podría borrarlo sin que nadie se entere.
TRES MENTIRAS DE LA GUÍA, CORREGIDAS. `event.locals.nonce` no existe en Kit
2.55.0 (cero ocurrencias en su código; `respond.js:168` es `locals: {}`), así que
la receta anterior pasaba `undefined`, el navegador bloqueaba el script y volvía
el flash sin un solo error. El placeholder estaba ENCIMA de `%sveltekit.head%`,
donde la política de prerender de Kit no lo alcanza: eso es evadir la CSP, no
cumplirla, y no vale cuando la política llega por cabecera. Y el `html.replace`
con cadena interpreta `$'`, así que ahora la receta usa reemplazo FUNCIÓN. Se
añade el aviso que puede tumbarle la página al consumidor: añadir un hash a un
`script-src` que lleva `'unsafe-inline'` hace que el navegador IGNORE
`unsafe-inline` y bloquee los demás scripts inline del sitio, incluido el
arranque de Kit.
EL CRITERIO SE CORRIGE. `boot-delta.test.ts` gana un tercer snapshot contra un
documento LIMPIO. El anterior arrancaba el runtime sobre el documento ya sellado,
y como `directionDimension.derive` devuelve `env.direction` antes de derivar del
idioma y el entorno se siembra leyendo el `<html dir>` que el boot acaba de
escribir, la columna `dir` se confirmaba a sí misma. Medido con el boot clavado a
`rtl`: los cinco rojos caen todos en la lectura nueva y ninguno en la anterior.
AUDIT DEV. La raíz compara en desarrollo su primera proyección con lo que ya hay
en `<html>` y NOMBRA los ejes que discrepan. Convierte en ruidosos cinco fallos
que hoy son idénticos y mudos: pin olvidado, `themeIds` incompleto, `storageKey`
distinto, artefacto rancio y script bloqueado por la CSP.
Artefacto 14 666 B, `sha256-5fulJ/2Q6DttR258KqIhWSwfm5g7973BEzzzq14fapY=`,
generación determinista. Verificación: suite entera 461 ficheros / 5404 tests, exit 0 · check con
0 errores bajo `src/` y bajo `scripts/` (89 en `web/`, ledger intacto) ·
docs:check 0/0/819 · `npx vitest run src/uix/active-uix` 9 ficheros / 97 tests.
El test nuevo `boot/pack.test.ts` empaqueta `render.ts` y lo ejecuta: sale ROJO
contra el código anterior con el ENOENT literal, y es lo único que impide que
esta clase vuelva.
Adversarial Opus independiente (árbol byte-idéntico al terminar, 14 mutaciones
revertidas): midió el hash bajo CSP real contra un servidor HTTP en Chromium 145,
Firefox 146 y WebKit 26, con control negativo bloqueado; montó una app SvelteKit
real siguiendo la guía al pie de la letra y la vio construir, prerenderizar y
arrancar sin violaciones; y probó el ENOENT con un build real. Declaró 5 defectos
del lote, ninguno bloqueante, cerrados en una segunda ronda y re-verificados uno
a uno con su reproducción original: un universal falso en un comentario, la
puerta del artefacto sin validar el cuerpo (ahora con guard propio importable),
la constante del hash sin comillas para quien escriba su propia cabecera, el
audit que SÍ viaja al bundle de producción (1 428 B medidos, no un supuesto; no
CORRE, gracias a un ternario que el bundler no pliega) y el eje que se nombraba a
profundidad 2 y ahora va en el mensaje. La redacción del guard del artefacto,
último defecto de la segunda pasada, la corregí yo: corre en cada render, 76 us
con artefacto frente a 1,49 us sin él, medido.
Filas que deja para el autor: los 1 428 B del audit en producción, si se gatean
con una bandera que el bundler pliegue · el audit queda mudo si el runtime se
desecha en el mismo turno · la fila 2 debe decidir si la puerta verifica que el
hash describa al cuerpo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
### The nonce — an emergency exit, not the recipe
|
|
|
|
|
|
|
|
|
|
|
|
`renderUixBootScript({ …, nonce })` exists for a site that runs its OWN policy
|
|
|
|
|
|
and issues its own nonces. That site writes the header by hand, so if it stays
|
|
|
|
|
|
with the hash it also writes the quotes by hand — `'sha256-…'`, step 4. Two
|
|
|
|
|
|
things to know before reaching for the nonce:
|
|
|
|
|
|
|
|
|
|
|
|
- **`event.locals.nonce` does not exist.** Kit starts `locals` as `{}` and
|
|
|
|
|
|
never writes a nonce into it. Passing it renders no attribute at all, the
|
|
|
|
|
|
browser blocks the script, and the flash comes back — silently. The only
|
|
|
|
|
|
nonce Kit exposes is the `%sveltekit.nonce%` substitution, which happens
|
|
|
|
|
|
across the whole template BEFORE `transformPageChunk`; a site that wants it
|
|
|
|
|
|
carries it through a placeholder of its own (`%uix.boot:%sveltekit.nonce%%`
|
|
|
|
|
|
in `app.html`, a regex replacement in the hook).
|
|
|
|
|
|
- **A prerendered page has no nonce at all.** Kit rejects `%sveltekit.nonce%`
|
|
|
|
|
|
in the template and `mode: 'nonce'` when prerendering, because the value
|
|
|
|
|
|
would be baked into a file served to everyone. On `adapter-static` the hash
|
|
|
|
|
|
is the only option.
|
|
|
|
|
|
|
|
|
|
|
|
The nonce must fit the CSP Level 3 nonce grammar (base64 or base64url
|
|
|
|
|
|
characters, at most two trailing `=`), and Kit's always does: it is base64.
|
|
|
|
|
|
Outside that grammar the value is not a valid `'nonce-…'` source — engines that
|
|
|
|
|
|
implement the grammar never match it, and the tolerance some engines show for a
|
|
|
|
|
|
stray `=` is nothing a portable policy can rely on — so `renderUixBootScript`
|
|
|
|
|
|
throws `ActiveUixInvalidBootNonceError` instead of writing it into the tag.
|
feat(prefs)!: un motor, un sobre, un codigo — mode/theme/density/scaling son prefs, sobre canonico y boot compilado
Auditoria P2 #2: los attrs de preferencias se estampaban solo en cliente
tras hidratar (app.html sin script, build estatico sin servidor) — flash
claro para todo usuario dark — y no podia existir un script de arranque
porque no habia contrato de persistencia (PrefsIntentStorage lo aporta
cada app) y eidos llevaba un SEGUNDO motor de preferencias (theme /
modeSource / densitySource / scalingSource por opciones, sin persistir),
por doctrina escrita en arts/prefs/README.md («UIX does not turn it into
prefs.theme»). El autor REVOCA esa doctrina.
UN MOTOR. `mode` es una dimension de prefs junto a `motion` (mismo patron,
`resolveTheme` de $libs/theme, que ya existia como gemelo de
`resolveMotion`); `theme` (id de familia), `density` y `scaling` las
declara la RAIZ en un modulo PURO (src/uix/active-uix/prefs-schema.ts) —
arts/prefs no importa src/uix. Eidos lee `uix.prefs` por su puerto
`ActiveEidosPreferenceSource` (src/uix/eidos/prefs-source.ts), como ya
consume uix.langs / uix.dom / uix.motion. El resolver de tema baja a una
funcion pura `resolveThemeId(theme, mode, isRegistered)`
(src/uix/eidos/lib/theme-id.ts); los cuatro nombres de attr de eidos a
src/uix/eidos/lib/attrs.ts y los de la proyeccion a
src/arts/prefs/dom-attrs.ts: modulos sin Svelte, porque el boot los
comparte.
UN SOBRE. src/libs/prefs/document.ts: `uix.prefs-intent` v1 bajo la clave
`uix.prefs`, espejo del documento de eidos (kind + version estrictos,
version desconocida se RECHAZA). Solo intencion — nunca efectivo ni
entorno. Adaptador localStorage canonico creado en la raiz standalone
(`prefs.storage`: propio gana, `false` desactiva). HIDRATACION SINCRONA:
el bridge awaitea `load()` aunque sea sincrono y produciria una primera
resolucion con defaults y un salto un tick despues — el flash movido de
sitio; la raiz lee el sobre ANTES de crear el motor y lo pasa como
`intent`; el bridge queda solo para persistir (skipHydrate).
UN CODIGO. El boot no se escribe: se COMPILA con esbuild desde los mismos
modulos puros (src/uix/active-uix/boot/boot.ts → generated/boot.js,
15.009 bytes / 5.625 gzip, IIFE sin globales, cero Svelte; test de
sincronia, precedente generated/base.css). `renderUixBootScript(params)`
(boot/render.ts) es Kit-agnostico: el framework entrega el string; el
hook de Kit son tres lineas de la app (receta en docs/theming/guide.md,
NO cableada: el layout congelado lleva persistencia propia). Estampa los
NUEVE attrs (dir lang data-motion data-sound data-haptic data-theme
data-mode data-density data-scaling) → DELTA CERO al hidratar, probado
por integracion con la raiz REAL (boot/boot-delta.test.ts, tres casos)
y por tres mutaciones (sin hidratacion sincrona → rojo; constante de
attr cambiada en un solo lado → rojo; boot compilado con `data-modo` →
los tres casos rojos).
Hallazgo del test de delta cero: la raiz no aplicaba el entorno del
navegador (esperaba que el app llamara detectBrowserEnvironment en
onMount) — ahora lo siembra ella (guardado por `document`) y observa sus
cambios (watchBrowserEnvironment → patchEnvironment), sustituyendo el
listener que eidos tenia en createSystemColorSchemeSource.
Cifras: vitest prefs+libs/prefs+active-uix+eidos+value-channels 57/593 ·
suite completa 453/5306 · check 73 (src/ a cero; ledger intacto) ·
check:gate OK · eidos:lint 0 · docs:check 0/0 · morfo:vocabulary,
arts/blocks/packs/rtl/translations/agent:check OK.
ESTADO INTERINO, firmado para manana (acta en CONTINUE-audit-p0.md):
`resolvePreferences` LANZA con `uix` + escalar/source (forma firmada
originalmente); medido despues, `ActiveEidos.create()` inyecta `uix`
SIEMPRE y todas las demos pasan escalares o sources (>=12 sitios,
incluido web/routes/uix/+layout@.svelte:511) → el sitio de demos lanza al
arrancar hasta la forma (c'): pines si (escalar explicito gana, precedente
`options.dom ?? options.uix?.dom`), sources reactivas por eje FUERA, sin
throw, pines en el boot. Pendientes anotados: contracts.test.ts:1270-1276
codifica la doctrina revocada; esbuild no es dependencia declarada
(transitiva de vite) — decision del autor con servidores parados.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
|
feat(eidos)!: un escalar es una instancia CLAVADA — mueren el throw de la puerta 2 y las fuentes por eje (c')
Cierra el estado INTERINO de e3c0899dd. `resolvePreferences` lanzaba con `uix` +
escalar y `ActiveEidos.create()` inyecta `uix` SIEMPRE, así que la regla no decía
«no mezcles dos motores»: decía «ningún escalar, nunca», y toda demo que clava un
panel en oscuro arrancaba con excepción. Forma firmada por el autor (c'):
- Pines SÍ: `theme` / `mode` / `density` / `scaling` en `ActiveEidosOptions` son
PINES — el eje queda clavado en la instancia y gana sobre la fuente, prefs
incluido (precedente en el mismo constructor: `options.dom ?? options.uix?.dom`).
Un eje clavado sigue suscrito: `apply()` corre y lo encuentra quieto.
- Sources por eje FUERA sin shim: `modeSource` / `densitySource` / `scalingSource`
eran la API del segundo motor. La puerta de sustitución ENTERA sigue siendo
`preferences`. Mueren `createComposedPreferenceSource`, `createStaticValueSource`
y `PREFERENCE_OPTION_KEYS`; nace `createStandalonePreferenceSource(dom)`:
sin primer motor no hay segundo — standalone sigue al SO EN VIVO para `mode`.
- Sin throw. UNA precedencia por UN envoltorio (`createPinnedPreferenceSource`)
sobre la puerta que responda (`preferences` · `uix.prefs` · standalone):
`pin ?? fuente ?? fallback` en las tres.
- Una función pura, dos lectores: `src/uix/eidos/lib/visual-preference.ts`
(`VisualPreferencePins` + `resolveVisualPreference(pin, value)`), importable por
el boot compilado como `lib/theme-id.ts`. `ActiveEidosOptions extends
VisualPreferencePins`; `UixBootParams.pins` lo toma entero;
`renderUixBootScript({ pins })` los embarca. DOS parámetros y no tres: el
fallback no es compartido (el boot resuelve siempre los cuatro ejes; en runtime
lo aporta cada fuente) — acta en changelog §60.
- Delta cero a CINCO casos: instancia clavada a dark con sobre en light (ni un
attr se mueve al hidratar) + familia clavada `acme` con modo de prefs
(`acme-dark` a los dos lados: el pin atraviesa `resolveThemeId`). Mutaciones
probadas: sin pin en el boot → 2 rojos; sin pin en el envoltorio → 2 rojos;
restauración por sha256.
- `contracts.test.ts`: retirado el guard «guards UIX docs shell from writing
visual prefs through ActivePrefs» — codificaba la doctrina REVOCADA el
2026-09-14 (escribir `theme` en prefs es el camino canónico); no se invierte:
un guard sobre un árbol congelado que se reconstruye no mide nada. Acta en §60.
- Coste en el árbol congelado: diez demos pasan `*Source` y pierden el tipo →
ledger `check-debt.ts` +22 (8 entradas nuevas, 2 subidas), cada una con causa
fechada (excepción firmada 2026-09-13). `src/` a CERO.
- Docs: eidos.md §preferencias · prefs README §Eidos Boundary · guide.md
(`pins`, tercer parámetro del boot) · changelog §60 · active-uix / overview /
active-architecture / blocks (snippets con `modeSource` al flujo canónico).
Verificación: vitest eidos+active-uix+prefs+contracts+value-channels 59/640 ·
check 95 = 73 + 22 exacto, src/ 0 · check:gate OK · docs:check 819 OK ·
generate:boot 15104 bytes con sync verde · adversarial Opus independiente
(informe en el handoff). Constructor + adversarial Opus 5; la sesión coordina.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
### The three parameters, and why they are yours
|
feat(prefs)!: un motor, un sobre, un codigo — mode/theme/density/scaling son prefs, sobre canonico y boot compilado
Auditoria P2 #2: los attrs de preferencias se estampaban solo en cliente
tras hidratar (app.html sin script, build estatico sin servidor) — flash
claro para todo usuario dark — y no podia existir un script de arranque
porque no habia contrato de persistencia (PrefsIntentStorage lo aporta
cada app) y eidos llevaba un SEGUNDO motor de preferencias (theme /
modeSource / densitySource / scalingSource por opciones, sin persistir),
por doctrina escrita en arts/prefs/README.md («UIX does not turn it into
prefs.theme»). El autor REVOCA esa doctrina.
UN MOTOR. `mode` es una dimension de prefs junto a `motion` (mismo patron,
`resolveTheme` de $libs/theme, que ya existia como gemelo de
`resolveMotion`); `theme` (id de familia), `density` y `scaling` las
declara la RAIZ en un modulo PURO (src/uix/active-uix/prefs-schema.ts) —
arts/prefs no importa src/uix. Eidos lee `uix.prefs` por su puerto
`ActiveEidosPreferenceSource` (src/uix/eidos/prefs-source.ts), como ya
consume uix.langs / uix.dom / uix.motion. El resolver de tema baja a una
funcion pura `resolveThemeId(theme, mode, isRegistered)`
(src/uix/eidos/lib/theme-id.ts); los cuatro nombres de attr de eidos a
src/uix/eidos/lib/attrs.ts y los de la proyeccion a
src/arts/prefs/dom-attrs.ts: modulos sin Svelte, porque el boot los
comparte.
UN SOBRE. src/libs/prefs/document.ts: `uix.prefs-intent` v1 bajo la clave
`uix.prefs`, espejo del documento de eidos (kind + version estrictos,
version desconocida se RECHAZA). Solo intencion — nunca efectivo ni
entorno. Adaptador localStorage canonico creado en la raiz standalone
(`prefs.storage`: propio gana, `false` desactiva). HIDRATACION SINCRONA:
el bridge awaitea `load()` aunque sea sincrono y produciria una primera
resolucion con defaults y un salto un tick despues — el flash movido de
sitio; la raiz lee el sobre ANTES de crear el motor y lo pasa como
`intent`; el bridge queda solo para persistir (skipHydrate).
UN CODIGO. El boot no se escribe: se COMPILA con esbuild desde los mismos
modulos puros (src/uix/active-uix/boot/boot.ts → generated/boot.js,
15.009 bytes / 5.625 gzip, IIFE sin globales, cero Svelte; test de
sincronia, precedente generated/base.css). `renderUixBootScript(params)`
(boot/render.ts) es Kit-agnostico: el framework entrega el string; el
hook de Kit son tres lineas de la app (receta en docs/theming/guide.md,
NO cableada: el layout congelado lleva persistencia propia). Estampa los
NUEVE attrs (dir lang data-motion data-sound data-haptic data-theme
data-mode data-density data-scaling) → DELTA CERO al hidratar, probado
por integracion con la raiz REAL (boot/boot-delta.test.ts, tres casos)
y por tres mutaciones (sin hidratacion sincrona → rojo; constante de
attr cambiada en un solo lado → rojo; boot compilado con `data-modo` →
los tres casos rojos).
Hallazgo del test de delta cero: la raiz no aplicaba el entorno del
navegador (esperaba que el app llamara detectBrowserEnvironment en
onMount) — ahora lo siembra ella (guardado por `document`) y observa sus
cambios (watchBrowserEnvironment → patchEnvironment), sustituyendo el
listener que eidos tenia en createSystemColorSchemeSource.
Cifras: vitest prefs+libs/prefs+active-uix+eidos+value-channels 57/593 ·
suite completa 453/5306 · check 73 (src/ a cero; ledger intacto) ·
check:gate OK · eidos:lint 0 · docs:check 0/0 · morfo:vocabulary,
arts/blocks/packs/rtl/translations/agent:check OK.
ESTADO INTERINO, firmado para manana (acta en CONTINUE-audit-p0.md):
`resolvePreferences` LANZA con `uix` + escalar/source (forma firmada
originalmente); medido despues, `ActiveEidos.create()` inyecta `uix`
SIEMPRE y todas las demos pasan escalares o sources (>=12 sitios,
incluido web/routes/uix/+layout@.svelte:511) → el sitio de demos lanza al
arrancar hasta la forma (c'): pines si (escalar explicito gana, precedente
`options.dom ?? options.uix?.dom`), sources reactivas por eje FUERA, sin
throw, pines en el boot. Pendientes anotados: contracts.test.ts:1270-1276
codifica la doctrina revocada; esbuild no es dependencia declarada
(transitiva de vite) — decision del autor con servidores parados.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
|
|
|
|
|
|
- `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.
|
docs: el repositorio, el formato y los guards de texto entran en el corpus; cuatro afirmaciones caducas mueren (documentación del cierre)
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-<n>`) 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 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
- `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
|
feat(prefs)!: un motor, un sobre, un codigo — mode/theme/density/scaling son prefs, sobre canonico y boot compilado
Auditoria P2 #2: los attrs de preferencias se estampaban solo en cliente
tras hidratar (app.html sin script, build estatico sin servidor) — flash
claro para todo usuario dark — y no podia existir un script de arranque
porque no habia contrato de persistencia (PrefsIntentStorage lo aporta
cada app) y eidos llevaba un SEGUNDO motor de preferencias (theme /
modeSource / densitySource / scalingSource por opciones, sin persistir),
por doctrina escrita en arts/prefs/README.md («UIX does not turn it into
prefs.theme»). El autor REVOCA esa doctrina.
UN MOTOR. `mode` es una dimension de prefs junto a `motion` (mismo patron,
`resolveTheme` de $libs/theme, que ya existia como gemelo de
`resolveMotion`); `theme` (id de familia), `density` y `scaling` las
declara la RAIZ en un modulo PURO (src/uix/active-uix/prefs-schema.ts) —
arts/prefs no importa src/uix. Eidos lee `uix.prefs` por su puerto
`ActiveEidosPreferenceSource` (src/uix/eidos/prefs-source.ts), como ya
consume uix.langs / uix.dom / uix.motion. El resolver de tema baja a una
funcion pura `resolveThemeId(theme, mode, isRegistered)`
(src/uix/eidos/lib/theme-id.ts); los cuatro nombres de attr de eidos a
src/uix/eidos/lib/attrs.ts y los de la proyeccion a
src/arts/prefs/dom-attrs.ts: modulos sin Svelte, porque el boot los
comparte.
UN SOBRE. src/libs/prefs/document.ts: `uix.prefs-intent` v1 bajo la clave
`uix.prefs`, espejo del documento de eidos (kind + version estrictos,
version desconocida se RECHAZA). Solo intencion — nunca efectivo ni
entorno. Adaptador localStorage canonico creado en la raiz standalone
(`prefs.storage`: propio gana, `false` desactiva). HIDRATACION SINCRONA:
el bridge awaitea `load()` aunque sea sincrono y produciria una primera
resolucion con defaults y un salto un tick despues — el flash movido de
sitio; la raiz lee el sobre ANTES de crear el motor y lo pasa como
`intent`; el bridge queda solo para persistir (skipHydrate).
UN CODIGO. El boot no se escribe: se COMPILA con esbuild desde los mismos
modulos puros (src/uix/active-uix/boot/boot.ts → generated/boot.js,
15.009 bytes / 5.625 gzip, IIFE sin globales, cero Svelte; test de
sincronia, precedente generated/base.css). `renderUixBootScript(params)`
(boot/render.ts) es Kit-agnostico: el framework entrega el string; el
hook de Kit son tres lineas de la app (receta en docs/theming/guide.md,
NO cableada: el layout congelado lleva persistencia propia). Estampa los
NUEVE attrs (dir lang data-motion data-sound data-haptic data-theme
data-mode data-density data-scaling) → DELTA CERO al hidratar, probado
por integracion con la raiz REAL (boot/boot-delta.test.ts, tres casos)
y por tres mutaciones (sin hidratacion sincrona → rojo; constante de
attr cambiada en un solo lado → rojo; boot compilado con `data-modo` →
los tres casos rojos).
Hallazgo del test de delta cero: la raiz no aplicaba el entorno del
navegador (esperaba que el app llamara detectBrowserEnvironment en
onMount) — ahora lo siembra ella (guardado por `document`) y observa sus
cambios (watchBrowserEnvironment → patchEnvironment), sustituyendo el
listener que eidos tenia en createSystemColorSchemeSource.
Cifras: vitest prefs+libs/prefs+active-uix+eidos+value-channels 57/593 ·
suite completa 453/5306 · check 73 (src/ a cero; ledger intacto) ·
check:gate OK · eidos:lint 0 · docs:check 0/0 · morfo:vocabulary,
arts/blocks/packs/rtl/translations/agent:check OK.
ESTADO INTERINO, firmado para manana (acta en CONTINUE-audit-p0.md):
`resolvePreferences` LANZA con `uix` + escalar/source (forma firmada
originalmente); medido despues, `ActiveEidos.create()` inyecta `uix`
SIEMPRE y todas las demos pasan escalares o sources (>=12 sitios,
incluido web/routes/uix/+layout@.svelte:511) → el sitio de demos lanza al
arrancar hasta la forma (c'): pines si (escalar explicito gana, precedente
`options.dom ?? options.uix?.dom`), sources reactivas por eje FUERA, sin
throw, pines en el boot. Pendientes anotados: contracts.test.ts:1270-1276
codifica la doctrina revocada; esbuild no es dependencia declarada
(transitiva de vite) — decision del autor con servidores parados.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
complete theme id?" branch; without it every family gets a `-light` /
|
|
|
|
|
|
`-dark` suffix appended and `data-theme` disagrees with the runtime.
|
feat(eidos)!: un escalar es una instancia CLAVADA — mueren el throw de la puerta 2 y las fuentes por eje (c')
Cierra el estado INTERINO de e3c0899dd. `resolvePreferences` lanzaba con `uix` +
escalar y `ActiveEidos.create()` inyecta `uix` SIEMPRE, así que la regla no decía
«no mezcles dos motores»: decía «ningún escalar, nunca», y toda demo que clava un
panel en oscuro arrancaba con excepción. Forma firmada por el autor (c'):
- Pines SÍ: `theme` / `mode` / `density` / `scaling` en `ActiveEidosOptions` son
PINES — el eje queda clavado en la instancia y gana sobre la fuente, prefs
incluido (precedente en el mismo constructor: `options.dom ?? options.uix?.dom`).
Un eje clavado sigue suscrito: `apply()` corre y lo encuentra quieto.
- Sources por eje FUERA sin shim: `modeSource` / `densitySource` / `scalingSource`
eran la API del segundo motor. La puerta de sustitución ENTERA sigue siendo
`preferences`. Mueren `createComposedPreferenceSource`, `createStaticValueSource`
y `PREFERENCE_OPTION_KEYS`; nace `createStandalonePreferenceSource(dom)`:
sin primer motor no hay segundo — standalone sigue al SO EN VIVO para `mode`.
- Sin throw. UNA precedencia por UN envoltorio (`createPinnedPreferenceSource`)
sobre la puerta que responda (`preferences` · `uix.prefs` · standalone):
`pin ?? fuente ?? fallback` en las tres.
- Una función pura, dos lectores: `src/uix/eidos/lib/visual-preference.ts`
(`VisualPreferencePins` + `resolveVisualPreference(pin, value)`), importable por
el boot compilado como `lib/theme-id.ts`. `ActiveEidosOptions extends
VisualPreferencePins`; `UixBootParams.pins` lo toma entero;
`renderUixBootScript({ pins })` los embarca. DOS parámetros y no tres: el
fallback no es compartido (el boot resuelve siempre los cuatro ejes; en runtime
lo aporta cada fuente) — acta en changelog §60.
- Delta cero a CINCO casos: instancia clavada a dark con sobre en light (ni un
attr se mueve al hidratar) + familia clavada `acme` con modo de prefs
(`acme-dark` a los dos lados: el pin atraviesa `resolveThemeId`). Mutaciones
probadas: sin pin en el boot → 2 rojos; sin pin en el envoltorio → 2 rojos;
restauración por sha256.
- `contracts.test.ts`: retirado el guard «guards UIX docs shell from writing
visual prefs through ActivePrefs» — codificaba la doctrina REVOCADA el
2026-09-14 (escribir `theme` en prefs es el camino canónico); no se invierte:
un guard sobre un árbol congelado que se reconstruye no mide nada. Acta en §60.
- Coste en el árbol congelado: diez demos pasan `*Source` y pierden el tipo →
ledger `check-debt.ts` +22 (8 entradas nuevas, 2 subidas), cada una con causa
fechada (excepción firmada 2026-09-13). `src/` a CERO.
- Docs: eidos.md §preferencias · prefs README §Eidos Boundary · guide.md
(`pins`, tercer parámetro del boot) · changelog §60 · active-uix / overview /
active-architecture / blocks (snippets con `modeSource` al flujo canónico).
Verificación: vitest eidos+active-uix+prefs+contracts+value-channels 59/640 ·
check 95 = 73 + 22 exacto, src/ 0 · check:gate OK · docs:check 819 OK ·
generate:boot 15104 bytes con sync verde · adversarial Opus independiente
(informe en el handoff). Constructor + adversarial Opus 5; la sesión coordina.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
- `pins` — the axes your ROOT `ActiveEidos` nailed, if any
|
|
|
|
|
|
(`ActiveEidos.create({ mode: 'dark' })` is a nailed instance, not a
|
|
|
|
|
|
preference). A pin wins over the resolved preference on both sides, so a
|
|
|
|
|
|
site that nails an axis and does not repeat it here paints the user's
|
|
|
|
|
|
preference before hydration and swaps to the pin after it — this section's
|
|
|
|
|
|
flash, inverted. Nothing pinned, nothing to pass.
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
renderUixBootScript({
|
|
|
|
|
|
defaultLocale: 'es',
|
docs: el repositorio, el formato y los guards de texto entran en el corpus; cuatro afirmaciones caducas mueren (documentación del cierre)
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-<n>`) 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 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
themeIds: THEME_IDS,
|
feat(eidos)!: un escalar es una instancia CLAVADA — mueren el throw de la puerta 2 y las fuentes por eje (c')
Cierra el estado INTERINO de e3c0899dd. `resolvePreferences` lanzaba con `uix` +
escalar y `ActiveEidos.create()` inyecta `uix` SIEMPRE, así que la regla no decía
«no mezcles dos motores»: decía «ningún escalar, nunca», y toda demo que clava un
panel en oscuro arrancaba con excepción. Forma firmada por el autor (c'):
- Pines SÍ: `theme` / `mode` / `density` / `scaling` en `ActiveEidosOptions` son
PINES — el eje queda clavado en la instancia y gana sobre la fuente, prefs
incluido (precedente en el mismo constructor: `options.dom ?? options.uix?.dom`).
Un eje clavado sigue suscrito: `apply()` corre y lo encuentra quieto.
- Sources por eje FUERA sin shim: `modeSource` / `densitySource` / `scalingSource`
eran la API del segundo motor. La puerta de sustitución ENTERA sigue siendo
`preferences`. Mueren `createComposedPreferenceSource`, `createStaticValueSource`
y `PREFERENCE_OPTION_KEYS`; nace `createStandalonePreferenceSource(dom)`:
sin primer motor no hay segundo — standalone sigue al SO EN VIVO para `mode`.
- Sin throw. UNA precedencia por UN envoltorio (`createPinnedPreferenceSource`)
sobre la puerta que responda (`preferences` · `uix.prefs` · standalone):
`pin ?? fuente ?? fallback` en las tres.
- Una función pura, dos lectores: `src/uix/eidos/lib/visual-preference.ts`
(`VisualPreferencePins` + `resolveVisualPreference(pin, value)`), importable por
el boot compilado como `lib/theme-id.ts`. `ActiveEidosOptions extends
VisualPreferencePins`; `UixBootParams.pins` lo toma entero;
`renderUixBootScript({ pins })` los embarca. DOS parámetros y no tres: el
fallback no es compartido (el boot resuelve siempre los cuatro ejes; en runtime
lo aporta cada fuente) — acta en changelog §60.
- Delta cero a CINCO casos: instancia clavada a dark con sobre en light (ni un
attr se mueve al hidratar) + familia clavada `acme` con modo de prefs
(`acme-dark` a los dos lados: el pin atraviesa `resolveThemeId`). Mutaciones
probadas: sin pin en el boot → 2 rojos; sin pin en el envoltorio → 2 rojos;
restauración por sha256.
- `contracts.test.ts`: retirado el guard «guards UIX docs shell from writing
visual prefs through ActivePrefs» — codificaba la doctrina REVOCADA el
2026-09-14 (escribir `theme` en prefs es el camino canónico); no se invierte:
un guard sobre un árbol congelado que se reconstruye no mide nada. Acta en §60.
- Coste en el árbol congelado: diez demos pasan `*Source` y pierden el tipo →
ledger `check-debt.ts` +22 (8 entradas nuevas, 2 subidas), cada una con causa
fechada (excepción firmada 2026-09-13). `src/` a CERO.
- Docs: eidos.md §preferencias · prefs README §Eidos Boundary · guide.md
(`pins`, tercer parámetro del boot) · changelog §60 · active-uix / overview /
active-architecture / blocks (snippets con `modeSource` al flujo canónico).
Verificación: vitest eidos+active-uix+prefs+contracts+value-channels 59/640 ·
check 95 = 73 + 22 exacto, src/ 0 · check:gate OK · docs:check 819 OK ·
generate:boot 15104 bytes con sync verde · adversarial Opus independiente
(informe en el handoff). Constructor + adversarial Opus 5; la sesión coordina.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
pins: { mode: 'dark' }
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
feat(prefs)!: un motor, un sobre, un codigo — mode/theme/density/scaling son prefs, sobre canonico y boot compilado
Auditoria P2 #2: los attrs de preferencias se estampaban solo en cliente
tras hidratar (app.html sin script, build estatico sin servidor) — flash
claro para todo usuario dark — y no podia existir un script de arranque
porque no habia contrato de persistencia (PrefsIntentStorage lo aporta
cada app) y eidos llevaba un SEGUNDO motor de preferencias (theme /
modeSource / densitySource / scalingSource por opciones, sin persistir),
por doctrina escrita en arts/prefs/README.md («UIX does not turn it into
prefs.theme»). El autor REVOCA esa doctrina.
UN MOTOR. `mode` es una dimension de prefs junto a `motion` (mismo patron,
`resolveTheme` de $libs/theme, que ya existia como gemelo de
`resolveMotion`); `theme` (id de familia), `density` y `scaling` las
declara la RAIZ en un modulo PURO (src/uix/active-uix/prefs-schema.ts) —
arts/prefs no importa src/uix. Eidos lee `uix.prefs` por su puerto
`ActiveEidosPreferenceSource` (src/uix/eidos/prefs-source.ts), como ya
consume uix.langs / uix.dom / uix.motion. El resolver de tema baja a una
funcion pura `resolveThemeId(theme, mode, isRegistered)`
(src/uix/eidos/lib/theme-id.ts); los cuatro nombres de attr de eidos a
src/uix/eidos/lib/attrs.ts y los de la proyeccion a
src/arts/prefs/dom-attrs.ts: modulos sin Svelte, porque el boot los
comparte.
UN SOBRE. src/libs/prefs/document.ts: `uix.prefs-intent` v1 bajo la clave
`uix.prefs`, espejo del documento de eidos (kind + version estrictos,
version desconocida se RECHAZA). Solo intencion — nunca efectivo ni
entorno. Adaptador localStorage canonico creado en la raiz standalone
(`prefs.storage`: propio gana, `false` desactiva). HIDRATACION SINCRONA:
el bridge awaitea `load()` aunque sea sincrono y produciria una primera
resolucion con defaults y un salto un tick despues — el flash movido de
sitio; la raiz lee el sobre ANTES de crear el motor y lo pasa como
`intent`; el bridge queda solo para persistir (skipHydrate).
UN CODIGO. El boot no se escribe: se COMPILA con esbuild desde los mismos
modulos puros (src/uix/active-uix/boot/boot.ts → generated/boot.js,
15.009 bytes / 5.625 gzip, IIFE sin globales, cero Svelte; test de
sincronia, precedente generated/base.css). `renderUixBootScript(params)`
(boot/render.ts) es Kit-agnostico: el framework entrega el string; el
hook de Kit son tres lineas de la app (receta en docs/theming/guide.md,
NO cableada: el layout congelado lleva persistencia propia). Estampa los
NUEVE attrs (dir lang data-motion data-sound data-haptic data-theme
data-mode data-density data-scaling) → DELTA CERO al hidratar, probado
por integracion con la raiz REAL (boot/boot-delta.test.ts, tres casos)
y por tres mutaciones (sin hidratacion sincrona → rojo; constante de
attr cambiada en un solo lado → rojo; boot compilado con `data-modo` →
los tres casos rojos).
Hallazgo del test de delta cero: la raiz no aplicaba el entorno del
navegador (esperaba que el app llamara detectBrowserEnvironment en
onMount) — ahora lo siembra ella (guardado por `document`) y observa sus
cambios (watchBrowserEnvironment → patchEnvironment), sustituyendo el
listener que eidos tenia en createSystemColorSchemeSource.
Cifras: vitest prefs+libs/prefs+active-uix+eidos+value-channels 57/593 ·
suite completa 453/5306 · check 73 (src/ a cero; ledger intacto) ·
check:gate OK · eidos:lint 0 · docs:check 0/0 · morfo:vocabulary,
arts/blocks/packs/rtl/translations/agent:check OK.
ESTADO INTERINO, firmado para manana (acta en CONTINUE-audit-p0.md):
`resolvePreferences` LANZA con `uix` + escalar/source (forma firmada
originalmente); medido despues, `ActiveEidos.create()` inyecta `uix`
SIEMPRE y todas las demos pasan escalares o sources (>=12 sitios,
incluido web/routes/uix/+layout@.svelte:511) → el sitio de demos lanza al
arrancar hasta la forma (c'): pines si (escalar explicito gana, precedente
`options.dom ?? options.uix?.dom`), sources reactivas por eje FUERA, sin
throw, pines en el boot. Pendientes anotados: contracts.test.ts:1270-1276
codifica la doctrina revocada; esbuild no es dependencia declarada
(transitiva de vite) — decision del autor con servidores parados.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
|
|
|
|
|
|
Pass `storageKey` as well if you replaced the canonical persistence adapter
|
|
|
|
|
|
(`prefs: { storage }`) with one that writes somewhere else. Key and adapter
|
|
|
|
|
|
move together or the two readers stop agreeing.
|
|
|
|
|
|
|
feat(active-uix)!: el boot viaja como CUERPO CONSTANTE con los parámetros en un atributo — un hash de CSP fijo, sin node:fs, y la delta deja de confirmarse a sí misma (fila 1)
La receta documentada del boot pre-hidratación no funcionaba en ningún despliegue
real, y el panel de diseño de nueve agentes que el autor pidió lo confirmó
midiendo: con el `render.ts` anterior, un build REAL de SvelteKit con
adapter-static revienta el prerender con `ENOENT …/output/server/generated/boot.js`
y devuelve un 500. La causa: `renderUixBootScript` leía el bundle DEL DISCO junto
a su propio módulo, y una vez empaquetado ese fichero no está ahí.
FORMA NUEVA. El generador compila una entrada propia (`boot/entry.ts`), sin
`globalName`, y emite el MISMO fichero de siempre (`generated/boot.js`, sin
borrar nada) como módulo con dos constantes: el texto exacto del script y su
`sha256-…`. `renderUixBootScript` pierde `node:fs`, `node:path`, `node:url`, la
caché y el envoltorio que estaba ESCRITO A MANO: al no haber `globalName`, la
llamada la hace la propia entrada compilada. «Compilado, no escrito» sale
reforzado, no rebajado.
LOS PARÁMETROS VIAJAN EN UN ATRIBUTO del mismo `<script>`, no dentro del cuerpo.
Consecuencia, que es el punto entero de la fila: el cuerpo es CONSTANTE, así que
su hash es constante por versión del framework, igual para todos los sitios y
todos los `themeIds`. La guía pasa a tener UNA sola receta, por hash, que cubre
prerender, SSR y hasta `mode: 'nonce'`; el nonce baja a salida de emergencia. Un
bloque `application/json` hermano se descartó con razón medida: rompería la
invariante de que el tag parsea a exactamente un `<script>` y un minificador de
HTML podría borrarlo sin que nadie se entere.
TRES MENTIRAS DE LA GUÍA, CORREGIDAS. `event.locals.nonce` no existe en Kit
2.55.0 (cero ocurrencias en su código; `respond.js:168` es `locals: {}`), así que
la receta anterior pasaba `undefined`, el navegador bloqueaba el script y volvía
el flash sin un solo error. El placeholder estaba ENCIMA de `%sveltekit.head%`,
donde la política de prerender de Kit no lo alcanza: eso es evadir la CSP, no
cumplirla, y no vale cuando la política llega por cabecera. Y el `html.replace`
con cadena interpreta `$'`, así que ahora la receta usa reemplazo FUNCIÓN. Se
añade el aviso que puede tumbarle la página al consumidor: añadir un hash a un
`script-src` que lleva `'unsafe-inline'` hace que el navegador IGNORE
`unsafe-inline` y bloquee los demás scripts inline del sitio, incluido el
arranque de Kit.
EL CRITERIO SE CORRIGE. `boot-delta.test.ts` gana un tercer snapshot contra un
documento LIMPIO. El anterior arrancaba el runtime sobre el documento ya sellado,
y como `directionDimension.derive` devuelve `env.direction` antes de derivar del
idioma y el entorno se siembra leyendo el `<html dir>` que el boot acaba de
escribir, la columna `dir` se confirmaba a sí misma. Medido con el boot clavado a
`rtl`: los cinco rojos caen todos en la lectura nueva y ninguno en la anterior.
AUDIT DEV. La raíz compara en desarrollo su primera proyección con lo que ya hay
en `<html>` y NOMBRA los ejes que discrepan. Convierte en ruidosos cinco fallos
que hoy son idénticos y mudos: pin olvidado, `themeIds` incompleto, `storageKey`
distinto, artefacto rancio y script bloqueado por la CSP.
Artefacto 14 666 B, `sha256-5fulJ/2Q6DttR258KqIhWSwfm5g7973BEzzzq14fapY=`,
generación determinista. Verificación: suite entera 461 ficheros / 5404 tests, exit 0 · check con
0 errores bajo `src/` y bajo `scripts/` (89 en `web/`, ledger intacto) ·
docs:check 0/0/819 · `npx vitest run src/uix/active-uix` 9 ficheros / 97 tests.
El test nuevo `boot/pack.test.ts` empaqueta `render.ts` y lo ejecuta: sale ROJO
contra el código anterior con el ENOENT literal, y es lo único que impide que
esta clase vuelva.
Adversarial Opus independiente (árbol byte-idéntico al terminar, 14 mutaciones
revertidas): midió el hash bajo CSP real contra un servidor HTTP en Chromium 145,
Firefox 146 y WebKit 26, con control negativo bloqueado; montó una app SvelteKit
real siguiendo la guía al pie de la letra y la vio construir, prerenderizar y
arrancar sin violaciones; y probó el ENOENT con un build real. Declaró 5 defectos
del lote, ninguno bloqueante, cerrados en una segunda ronda y re-verificados uno
a uno con su reproducción original: un universal falso en un comentario, la
puerta del artefacto sin validar el cuerpo (ahora con guard propio importable),
la constante del hash sin comillas para quien escriba su propia cabecera, el
audit que SÍ viaja al bundle de producción (1 428 B medidos, no un supuesto; no
CORRE, gracias a un ternario que el bundler no pliega) y el eje que se nombraba a
profundidad 2 y ahora va en el mensaje. La redacción del guard del artefacto,
último defecto de la segunda pasada, la corregí yo: corre en cada render, 76 us
con artefacto frente a 1,49 us sin él, medido.
Filas que deja para el autor: los 1 428 B del audit en producción, si se gatean
con una bandera que el bundler pliegue · el audit queda mudo si el runtime se
desecha en el mismo turno · la fila 2 debe decidir si la puerta verifica que el
hash describa al cuerpo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
`nonce` has its own section above, and `artifact` is for a site that compiles a
|
|
|
|
|
|
narrower boot of its own: it replaces the framework's `{ script, hash }` pair,
|
|
|
|
|
|
through the same function, so the hash in your CSP and the body in your page can
|
|
|
|
|
|
never come from two places.
|
|
|
|
|
|
|
|
|
|
|
|
**In development, the root tells you when the two disagree.** `createActiveUix`
|
|
|
|
|
|
reads what the boot stamped on `<html>` before its own projection writes, reads
|
|
|
|
|
|
it again when the turn is over, and names the axis that moved through the
|
|
|
|
|
|
logger. A forgotten `pin`, a `themeIds` missing a family, a `storageKey` that
|
|
|
|
|
|
does not match the adapter, an artifact that lags the schema and a tag the CSP
|
|
|
|
|
|
blocked all produce the same mute flash otherwise. It measures and changes
|
|
|
|
|
|
nothing, and it is silent on a site that never added the tag.
|
test(active-uix): el boot se prueba por el tag REAL que inyecta el sitio, y el nonce se valida contra la gramática de CSP
`renderUixBootScript` es el único camino de producción al boot pre-hidratación
(guía de theming, §59) y ningún test lo importaba. La suite de delta cero no lo
llamaba: ejecutaba una COPIA de su envoltorio con `new Function`, la segunda
implementación que la doctrina del boot prohíbe («compilado, no escrito»). Nadie
vigilaba que `pins` llegase al boot, que el escape de `<` impidiera cerrar el tag
antes de tiempo, ni el atributo `nonce`.
Tres decisiones firmadas por el autor:
1. `boot/render.test.ts` (nuevo, jsdom): forma del tag (un `<script>`, un solo
cierre, ningún `<!--`); nonce ausente, válido (base64 y base64url) e inválido;
reenvío de TODOS los parámetros medido por comportamiento en `<html>`
(`defaultLocale`, `pins`, `themeIds`, `storageKey`); ida y vuelta byte a byte
de una clave y un themeId hostiles con `</script>` y `<!--` sin inyectar
markup; ningún global tras ejecutar; higiene del bundle generado.
2. La suite de delta ejecuta el tag real: muere `runGeneratedBoot` con su
`new Function` y `BOOT_SOURCE`. El único ejecutor vive en
`test/boot-script-tag.ts` (DOMParser inerte + `vm.runInThisContext`, script
clásico en ámbito global). No eval indirecto: el bundle abre con
`"use strict"` y el eval estricto guarda sus `var` en un entorno propio, así
que una fuga de `__uixBoot` habría sido invisible (medido en Node y Chromium;
un control positivo lo pone en rojo si vuelve).
3. El nonce se valida contra CSP Level 3 `base64-value` y fuera de ella lanza
`ActiveUixInvalidBootNonceError` (`uix::boot.invalid_nonce`, con la longitud,
nunca el valor). `JSON.stringify` escapaba para JavaScript y no para un
atributo HTML: `a"b` salía como `nonce="a\"` más un atributo basura, y `<`
llegaba decodificado. La validación ES el escape. Razón precisa (corregida por
el adversarial antes de anclar): fuera de la gramática el valor no es un
`nonce-source` válido; Chromium 145 y Firefox 146 no lo casan, WebKit 26
tolera `=` de más, así que ninguna política portable puede apoyarse en él. El
nonce de SvelteKit 2.55.0 (`btoa` sobre bytes aleatorios) siempre la cumple.
`render.ts` localiza el bundle con una ruta de disco resuelta desde el módulo en
vez del idioma `new URL(…, import.meta.url)`, que el entorno jsdom de Vitest
reescribe a `self.location`. Medido por el adversarial con un build REAL de Vite
7.3.1: neutro en producción (build SSR suelto y con disposición tipo Kit: las dos
formas se comportan igual), funciona en dev SSR, y solo la nueva funciona bajo
Vitest jsdom. El barrel exporta la clase de error junto a las otras seis.
Verificación: suite de `boot` + `test` 5 ficheros / 29 tests → 6 / 52 · check con
0 errores bajo src/ (89 en web/, ledger intacto) · docs:check 0/0 · suite entera
459 ficheros / 5389 tests, exit 0. Mutaciones del constructor, cada una restaurada byte a byte:
`pins` fuera del reenvío 4 rojos (2 de ellos en la suite de delta: prueba de que
ya pasa por el camino real) · sin escape de `<` 2 · sin envoltorio 1 · sin
validación 10 · ejecutor vuelto a eval 1 · gramática sin `^` 6 · `storageKey`
sin reenviar 2. Adversarial Opus independiente (árbol intacto al terminar):
gramática = el ABNF exacto, 0 discrepancias en ~600 000 entradas contra un
oráculo propio; suite sin dependencia de orden en 7 semillas; 3 defectos del lote
cerrados en una segunda ronda — la frase absoluta sobre CSP en cuatro sitios, y
dos mutaciones que sobrevivían (clave en minúsculas; regex con flag `g` según el
orden), re-ejecutadas después por el coordinador: rojo en orden por defecto y con
las semillas 42 y 777.
Filas PREEXISTENTES que destapó el adversarial, no tocadas aquí (van al handoff
para el autor): en cualquier build de servidor empaquetado `renderUixBootScript`
lanza ENOENT al leer el bundle; la receta de la guía pasa `event.locals.nonce`,
que Kit nunca rellena (el navegador bloquea y vuelve el flash, sin error); y el
`html.replace` con cadena interpreta `$'` y puede cerrar el script.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
|
feat(active-uix)!: el boot lo compila el build del SITIO con su propio esquema, y lo que UIX escribe en <html dir> lleva marca de propiedad (fila 2 del boot, F1 del cierre)
F1 del plan de cierre del framework. Cierra el último trabajo del eje boot.
EL DEFECTO. El boot por defecto compilaba un catálogo de preferencias de UN
solo idioma (createDefaultUixPrefsSchema), y la raíz sembraba su entorno
leyendo el <html dir> que el boot acababa de escribir: un usuario árabe de
una app multiidioma no veía un parpadeo, se quedaba en LTR TODA la sesión,
porque el runtime tomaba la salida del boot como si la hubiera declarado la
página.
EL COMPILADOR POR SITIO. Una sola fusión, composeUixPrefsSchema, la consumen
createActiveUix y el boot: no puede derivar. El generador acepta --schema y
--out (el especificador boot/boot-schema.ts apunta al esquema del sitio o a
boot/default-schema.ts); los guards (sin runas, techo de tamaño, ASCII, sin
</script, valores string por atributo) son errores del COMPILADOR con mensajes
para el consumidor, porque ahora el artefacto lo produce el sitio. Un esquema
que importa el barrel $prefs se rechaza nombrándolo. scripts/uix-boot-check.ts
es el guard de rancidez: plugin de Vite que tumba el build con un artefacto
viejo (probado con vite build real) y función para CI. Delta cero por esquema:
por defecto, multiidioma con árabe, ejes redefinidos y esquema parcial.
LA MARCA DE PROPIEDAD, diseño firmado por el autor. El canon de dirección dice
que <html dir> es una proyección y nunca una fuente, con una sola excepción: el
dir del AUTOR en la plantilla o el servidor. El boot rompió la premisa de esa
excepción. Todo lo que UIX escribe en <html dir> lleva ahora
PREFS_DIR_PROJECTED_ATTR (el boot con valor «boot», cada proyección del runtime
con un token de instancia); la semilla solo adopta un dir SIN marca. Un dir
escrito por script no es una fuente: en ejecución la dirección se afirma con
prefs.setIntent('direction') o options.prefs.environment, que ganan a la
semilla. Se descartaron, midiendo, la marca con valor (cierra el script y
congela la dirección al navegar entre layouts) y la inferencia por valor.
dispose solo retira lo que todavía es SUYO: SvelteKit crea la raíz del layout
nuevo ANTES de destruir la vieja (medido), y un retiro a ciegas dejaba el
<html> de la raíz nueva sin dir, lang ni data-motion/sound/haptic (medido hoy:
los nueve atributos a null). Mismo principio que el unstamp de sema, que
comprueba data-event-id.
esbuild se declara como devDependency EXACTA 0.27.4: los bytes del boot y su
hash dependen del minificador, y una subida dentro de un rango rompería la
sincronía. Boot 15 198 B, sha256-hsqdGYcrRu3oEc0Q3G/A67ApQT3q9c/vT9zMDgxROg8=
(el hash se mueve: firmado por el autor).
VERIFICACIÓN. Constructor Opus en cuatro rondas y adversarial Opus en dos
pasadas independientes, con sus reproducciones repetidas tras cada cierre:
en Chromium, entrar en árabe y pasar a inglés y a español sigue al idioma, y al
revés también; el dir de plantilla gana antes y después de hidratar y en una
raíz recreada; una raíz recreada sobre una viva sigue al idioma con y sin boot;
tras create b → destroy a, <html> conserva lo que proyectó b. Diez defectos
declarados por el adversarial, cerrados (D1–D10): marca de propiedad, esquema
parcial que estampaba "undefined", vigilante de dev mudo, plugin sin test,
receta de CI que no cargaba en jsdom, cifras y prosa, y un vite build real que
se cae cuando el boot no compila. Mutaciones en rojo, restauradas byte a byte:
la proyección no marca · dispose sin guard de dueño (también repetida por el
coordinador: 3 rojos) · la semilla compara valor en vez de presencia · marcar
el dir del autor. Suite entera 463 ficheros / 5 437 tests, exit 0 · check 0 errores en src/ y en scripts/ (89 en web/, ledger intacto) ·
docs:check 0/0 · generate:boot dos veces byte-idéntico.
LO QUE NO CIERRA (al ledger de cierre): (1) PREEXISTENTE — en la misma
navegación entre layouts, el ActiveEidos.dispose de la raíz vieja sigue
retirando data-theme/mode/density/scaling de la raíz nueva; exige cambiar el
dispose de eidos, otra capa. (2) La propiedad de los atributos proyectados
cuelga de la marca de dir: si la plantilla declara la dirección, dispose deja
lang y data-motion puestos cuando la raíz se desmonta sin sustituta
(benigno). (3) Sin boot, un script que escribe dir antes de la primera raíz es
indistinguible de la plantilla y se adopta. (4) El vigilante de dev no
reacciona a ficheros nuevos ni a inputs fuera del root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 weeks ago
|
|
|
|
### The declared limits
|
|
|
|
|
|
|
|
|
|
|
|
**The theme resolver.** The boot reproduces the DEFAULT theme resolution
|
|
|
|
|
|
(`resolveThemeId`: registered id → as written; already mode-qualified → as
|
|
|
|
|
|
written; otherwise `${theme}-${mode}`). An app that passes its own
|
|
|
|
|
|
`themeResolver` to `ActiveEidos` has replaced that function and the compiled
|
|
|
|
|
|
boot does not know it, so `data-theme` differs until hydration.
|
|
|
|
|
|
|
|
|
|
|
|
The line is narrower than "a custom resolver". `ActiveEidosThemeResolver` is
|
|
|
|
|
|
`(context, active) => string` and `ActiveEidos` always calls it with the live
|
|
|
|
|
|
instance (`#themeResolver(themeContext, this)`). A resolver that reads only
|
|
|
|
|
|
`context` is a pure function of plain data, so it COULD travel into the boot
|
|
|
|
|
|
the way the schema now does; one that touches `active` cannot, because at
|
|
|
|
|
|
that moment there is no instance to touch — no `ActiveEidos`, no prefs
|
|
|
|
|
|
engine, nothing but a `<script>` in `<head>`. The seam that would carry the
|
|
|
|
|
|
first kind is not built: only the schema has one today.
|
|
|
|
|
|
|
|
|
|
|
|
**Attach mode.** `attachActiveUix(app)` is NOT covered. The app owns its prefs
|
|
|
|
|
|
engine there, so the framework merges nothing for it and the boot has no way
|
|
|
|
|
|
to know which schema that engine was built with. An app on attach composes
|
|
|
|
|
|
`uixVisualPrefsDimensions()` into its own schema and owns its pre-paint stamp;
|
|
|
|
|
|
the row that closes attach properly is open.
|