You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/scripts/__plane-open.mjs

122 lines
4.9 KiB

feat(theming)!: FIRMA §12.9 — el plano de profundidad es el SUELO, no el techo El plano `data-depth` pinta el bundle de elevación (superficie · borde · sombra · tipografía on-surface) y hasta hoy lo hacía a (0,1,0), la misma especificidad que la receta del componente. La Decisión 8 daba por bueno ese empate porque «las recetas cargan después de la fundación y ganan por ORDEN DE CASCADA». ESA PREMISA ERA FALSA La fundación la inyecta ActiveEidos en runtime como <style> gestionado, y las recetas llegan como chunks code-split de Vite: a igual peso ganaba quien cargara el último, y se midió AL REVÉS en dev que en producción (§13). No era una decisión de diseño, era una moneda al aire — y la moneda cayó del lado del plano. LO QUE COSTABA, MEDIDO Un toast `risk` y uno `fulfill` vestían la MISMA TARJETA GRIS: los seis tonos idénticos y la franja de acento —el rasgo que identifica la intención de un vistazo— reducida a 0,67 px de gris neutro, porque el atajo `border` del plano pisaba el longhand `border-inline-start` en los cuatro lados. Las TRES variantes de tooltip computaban lo mismo: `outline` indistinguible de `solid`, `ghost` un backdrop-filter invisible tras una superficie opaca. Y ~35 claves públicas quedaban adjudicadas como mudas en cinco componentes, con tres recetas que habían RETIRADO su tipografía por esto. LA REGLA La regla de apariencia del plano se emite envuelta en `:where(...)` — especificidad CERO. La receta gana donde el componente HABLA, en cualquier orden de carga; el plano sigue pintando todo lo que el componente CALLA, que es exactamente lo que significa «baseline». Vale igual para un plano que añada un tema (jaula abierta). EL FROST NO BAJA, Y ES DELIBERADO `[data-depth='{plane}'][data-frost]` conserva sus (0,2,0). El baseline es un suelo que la receta puede pisar; el frost es una petición explícita por elemento —alguien escribió `data-frost`— y honrarla significa ganarle al fondo propio del componente. Mismo atributo, intención opuesta: queda comentado en el emisor para que nadie los «armonice», y el guard lo fija por los dos lados. QUÉ MUEVE DE PÍXEL, ENTERO De 19 adoptantes, DIECISÉIS con diff CERO sobre ~49.000 valores computados. Los tres que se mueven son la firma haciendo su trabajo: - toast: 35 diffs (7 fondos + 7 bordes de tono + 21 anchuras). Cuatro tonos medidos en la demo real: 4 de 4 DISTINTOS donde antes 3 de 3 eran idénticos, y la franja de acento pasa de 0,67 px de gris a 3 px del color de la intención. - tooltip: sus tres variantes vuelven a distinguirse — solid opaco con sombra, outline transparente con borde, ghost translúcido al 70 %. - popover, tooltip y link-preview: `line-height` de 1.25 (el `--leading-ui` del plano) al que cada receta pide. Es EXACTAMENTE la decisión que la receta de link-preview tenía escrita desde el 2026-08-21: «se quedan declaradas A PROPÓSITO; retirarlas arreglaría el empate a favor del plano — una decisión de píxel que pertenece a la firma pendiente de §12.9». `font-family` no se movió en ninguno: las recetas piden el mismo `--style-label-font-family` que el plano pinta, así que la seguridad tipográfica del portal (Decisión 8) queda intacta. Refutada una hipótesis propia: temía que el atajo `background` del plano estuviera matando el velo de hover del sistema y que bajarlo lo resucitara moviendo píxel. `backgroundImage` no cambia en ninguno de los 19 — el velo vive en los ítems, no en la superficie. EL LEDGER 17 claves salieron STALE solas y se retiran: toast (3), tooltip (5), float-panel (5), link-preview (2), popover (2). Los cinco vuelven verdes — toast pasa de 51/85 a 72/85. El ledger sigue en 87 bloques, sin duplicados, y parsea. DOS VECES QUE EL INSTRUMENTO MINTIÓ La sonda compartida mide UN nodo en dialog, drawer, popover, tooltip, context-menu, dropdown-menu y link-preview: nunca abre la superficie, que es justo donde el plano pinta, así que sus «0 diffs» no probaban nada. La medición buena reproduce el «antes» EN RUNTIME, inyectando la regla vieja a (0,1,0) al final del head en vez de revertir el fichero — entra como `scripts/__plane-open.mjs`. Y el primer barrido del ledger dio 87 de 87 rojos: «todos fallan igual» es la firma del instrumento, y era la misma trampa del CR de Python en Windows que ya está registrada. Re-corrido con la lista limpia. EL BARRIDO DEL LEDGER ENTERO 87 de 87 componentes con entrada en el ledger, re-corridos tras el flip: cero NO EFFECT sin adjudicar, cero STALE, cero errores. El cambio es global y se verificó globalmente. GUARDS vitest src/uix/eidos 439/440 (el rojo conocido skin-media-player) · active-eidos-config 75/75 · npm run check COMPLETED con los 72 errores preexistentes de la rama y ninguno en ficheros de este cambio · rtl:check 0 · docs:check 0 · eidos-lint 0 invalid en los cinco tocados · capturas de las tres superficies que se mueven. Un test hermano afirmaba el selector desnudo para un plano añadido por un tema: ahora afirma `:where()`, porque un plano de tema es un suelo igual que los cinco de fábrica. Doctrina en docs/theming/changelog.md §29 (junto a la Decisión 8 que corrige), cierre del expediente en next-features §13 y handoff al día. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
/**
* §12.9 — measure the OPENED surface of every portaled adopter, before vs after.
*
* The shared probe measures 1 node on these components: it never opens them, so a
* "0 diffs" there proves nothing about the plane, which paints exactly the surface
* the probe cannot see (the 6.bis law: count the nodes before believing anything).
*
* The "before" is reproduced AT RUNTIME instead of reverting the file: inject the
* plane's appearance rules at their OLD specificity (0,1,0), appended last so they
* win by order — which is what they did in dev, measured. Whatever moves between
* the two reads is exactly what the `:where()` flip changed on that surface.
*/
import { chromium } from 'playwright';
const OLD_PLANE = `
[data-depth='raised'] { background: var(--depth-raised-surface); font-family: var(--style-label-font-family); line-height: var(--leading-ui); border: var(--border-width) solid var(--depth-raised-border); box-shadow: var(--depth-raised-shadow), var(--depth-raised-halo); }
[data-depth='overlay'] { background: var(--depth-overlay-surface); font-family: var(--style-label-font-family); line-height: var(--leading-ui); border: var(--border-width) solid var(--depth-overlay-border); box-shadow: var(--depth-overlay-shadow), var(--depth-overlay-halo); }
[data-depth='modal'] { background: var(--depth-modal-surface); font-family: var(--style-label-font-family); line-height: var(--leading-ui); border: var(--border-width) solid var(--depth-modal-border); box-shadow: var(--depth-modal-shadow), var(--depth-modal-halo); }
`;
const PROPS = [
'backgroundColor',
'backgroundImage',
'borderTopColor',
'borderTopWidth',
'borderInlineStartColor',
'borderInlineStartWidth',
'boxShadow',
'fontFamily',
'lineHeight',
'color',
'borderRadius'
];
/** component → [how to open, surface selector]. `hover` for the ones that open on pointer. */
const CASES = [
['dialog', 'click', '[data-dialog-content]'],
['drawer', 'click', '[data-drawer-content]'],
['dropdown-menu', 'click', '[data-dropdown-menu-content]'],
['popover', 'click', '[data-popover-content]'],
['select', 'click', '[data-select-content]'],
['combobox', 'click', '[data-combobox-content]'],
['menubar', 'click', '[data-menubar-content]'],
['tooltip', 'hover', '[data-tooltip-content]'],
['link-preview', 'hover', '[data-link-preview-content]'],
['navigation-menu', 'hover', '[data-navigation-menu-content]'],
['context-menu', 'context', '[data-context-menu-content]']
];
const browser = await chromium.launch();
const report = [];
for (const [comp, how, surface] of CASES) {
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
const url = `http://localhost:5180/uix/components/${comp}`;
try {
try {
await page.goto(url, { waitUntil: 'networkidle', timeout: 25000 });
} catch {
await page.goto(url, { waitUntil: 'load', timeout: 25000 });
}
await page.waitForTimeout(1500);
await page.addStyleTag({
content: '*, *::before, *::after { transition: none !important; animation: none !important; }'
});
const trigger = page.locator(`[data-${comp}-trigger]`).first();
if (!(await trigger.count())) {
report.push({ comp, err: 'no trigger' });
await page.close();
continue;
}
if (how === 'click') await trigger.click({ timeout: 5000 }).catch(() => {});
else if (how === 'hover') await trigger.hover({ timeout: 5000 }).catch(() => {});
else await trigger.click({ button: 'right', timeout: 5000 }).catch(() => {});
await page.waitForTimeout(900);
const res = await page.evaluate(
({ surface, PROPS, OLD_PLANE }) => {
const nodes = [...document.querySelectorAll(surface)];
if (!nodes.length) return { err: 'surface not mounted' };
const snap = () =>
nodes.map((n) => {
const cs = getComputedStyle(n);
const o = {};
for (const p of PROPS) o[p] = cs[p];
return o;
});
const after = snap(); // current code = :where()
const st = document.createElement('style');
st.textContent = OLD_PLANE; // reproduce the old (0,1,0), appended last
document.head.append(st);
const before = snap();
st.remove();
const diffs = [];
after.forEach((a, i) => {
for (const p of PROPS)
if (a[p] !== before[i][p]) diffs.push({ node: i, prop: p, antes: before[i][p], despues: a[p] });
});
return { nodes: nodes.length, depth: nodes[0].getAttribute('data-depth'), diffs };
},
{ surface, PROPS, OLD_PLANE }
);
report.push({ comp, ...res });
} catch (e) {
report.push({ comp, err: String(e).slice(0, 80) });
}
await page.close();
}
await browser.close();
for (const r of report) {
if (r.err) {
console.log(`${r.comp.padEnd(18)} ⚠ ${r.err}`);
continue;
}
const head = `${r.comp.padEnd(18)} nodos=${r.nodes} depth=${r.depth} diffs=${r.diffs.length}`;
console.log(head);
for (const d of r.diffs.slice(0, 8))
console.log(` ${d.prop}: «${String(d.antes).slice(0, 46)}» → «${String(d.despues).slice(0, 46)}»`);
}

Powered by TurnKey Linux.