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/check-eidos-recipes.ts

259 lines
9.2 KiB

/**
* Verifies eidos recipes render correctly in /uix/components.
*
* 1. Toggle (★) — has data-archetype + focus ring on Tab.
* 2. Switch — has data-archetype + focus ring on Tab.
* 3. Dialog — opens via click, content + overlay visible, persists.
*
* Saves screenshots to /tmp/eidos-*.png so I can inspect.
*/
import { chromium } from 'playwright';
import { mkdir } from 'node:fs/promises';
const OUT = 'g:/tmp/eidos';
async function probePort(): Promise<string | null> {
for (let port = 5173; port <= 5180; port++) {
try {
const r = await fetch(`http://localhost:${port}/`, { signal: AbortSignal.timeout(500) });
if (r.ok || r.status === 404 || r.status === 500) return `http://localhost:${port}`;
} catch {}
}
return null;
}
async function main() {
const base = await probePort();
if (!base) {
console.error('No dev server. Run npm run dev.');
process.exit(2);
}
await mkdir(OUT, { recursive: true });
const browser = await chromium.launch();
const ctx = await browser.newContext({ viewport: { width: 900, height: 1100 } });
const page = await ctx.newPage();
page.on('console', (msg) => {
if (msg.type() === 'error') console.log(`[console error] ${msg.text()}`);
});
// ── 1. Toggle ────────────────────────────────────────────────────────────
await page.goto(`${base}/uix/components/toggle`, { waitUntil: 'networkidle' });
await page.waitForTimeout(300);
const toggle = page.locator('[data-toggle]').first();
const toggleAttrs = await toggle.evaluate((el) => ({
dataToggle: el.getAttribute('data-toggle'),
dataArchetype: el.getAttribute('data-archetype'),
tag: el.tagName.toLowerCase()
}));
console.log('Toggle attrs:', toggleAttrs);
// Click body first so we have a focus origin, then tab into the toggle.
await page.locator('body').click({ position: { x: 5, y: 5 } });
await page.waitForTimeout(50);
// Tab until we hit the toggle button (skip the Soma layout bar if present).
for (let i = 0; i < 10; i++) {
await page.keyboard.press('Tab');
const isToggle = await page.evaluate(
() => document.activeElement?.hasAttribute('data-toggle') ?? false
);
if (isToggle) break;
}
await page.waitForTimeout(100);
const toggleFocusOutline = await toggle.evaluate((el) => {
const cs = getComputedStyle(el);
return {
matchesFocusVisible: el.matches(':focus-visible'),
outlineStyle: cs.outlineStyle,
outlineWidth: cs.outlineWidth,
boxShadow: cs.boxShadow
};
});
console.log('Toggle ring (Tab focused):', toggleFocusOutline);
await page.screenshot({ path: `${OUT}/01-toggle-focused.png`, fullPage: false });
// ── 2. Switch ────────────────────────────────────────────────────────────
await page.goto(`${base}/uix/components/switch`, { waitUntil: 'networkidle' });
await page.waitForTimeout(300);
const sw = page.locator('[data-switch]').first();
const swAttrs = await sw.evaluate((el) => ({
dataSwitch: el.getAttribute('data-switch'),
dataArchetype: el.getAttribute('data-archetype'),
dataState: el.getAttribute('data-state'),
tag: el.tagName.toLowerCase()
}));
console.log('Switch attrs:', swAttrs);
for (let i = 0; i < 5; i++) {
await page.keyboard.press('Tab');
const isSwitch = await page.evaluate(
() => document.activeElement?.hasAttribute('data-switch') ?? false
);
if (isSwitch) break;
}
await page.waitForTimeout(100);
const swFocusOutline = await sw.evaluate((el) => {
const cs = getComputedStyle(el);
return {
matchesFocusVisible: el.matches(':focus-visible'),
boxShadow: cs.boxShadow,
width: cs.width,
height: cs.height,
background: cs.backgroundColor
};
});
console.log('Switch ring (Tab focused):', swFocusOutline);
await page.screenshot({ path: `${OUT}/02-switch-focused.png`, fullPage: false });
// ── 3. Dialog ────────────────────────────────────────────────────────────
await page.goto(`${base}/uix/components/dialog`, { waitUntil: 'networkidle' });
await page.waitForTimeout(300);
const dialogTrigger = page.locator('[data-dialog-trigger]').first();
const triggerExists = (await dialogTrigger.count()) > 0;
console.log('Dialog trigger exists:', triggerExists);
if (triggerExists) {
// Inject a sampler that records opacity at every rAF after the click.
await page.evaluate(`(() => {
window.__samples = [];
const trigger = document.querySelector('[data-dialog-trigger]');
const t0 = performance.now();
trigger.click();
const tick = () => {
const t = performance.now() - t0;
const el = document.querySelector('[data-dialog-content]');
if (el) {
const cs = getComputedStyle(el);
window.__samples.push({
t: Math.round(t),
opacity: parseFloat(cs.opacity),
hasStartingStyle: el.hasAttribute('data-starting-style')
});
}
if (t < 500) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
})()`);
await page.waitForTimeout(550);
const entranceSamples = (await page.evaluate('window.__samples')) as Array<{
t: number;
opacity: number;
hasStartingStyle: boolean;
}>;
const decimated = entranceSamples.filter(
(_, i) => i % 3 === 0 || i === entranceSamples.length - 1
);
console.log('Entrance samples (t-ms, opacity, hasStartingStyle):');
for (const s of decimated) {
console.log(` t=${s.t}ms opacity=${s.opacity.toFixed(3)} starting=${s.hasStartingStyle}`);
}
const dialogContent = page.locator('[data-dialog-content]');
await page.screenshot({ path: `${OUT}/03a-dialog-entrance-mid.png`, fullPage: false });
const dialogOverlay = page.locator('[data-dialog-overlay]');
const overlayCount = await dialogOverlay.count();
const contentCount = await dialogContent.count();
console.log(`Dialog opened — overlay count: ${overlayCount}, content count: ${contentCount}`);
if (contentCount > 0) {
const contentBox = await dialogContent.first().boundingBox();
const overlayBox = await dialogOverlay.first().boundingBox();
const overlayStyles = await dialogOverlay.first().evaluate((el) => {
const cs = getComputedStyle(el);
return {
position: cs.position,
inset: cs.inset,
top: cs.top,
left: cs.left,
right: cs.right,
bottom: cs.bottom,
width: cs.width,
height: cs.height,
background: cs.backgroundColor,
zIndex: cs.zIndex,
display: cs.display,
visibility: cs.visibility,
opacity: cs.opacity,
dataArchetype: el.getAttribute('data-archetype'),
parentTag: el.parentElement?.tagName.toLowerCase()
};
});
const contentStyles = await dialogContent.first().evaluate((el) => {
const cs = getComputedStyle(el);
return {
position: cs.position,
top: cs.top,
left: cs.left,
transform: cs.transform,
background: cs.backgroundColor,
zIndex: cs.zIndex,
display: cs.display,
visibility: cs.visibility,
opacity: cs.opacity
};
});
console.log('Dialog content box:', contentBox);
console.log('Dialog overlay box:', overlayBox);
console.log('Dialog overlay styles:', overlayStyles);
console.log('Dialog content styles:', contentStyles);
}
await page.screenshot({ path: `${OUT}/03-dialog-open.png`, fullPage: false });
await page.evaluate(`(() => {
window.__exitSamples = [];
const closeBtn = document.querySelector('[data-dialog-close]');
const t0 = performance.now();
closeBtn.click();
const tick = () => {
const t = performance.now() - t0;
const el = document.querySelector('[data-dialog-content]');
if (el) {
const cs = getComputedStyle(el);
window.__exitSamples.push({
t: Math.round(t),
opacity: parseFloat(cs.opacity),
hasEndingStyle: el.hasAttribute('data-ending-style'),
lastAction: el.getAttribute('data-last-action'),
borderTopColor: cs.borderTopColor,
mounted: true
});
} else {
window.__exitSamples.push({
t: Math.round(t), opacity: 0, hasEndingStyle: false, lastAction: null,
borderTopColor: '', mounted: false
});
}
if (t < 500) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
})()`);
await page.waitForTimeout(550);
const exitSamples = (await page.evaluate('window.__exitSamples')) as Array<{
t: number;
opacity: number;
hasEndingStyle: boolean;
lastAction: string | null;
borderTopColor: string;
mounted: boolean;
}>;
const exitDecimated = exitSamples.filter((_, i) => i % 3 === 0 || i === exitSamples.length - 1);
console.log('Exit samples (t-ms, opacity, hasEndingStyle, lastAction, borderTop, mounted):');
for (const s of exitDecimated) {
console.log(
` t=${s.t}ms opacity=${s.opacity.toFixed(3)} ending=${s.hasEndingStyle} lastAction=${s.lastAction} border=${s.borderTopColor} mounted=${s.mounted}`
);
}
await page.screenshot({ path: `${OUT}/04-dialog-closed.png`, fullPage: false });
}
await browser.close();
console.log(`\nScreenshots saved to ${OUT}/`);
}
main().catch((e) => {
console.error(e);
process.exit(1);
});

Powered by TurnKey Linux.