Guard prerendered Eidos foundation in base smoke

astra
dev 2 weeks ago
parent 0b8ec4ce4f
commit 2bbfb207a4

@ -3,15 +3,17 @@
*
* `build/` is served by a real Node HTTP server, never `route.fulfill`: a fulfilled body
* is not the bytes a browser decodes from the wire, and WebKit was measured blaming the CSP
* hash for it. A static check, then two passes for each visitor, one line of figures, exit 1
* on any failure:
* hash for it. A static check, a styled first-paint pass, then two passes for each visitor;
* one line of figures, exit 1 on any failure:
*
* 0. `index.html` — the boot tag comes AFTER the CSP `<meta>`. A `<meta>` policy governs
* only what follows it: a boot above it runs under no policy, and every browser pass
* below would stay green with a wrong hash (measured);
* 1. modules aborted (`/_app/**`) — the boot alone must stamp its nine attributes on
* 1. client JS aborted, linked CSS kept — foundation tokens must style the prerendered
* page before ActiveEidos can inject its runtime copy;
* 2. modules aborted (`/_app/**`) — the boot alone must stamp its nine attributes on
* `<html>`, mark the `dir` as its own, and give the visitor the values it asks for;
* 2. full load — 0 pageerror, 0 console.error, 0 CSP violations, and the nine attributes
* 3. full load — 0 pageerror, 0 console.error, 0 CSP violations, and the nine attributes
* unchanged once the app has hydrated.
*
* The visitors: `es-ES`; `ar-EG`, which must get `dir="rtl" lang="ar"`; and `es-ES` asking for
@ -155,6 +157,26 @@ async function bootOnly(browser, url, visitor) {
return { attrs, csp };
}
/** Keep linked CSS but block client JS: the prerendered first paint must be styled. */
async function foundationBeforeHydration(browser, url, visitor) {
const { context, page } = await open(browser, visitor, { abortModules: false });
await page.route('**/_app/**', (route) =>
route.request().resourceType() === 'script' ? route.abort() : route.continue()
);
await page.goto(url, { waitUntil: 'networkidle' });
const foundation = await page.evaluate(() => {
const style = getComputedStyle(document.documentElement);
return {
linked: document.querySelectorAll('link[rel="stylesheet"]').length > 0,
injected: document.getElementById('uix-eidos-static') !== null,
color: style.getPropertyValue('--color-primary-element').trim(),
dialogWidth: style.getPropertyValue('--dialog-content-width-md').trim()
};
});
await context.close();
return foundation;
}
/** Pass 2: the whole app, hydrated, watched for a late write. */
async function hydrated(browser, url, visitor) {
const { context, page, log } = await open(browser, visitor, { abortModules: false });
@ -211,6 +233,14 @@ try {
` boot tag at ${bootAt}, CSP <meta> at ${policyAt}: the boot is outside the policy`
);
}
const foundation = await foundationBeforeHydration(browser, url, VISITORS[0]);
const foundationReady =
foundation.linked && !foundation.injected && !!foundation.color && !!foundation.dialogWidth;
if (!foundationReady) {
failures.push(
` prerendered foundation missing before hydration: ${JSON.stringify(foundation)}`
);
}
for (const visitor of VISITORS) {
const boot = await bootOnly(browser, url, visitor);
@ -264,7 +294,7 @@ try {
);
const seconds = ((performance.now() - started) / 1000).toFixed(1);
console.log(
`smoke apps/base · boot ${stamped}/${BOOT_ATTRS.length * VISITORS.length} · ` +
`smoke apps/base · foundation ${Number(foundationReady)}/1 · boot ${stamped}/${BOOT_ATTRS.length * VISITORS.length} · ` +
Object.entries(figures)
.map(([name, value]) => `${name} ${value}`)
.join(' · ') +

@ -192,6 +192,27 @@ exportados inspeccionado no está vacío. Son cambios de tests, sin cambio de
runtime ni del árbol `web/`. El gate completo pasó 470/470 archivos y
5464/5464 pruebas; `apps/base` pasó check, build y smoke.
### L-130 · foundation CSS y primer pintado (2026-09-21)
El build prerenderizado de `apps/base` conserva la foundation enlazada y
`ActiveEidos` inserta otra copia de aproximadamente 425,6 KB al hidratar.
En Chromium, Firefox y WebKit, con JavaScript cliente bloqueado pero CSS cargada,
la captura de la página a 1280 × 800 fue idéntica byte a byte a la hidratada;
los tokens de color, ancho de diálogo y tipografía ya estaban presentes sin el
`<style>` runtime. En Chromium, el primer pintado ocurrió entre 160 y 196 ms y
la inserción del estilo entre 322 y 373 ms en las tres visitas medidas. Se
inspeccionó también el diálogo claro y en árabe/RTL oscuro. Estas mediciones
corresponden al build actual, no prueban equivalencia de una configuración de
Eidos diferente ni de breakpoints modificados en runtime.
El smoke de `apps/base` comprueba ahora el primer pintado con JS cliente
bloqueado: exige CSS enlazada y tokens de foundation antes de la hidratación.
Una inyección negativa que bloqueó también la CSS hizo fallar el guard; tras
restaurar exactamente el archivo, el smoke volvió a pasar. Por ello no se
sustituye `index.css` por `index-runtime.css` en esta app: perdería la foundation
del primer pintado. La segunda copia permanece como oportunidad de optimización
separada, condicionada a conservar configuración, cascade y cambios dinámicos.
El [ledger de cierre](./LEDGER-cierre-2026-09.md) es la fuente vigente del estado de
trabajos anteriores; los handoffs congelados sirven de procedencia. Este plan
ordena la auditoría, **no crea otro ledger ni considera cerradas sus filas**.

Loading…
Cancel
Save

Powered by TurnKey Linux.