oxedyne/daimond/dev/shot_phasea.mjs
5.1 KiB, 1 run
created by r2519314175:171, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | // shot_phasea.mjs — photograph every surface phase A touched, so it can be LOOKED at. |
| 2 | // |
| 3 | // Not a verifier: it asserts nothing. It drives the app to each changed surface, |
| 4 | // takes a full-panel shot and a magnified crop of the ink that matters, and |
| 5 | // prints where each one landed. The judging is done by a person (or a model with |
| 6 | // eyes) reading the files. |
| 7 | // |
| 8 | // node dev/shot_phasea.mjs [outdir] |
| 9 | // |
| 10 | // Needs dev/serve.mjs (DAIMOND_PORT, default 8777) and dev/mockllm.mjs |
| 11 | // (DAIMOND_MOCK_PORT, default 9099). |
| 12 | import fs from 'node:fs'; |
| 13 | import path from 'node:path'; |
| 14 | import { open, signInAs, newChat, chat, scratch } from './harness.mjs'; |
| 15 | |
| 16 | const OUT = process.argv[2] || scratch('phasea'); |
| 17 | fs.mkdirSync(OUT, { recursive: true }); |
| 18 | |
| 19 | const shots = []; |
| 20 | async function shot(page, label, opts = {}) { |
| 21 | const p = path.join(OUT, label + '.png'); |
| 22 | try { |
| 23 | if (opts.sel) { |
| 24 | const el = await page.$(opts.sel); |
| 25 | if (!el) { console.log(' MISS ' + label + ' — no ' + opts.sel); return; } |
| 26 | await el.screenshot({ path: p, timeout: 8000 }); |
| 27 | } else if (opts.clip) { |
| 28 | await page.screenshot({ path: p, clip: opts.clip, timeout: 8000 }); |
| 29 | } else { |
| 30 | await page.screenshot({ path: p, fullPage: false, timeout: 8000 }); |
| 31 | } |
| 32 | shots.push(p); |
| 33 | console.log(' shot ' + label); |
| 34 | } catch (e) { console.log(' MISS ' + label + ' — ' + (e.message || e).split('\n')[0]); } |
| 35 | } |
| 36 | |
| 37 | /// A magnified crop around one element, so the INK can be judged rather than the box. |
| 38 | /// Playwright cannot zoom a screenshot, so the page itself is zoomed and the element |
| 39 | /// re-measured at that scale. |
| 40 | async function zoomShot(page, label, sel, factor = 4, pad = 14) { |
| 41 | const found = await page.$(sel); |
| 42 | if (!found) { console.log(' MISS ' + label + ' — no ' + sel); return; } |
| 43 | // Zoom the page, then bring the element back into view before photographing it: |
| 44 | // a clip computed against the unzoomed layout lands outside the viewport, which |
| 45 | // is how the first version of this file produced nothing but MISS. |
| 46 | await page.evaluate((f) => { document.body.style.zoom = String(f); }, factor); |
| 47 | await page.waitForTimeout(250); |
| 48 | await page.evaluate((q) => { |
| 49 | const el = document.querySelector(q); |
| 50 | if (el) el.scrollIntoView({ block: 'center', inline: 'center' }); |
| 51 | }, sel); |
| 52 | await page.waitForTimeout(250); |
| 53 | const el = await page.$(sel); |
| 54 | const box = el ? await el.boundingBox().catch(() => null) : null; |
| 55 | if (box) { |
| 56 | const vw = page.viewportSize(); |
| 57 | await shot(page, label, { clip: { |
| 58 | x: Math.max(0, box.x - pad), |
| 59 | y: Math.max(0, box.y - pad), |
| 60 | width: Math.min(box.width + pad * 2, vw.width - Math.max(0, box.x - pad)), |
| 61 | height: Math.min(box.height + pad * 2, vw.height - Math.max(0, box.y - pad)), |
| 62 | } }); |
| 63 | } |
| 64 | await page.evaluate(() => { document.body.style.zoom = ''; }); |
| 65 | await page.waitForTimeout(200); |
| 66 | } |
| 67 | |
| 68 | const s = await open({ name: 'phasea', profile: scratch('pw', 'phasea') }); |
| 69 | const { page } = s; |
| 70 | await page.waitForTimeout(1200); |
| 71 | |
| 72 | // ── The rail: the search box is gone ──────────────────────────── |
| 73 | await shot(page, '01-rail-no-search', { sel: '#panel-rail' }); |
| 74 | |
| 75 | // ── A chat, for the composer and the copy icon ────────────────── |
| 76 | await newChat(s); |
| 77 | await page.waitForTimeout(500); |
| 78 | await chat(s, 'A short question, so the bubble is one line.').catch(() => {}); |
| 79 | await page.waitForTimeout(1200); |
| 80 | |
| 81 | await shot(page, '02-composer', { sel: '.chat-input-bar' }); |
| 82 | await zoomShot(page, '03-composer-chevrons-8x', '#chat-jump', 8, 10); |
| 83 | |
| 84 | // The copy control only exists under the pointer. |
| 85 | const userMsg = await page.$('.chat-msg-user'); |
| 86 | if (userMsg) { |
| 87 | await userMsg.hover(); |
| 88 | await page.waitForTimeout(400); |
| 89 | await shot(page, '04-copy-hover', { sel: '.chat-msg-user' }); |
| 90 | await zoomShot(page, '05-copy-gap-6x', '.chat-msg-user .msg-copy', 6, 16); |
| 91 | } |
| 92 | |
| 93 | // ── The Agents panel, empty ───────────────────────────────────── |
| 94 | await page.evaluate(() => { try { DaimondPanels.show('agents'); } catch (e) {} }); |
| 95 | await page.waitForTimeout(700); |
| 96 | await shot(page, '06-agents-empty', { sel: '#panel-agents' }); |
| 97 | |
| 98 | // ── The Doc panel header, with its new line-number toggle ─────── |
| 99 | await page.evaluate(() => { try { DaimondPanels.show('doc'); } catch (e) {} }); |
| 100 | await page.waitForTimeout(700); |
| 101 | await shot(page, '07-doc-panel', { sel: '#panel-doc' }); |
| 102 | await zoomShot(page, '08-doc-header-6x', '#panel-doc .chead', 6, 6); |
| 103 | |
| 104 | // ── The rail dialogs the CSS audit could not see ──────────────── |
| 105 | await page.evaluate(() => { try { DaimondPanels.show('rail'); } catch (e) {} }); |
| 106 | await page.waitForTimeout(400); |
| 107 | for (const [label, act] of [ |
| 108 | ['09-admin-home', () => document.getElementById('user-row')?.click()], |
| 109 | ['10-admin-models', () => document.getElementById('astat-model')?.click()], |
| 110 | ['11-admin-credits', () => document.getElementById('astat-account')?.click()], |
| 111 | ]) { |
| 112 | await page.evaluate(act).catch(() => {}); |
| 113 | await page.waitForTimeout(700); |
| 114 | await shot(page, label, { sel: '#admin-drawer, #settings-modal, .admin-body' }); |
| 115 | await shot(page, label + '-full'); |
| 116 | } |
| 117 | |
| 118 | console.log('\n' + shots.length + ' shots in ' + OUT); |
| 119 | await s.close(); |