mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-28 07:32:52 +00:00
115 lines
4.1 KiB
JavaScript
115 lines
4.1 KiB
JavaScript
// Shoot the workspace in every state that matters, so the layering can be
|
|
// JUDGED rather than asserted. One file per state, named by the state, small
|
|
// JPEGs because the question is "does this read as stacked paper" and that
|
|
// survives compression.
|
|
//
|
|
// node scripts/origami.mjs [out-dir] [filter]
|
|
//
|
|
// The filter is a substring of the shot name — `node scripts/origami.mjs out chat`
|
|
// re-shoots only /chat while iterating.
|
|
|
|
import { chromium } from '@playwright/test'
|
|
import { mkdir } from 'node:fs/promises'
|
|
|
|
const OUT = process.argv[2] ?? '/home/z/Desktop/origami'
|
|
const ONLY = process.argv[3] ?? ''
|
|
const HOST = 'http://localhost:3000'
|
|
|
|
const SIZES = [
|
|
{ name: 'desktop', width: 1440, height: 900 },
|
|
{ name: 'tablet', width: 768, height: 1024 },
|
|
{ name: 'phone', width: 390, height: 844 },
|
|
]
|
|
|
|
const ROUTES = ['/chat', '/dev', '/work']
|
|
const THEMES = ['dark', 'light']
|
|
|
|
/** The drawer button, by its accessible name — the one handle that does not
|
|
* depend on which icon the rail happens to draw, or which ELEMENT it renders
|
|
* (gui gives it `role="button"` on a div, so a `button[…]` selector finds
|
|
* nothing and the open state silently never gets shot). */
|
|
const RAIL = '[aria-label="Open sidebar"]'
|
|
|
|
/** `next dev` recompiles a route on first hit and aborts the navigation that
|
|
* triggered it. That is the dev server working, not the page failing. */
|
|
async function open(page, url) {
|
|
for (let tries = 3; ; tries--) {
|
|
try {
|
|
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 120_000 })
|
|
return
|
|
} catch (error) {
|
|
if (tries === 1) throw error
|
|
await page.waitForTimeout(2500)
|
|
}
|
|
}
|
|
}
|
|
|
|
async function shoot(page, name) {
|
|
if (ONLY && !name.includes(ONLY)) return
|
|
await page.screenshot({ path: `${OUT}/${name}.jpg`, type: 'jpeg', quality: 72 })
|
|
const overflow = await page.evaluate(() => ({
|
|
scroll: document.documentElement.scrollWidth,
|
|
client: document.documentElement.clientWidth,
|
|
}))
|
|
const bad = overflow.scroll > overflow.client + 1
|
|
console.log(`${bad ? 'OVERFLOW' : ' ok '} ${name} ${overflow.scroll}/${overflow.client}`)
|
|
return bad
|
|
}
|
|
|
|
const main = async () => {
|
|
await mkdir(OUT, { recursive: true })
|
|
const browser = await chromium.launch()
|
|
let overflowed = 0
|
|
|
|
for (const theme of THEMES) {
|
|
for (const size of SIZES) {
|
|
const context = await browser.newContext({
|
|
viewport: { width: size.width, height: size.height },
|
|
colorScheme: theme,
|
|
deviceScaleFactor: 1,
|
|
})
|
|
// @hanzo/appearance's head script replays what was last painted before
|
|
// first paint, so the page never renders in the wrong theme and then
|
|
// corrects itself in a screenshot; the preference is what the mount keeps.
|
|
await context.addInitScript((t) => {
|
|
try {
|
|
window.localStorage.setItem('hanzo.appearance', JSON.stringify({ theme: t }))
|
|
window.localStorage.setItem('hanzo.appearance.painted', JSON.stringify({ vars: {}, theme: t }))
|
|
} catch {}
|
|
}, theme)
|
|
|
|
if (ONLY && !`${size.name}-${theme}`.split('-').some((part) => ONLY.includes(part))) {
|
|
await context.close()
|
|
continue
|
|
}
|
|
const page = await context.newPage()
|
|
for (const route of ROUTES) {
|
|
await open(page, `${HOST}${route}`)
|
|
// gui writes its atomic sheet on first render; a shot taken before that
|
|
// is a picture of unstyled HTML and proves nothing.
|
|
await page.waitForTimeout(1400)
|
|
|
|
const slug = route.replace(/\W/g, '') || 'home'
|
|
const stem = `${slug}-${size.name}-${theme}`
|
|
if (await shoot(page, `${stem}-rail-closed`)) overflowed++
|
|
|
|
const toggle = page.locator(RAIL).first()
|
|
if (await toggle.count()) {
|
|
await toggle.click()
|
|
await page.waitForTimeout(500)
|
|
if (await shoot(page, `${stem}-rail-open`)) overflowed++
|
|
await toggle.click()
|
|
await page.waitForTimeout(400)
|
|
}
|
|
}
|
|
await context.close()
|
|
}
|
|
}
|
|
|
|
await browser.close()
|
|
console.log(overflowed ? `\n${overflowed} state(s) scroll sideways` : '\nno state scrolls sideways')
|
|
process.exit(overflowed ? 1 : 0)
|
|
}
|
|
|
|
main()
|