Oregami
Repositories/oxedyne/daimond

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).
12import fs from 'node:fs';
13import path from 'node:path';
14import { open, signInAs, newChat, chat, scratch } from './harness.mjs';
15
16const OUT = process.argv[2] || scratch('phasea');
17fs.mkdirSync(OUT, { recursive: true });
18
19const shots = [];
20async 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.
40async 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
68const s = await open({ name: 'phasea', profile: scratch('pw', 'phasea') });
69const { page } = s;
70await page.waitForTimeout(1200);
71
72// ── The rail: the search box is gone ────────────────────────────
73await shot(page, '01-rail-no-search', { sel: '#panel-rail' });
74
75// ── A chat, for the composer and the copy icon ──────────────────
76await newChat(s);
77await page.waitForTimeout(500);
78await chat(s, 'A short question, so the bubble is one line.').catch(() => {});
79await page.waitForTimeout(1200);
80
81await shot(page, '02-composer', { sel: '.chat-input-bar' });
82await zoomShot(page, '03-composer-chevrons-8x', '#chat-jump', 8, 10);
83
84// The copy control only exists under the pointer.
85const userMsg = await page.$('.chat-msg-user');
86if (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 ─────────────────────────────────────
94await page.evaluate(() => { try { DaimondPanels.show('agents'); } catch (e) {} });
95await page.waitForTimeout(700);
96await shot(page, '06-agents-empty', { sel: '#panel-agents' });
97
98// ── The Doc panel header, with its new line-number toggle ───────
99await page.evaluate(() => { try { DaimondPanels.show('doc'); } catch (e) {} });
100await page.waitForTimeout(700);
101await shot(page, '07-doc-panel', { sel: '#panel-doc' });
102await zoomShot(page, '08-doc-header-6x', '#panel-doc .chead', 6, 6);
103
104// ── The rail dialogs the CSS audit could not see ────────────────
105await page.evaluate(() => { try { DaimondPanels.show('rail'); } catch (e) {} });
106await page.waitForTimeout(400);
107for (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
118console.log('\n' + shots.length + ' shots in ' + OUT);
119await s.close();