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>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import { chromium } from 'playwright';
|
||||
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
|
||||
await page.goto('http://localhost:5176/stack-folder/', { waitUntil: 'networkidle' });
|
||||
|
||||
for (const y of [0, 400, 800, 1200]) {
|
||||
await page.evaluate((sy) => window.scrollTo(0, sy), y);
|
||||
await page.waitForTimeout(150);
|
||||
const data = await page.evaluate(() => {
|
||||
return [...document.querySelectorAll('.folder')].map((f, i) => {
|
||||
const cs = getComputedStyle(f);
|
||||
const r = f.getBoundingClientRect();
|
||||
const body = f.querySelector('.body');
|
||||
const br = body.getBoundingClientRect();
|
||||
const sec = f.closest('.scroll-section');
|
||||
const sr = sec.getBoundingClientRect();
|
||||
return {
|
||||
i,
|
||||
folderTop: Math.round(r.top),
|
||||
bodyTop: Math.round(br.top),
|
||||
position: cs.position,
|
||||
top: cs.top,
|
||||
zIndex: f.style.zIndex || cs.zIndex,
|
||||
sectionTop: Math.round(sr.top),
|
||||
sectionBottom: Math.round(sr.bottom),
|
||||
sectionH: Math.round(sr.height),
|
||||
};
|
||||
});
|
||||
});
|
||||
console.log('scroll', y, JSON.stringify(data.filter((d) => d.sectionBottom > 0 && d.sectionTop < 800), null, 2));
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
@@ -0,0 +1,91 @@
|
||||
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);
|
||||
Reference in New Issue
Block a user