Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/shot_pptw.mjs

5.9 KiB, 1 run

created by r2519314175:173, 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// dev/shot_pptw.mjs — look at the three-part PPTW, at every state, in the
2// places it is actually drawn.
3//
4// Not a verifier: an EYE. `verify_pausewidget` proves the arithmetic; this
5// proves the thing is legible — that three controls fit a 220px rail tile, that
6// a disabled verb reads as unavailable rather than as missing, and that the
7// light is distinguishable from the buttons beside it at the size it ships at.
8//
9// node dev/shot_pptw.mjs
10//
11// Needs a world: `bash dev/world.sh 0 --up`. No gateway, no model.
12
13import fs from 'node:fs';
14import os from 'node:os';
15import path from 'node:path';
16import { open, newChat } from './harness.mjs';
17
18const OUT = path.join(os.homedir(), '.cache/daimond/pptw-shots');
19fs.rmSync(OUT, { recursive: true, force: true });
20fs.mkdirSync(OUT, { recursive: true });
21
22const s = await open({ name: 'pptw' });
23const p = s.page;
24
25await newChat(s);
26// Two Diamonds, so a branch can be made genuinely mixed.
27async function newDiamond(name) {
28 await p.click('#new-diamond-btn', { force: true });
29 await p.waitForSelector('.dlg-input', { timeout: 10000 });
30 await p.fill('.dlg-input', name);
31 await p.click('.dlg-ok', { force: true });
32 await p.waitForTimeout(700);
33}
34await newDiamond('Alpha');
35await newDiamond('Beta');
36{
37 const drawer = p.locator('#admin-close');
38 if (await drawer.isVisible().catch(() => false)) { await drawer.click({ force: true }); await p.waitForTimeout(300); }
39}
40
41/// Crop tight on one element, at 8x, so the ink can be judged rather than
42/// guessed at.
43async function crop(sel, label, pad = 6) {
44 const el = await p.$(sel);
45 if (!el) { console.log(`(absent) ${label} — ${sel}`); return; }
46 const b = await el.boundingBox();
47 if (!b) { console.log(`(no box) ${label}`); return; }
48 await p.screenshot({
49 path: path.join(OUT, `${label}.png`),
50 clip: { x: b.x - pad, y: b.y - pad, width: b.width + pad * 2, height: b.height + pad * 2 },
51 scale: 'css',
52 });
53 console.log(`${label} ${Math.round(b.width)}x${Math.round(b.height)} at ${Math.round(b.x)},${Math.round(b.y)}`);
54}
55
56/// What the page says about one control, read from the DOM rather than from a
57/// picture: which verb is live, what each is called, and what the light says.
58async function readControl(sel) {
59 return p.evaluate((q) => {
60 const g = document.querySelector(q);
61 if (!g) return null;
62 const lamp = g.querySelector('.pptw-lamp');
63 return {
64 node: g.dataset.pauseNode,
65 state: g.dataset.state,
66 lamp: lamp && lamp.getAttribute('aria-label'),
67 lampFocusable: lamp ? lamp.tabIndex >= 0 : null,
68 acts: [...g.querySelectorAll('.pptw-act')].map((b) => ({
69 act: b.dataset.act, disabled: b.disabled, label: b.getAttribute('aria-label'),
70 })),
71 };
72 }, sel);
73}
74
75const GLOBAL = '#pptw-global .pptw';
76const TILE = '.diamond-box .pptw';
77
78for (const [what, setup] of [
79 ['running', () => p.evaluate(() => DaimondPause.set('root', true))],
80 ['paused', () => p.evaluate(() => DaimondPause.set('root', false))],
81 ['mixed', () => p.evaluate(() => {
82 DaimondPause.set('root', true);
83 const b = document.querySelector('.diamond-box .pptw');
84 if (b) DaimondPause.set(b.dataset.pauseNode, false);
85 })],
86]) {
87 await setup();
88 await p.waitForTimeout(250);
89 await crop(GLOBAL, `global-${what}`);
90 await crop(TILE, `tile-${what}`);
91 await crop('.diamond-box', `diamondtile-${what}`, 2);
92 console.log(what, 'global:', JSON.stringify(await readControl(GLOBAL)));
93 console.log(what, 'tile: ', JSON.stringify(await readControl(TILE)));
94}
95
96// The rail whole, and the rail at a phone width, which is where 60px of control
97// beside a name either fits or does not.
98await p.evaluate(() => DaimondPause.set('root', true));
99await p.waitForTimeout(200);
100await crop('.rail', 'rail-1500', 0);
101await p.setViewportSize({ width: 380, height: 780 });
102await p.waitForTimeout(500);
103// The rail is the left drawer behind the hamburger on a phone; the tiles are
104// only visible once it is open, and that is where 60px of control beside a name
105// either fits or does not.
106await p.click('#drawer-btn', { force: true }).catch(() => {});
107await p.waitForTimeout(600);
108await p.screenshot({ path: path.join(OUT, 'phone-380.png') });
109await crop('.diamond-box', 'phone-tile', 2);
110// The warm skin, which is the palette the tile dialog was clipped in.
111await p.evaluate(() => document.documentElement.dataset.skin = 'warm');
112await p.waitForTimeout(300);
113await crop('.diamond-box', 'phone-tile-warm', 2);
114await p.evaluate(() => document.documentElement.dataset.skin = 'sharp');
115console.log('phone shots written');
116
117// The tile dialog's Running section, and the trigger rows below it.
118await p.setViewportSize({ width: 1500, height: 950 });
119await p.waitForTimeout(400);
120await p.click('.diamond-box .tile-cog', { force: true }).catch(() => {});
121await p.waitForTimeout(700);
122await crop('.tile-dlg-pause', 'dlg-running', 4);
123// Add a triggered action so a trigger row has a control on it.
124await p.click('.trig-add button', { force: true }).catch(() => {});
125await p.waitForTimeout(700);
126await crop('.trig-list', 'trig-list', 4);
127await crop('.dlg-card', 'tile-dialog', 2);
128const dlgOpen = await p.evaluate(() => !!document.querySelector('.dlg-card'));
129const modalOver = await p.evaluate(() =>
130 [...document.querySelectorAll('.dlg-card')].filter((c) => c.offsetParent !== null).length);
131console.log('after + : dialogs on screen =', modalOver, '(1 means the editor did NOT open over it)');
132await p.keyboard.press('Escape');
133await p.waitForTimeout(400);
134
135// Does anything overflow its row?
136const over = await p.evaluate(() => {
137 const out = [];
138 for (const g of document.querySelectorAll('.pptw')) {
139 const par = g.parentElement;
140 if (!par) continue;
141 const a = g.getBoundingClientRect(), b = par.getBoundingClientRect();
142 if (a.right > b.right + 0.5 || a.left < b.left - 0.5) {
143 out.push({ node: g.dataset.pauseNode, parent: par.className, over: Math.round(a.right - b.right) });
144 }
145 }
146 return out;
147});
148console.log('overflow:', JSON.stringify(over));
149console.log('page errors:', s.errs.length, s.errs.slice(0, 5));
150console.log('shots in', OUT);
151await s.close();