levkin.ca/scripts/test-stack.mjs
ilia cfe1cf3922 Fix stack cover: single sticky folder unit, hide inactive bodies.
Tested all four variants in browser — only active layer body visible while
tabs stay staggered; scroll covers previous layers correctly.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 23:11:57 -04:00

92 lines
3.6 KiB
JavaScript

import { chromium } from 'playwright';
import { writeFileSync, mkdirSync } from 'fs';
import { join } from 'path';
const BASE = process.env.BASE_URL || 'http://localhost:5176';
const OUT = '/tmp/levkin-stack-test';
mkdirSync(OUT, { recursive: true });
const pages = [
{ name: 'stack', path: '/stack/' },
{ name: 'stack-folder', path: '/stack-folder/' },
{ name: 'stack-trace', path: '/stack-trace/' },
{ name: 'stack-rack', path: '/stack-rack/' },
];
const scrollY = [0, 400, 800, 1200, 1800, 2400];
async function analyze(page, label) {
return page.evaluate(() => {
const sections = [...document.querySelectorAll('.scroll-section')];
const bodies = [...document.querySelectorAll('.body, .layer-inner, .frame-body, .unit-body')];
const tabs = [...document.querySelectorAll('.tab, .frame-line, .unit-head')];
const sticky = [...document.querySelectorAll('.tab, .body, .layer-inner, .frame-line, .frame-body, .unit-head, .unit-body')];
const rects = (els) => els.map((el, i) => {
const r = el.getBoundingClientRect();
const cs = getComputedStyle(el);
return {
i,
tag: el.className?.slice?.(0, 40) || el.tagName,
top: Math.round(r.top),
bottom: Math.round(r.bottom),
height: Math.round(r.height),
position: cs.position,
zIndex: cs.zIndex,
visible: r.height > 0 && r.bottom > 0 && r.top < innerHeight,
};
});
const visibleBodies = rects(bodies).filter((b) => b.visible && b.height > 80);
const mount = document.querySelector('.mount, .stack-mount');
const mountH = mount ? Math.round(mount.getBoundingClientRect().height) : 0;
return {
scrollY: Math.round(window.scrollY),
pageH: Math.round(document.documentElement.scrollHeight),
viewport: innerHeight,
sections: sections.length,
visibleBodyCount: visibleBodies.length,
visibleBodies,
mountDocHeight: mountH,
firstSectionTop: sections[0] ? Math.round(sections[0].getBoundingClientRect().top) : null,
lastSectionBottom: sections.at(-1) ? Math.round(sections.at(-1).getBoundingClientRect().bottom) : null,
stickyPositions: rects(sticky.filter((el) => getComputedStyle(el).position === 'sticky')).slice(0, 14),
};
});
}
const browser = await chromium.launch({ headless: true });
const report = [];
for (const { name, path } of pages) {
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
const url = BASE + path;
await page.goto(url, { waitUntil: 'networkidle' });
const shots = [];
for (const y of scrollY) {
await page.evaluate((sy) => window.scrollTo(0, sy), y);
await page.waitForTimeout(200);
const data = await analyze(page, name);
const file = join(OUT, `${name}-scroll-${y}.png`);
await page.screenshot({ path: file, fullPage: false });
shots.push({ y, file, ...data });
}
report.push({ name, url, shots });
await page.close();
}
await browser.close();
const summary = report.map((r) => {
const problems = r.shots.map((s) => {
const issues = [];
if (s.visibleBodyCount > 2) issues.push(`${s.visibleBodyCount} bodies visible (want ≤2)`);
if (s.scrollY === 0 && s.visibleBodyCount > 1) issues.push('top: multiple full bodies');
if (s.pageH > s.viewport * 8) issues.push(`page very tall: ${s.pageH}px`);
return { y: s.y, issues, visibleBodyCount: s.visibleBodyCount, pageH: s.pageH };
});
return { variant: r.name, url: r.url, scrollChecks: problems };
});
writeFileSync(join(OUT, 'report.json'), JSON.stringify({ report, summary }, null, 2));
console.log(JSON.stringify(summary, null, 2));
console.log('\nScreenshots:', OUT);