aboutsummaryrefslogtreecommitdiff
path: root/tests/gallery-probes/probe.mjs
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-18 10:50:18 -0500
committerCraig Jennings <c@cjennings.net>2026-07-18 10:50:18 -0500
commitefd2e551dd727153463d22bd91d5cbe5484e7c3f (patch)
tree919ae62ef4cb4982baf521649179c3fcb0037354 /tests/gallery-probes/probe.mjs
parentb0db5e842d93564b1cb2e4b142ba0a0de1843fbd (diff)
downloadarchsetup-efd2e551dd727153463d22bd91d5cbe5484e7c3f.tar.gz
archsetup-efd2e551dd727153463d22bd91d5cbe5484e7c3f.zip
refactor(gallery): rename GW namespace to DUPRE, kit to The Dupre Kit
The gallery and its library are now The Dupre Kit, named for the dupre color theme. GW was too opaque to remember. The stylesheet id is now dupre-css, the card handle property is .dupre, and page prose says instrument instead of widget. File names stay put. Renaming them is a separate decision. Historical log entries in todo.org keep their GW references as a record.
Diffstat (limited to 'tests/gallery-probes/probe.mjs')
-rw-r--r--tests/gallery-probes/probe.mjs108
1 files changed, 54 insertions, 54 deletions
diff --git a/tests/gallery-probes/probe.mjs b/tests/gallery-probes/probe.mjs
index e394316..d9ba22f 100644
--- a/tests/gallery-probes/probe.mjs
+++ b/tests/gallery-probes/probe.mjs
@@ -1,4 +1,4 @@
-// CDP probe for the widget gallery — no playwright, Node global WebSocket/fetch.
+// CDP probe for the instrument gallery — no playwright, Node global WebSocket/fetch.
// Usage: node probe.mjs [--shot out.png] [--size N]
import { spawn } from 'node:child_process';
import { writeFileSync } from 'node:fs';
@@ -65,7 +65,7 @@ try {
const errs = events.filter(e => e.method === 'Runtime.exceptionThrown');
ok('no exceptions on load', errs.length === 0, errs.map(e => e.params.exceptionDetails?.exception?.description).join('; ').slice(0, 200));
- // 1. card count (the widgets/row default is checked in 1h)
+ // 1. card count (the instruments/row default is checked in 1h)
const cards = await evl(`document.querySelectorAll('.card').length`);
ok('111 cards', cards === 111, `got ${cards}`);
@@ -89,7 +89,7 @@ try {
const chips = [...document.querySelectorAll('#card-07 .fc')].map(c => c.title);
const missing = want.filter(w => !chips.includes(w));
if (missing.length) return 'card 07 missing accents: ' + missing.join(' ');
- const built = Object.keys(GW.accentStyles('--x'));
+ const built = Object.keys(DUPRE.accentStyles('--x'));
return JSON.stringify(built) === JSON.stringify(want) ? 'ok' : 'family is ' + JSON.stringify(built);
})()`);
ok('card 07 offers the shared accent family', accents === 'ok', accents);
@@ -107,7 +107,7 @@ try {
})()`);
ok('card 07 defaults to gold and recolours on click', chipInk === 'ok', chipInk);
- // 1e. The colour-policy backbone. Every widget's colour is either free (the
+ // 1e. The colour-policy backbone. Every instrument's colour is either free (the
// consumer picks) or locked for a stated reason, and the colour pass kept
// finding cards where "is this one a standard?" was answered from memory and
// wrong. So policy is declared on the builder and checked here. The invariant
@@ -116,10 +116,10 @@ try {
// an accent card can't forget to say so. Coverage grows card by card; this
// round classifies the touched set, and the count is reported not gated.
const policy = await evl(`(()=>{
- const KINDS = GW.POLICIES;
+ const KINDS = DUPRE.POLICIES;
if (!KINDS) return 'no POLICIES vocabulary';
const valid = new Set(Object.keys(KINDS));
- const fns = Object.entries(GW).filter(([,v]) => typeof v === 'function');
+ const fns = Object.entries(DUPRE).filter(([,v]) => typeof v === 'function');
const declared = fns.filter(([,v]) => v.POLICY);
// each POLICY record is complete: a valid kind, a why, an authentic range
const shape = declared.filter(([,v]) => !valid.has(v.POLICY.kind) || !v.POLICY.why || !v.POLICY.authentic)
@@ -177,10 +177,10 @@ try {
})()`);
ok('colour policy tally sums to the card total', ptally.startsWith('ok'), ptally);
- // 1h. widgets/row: the control sets an explicit column count and the cards
+ // 1h. instruments/row: the control sets an explicit column count and the cards
// resize to match. One-per-row cards must be much wider than four-per-row,
// and the count actually reaches the grid.
- ok('default is 3 widgets/row', await evl(`document.body.dataset.cols`) === '3');
+ ok('default is 3 instruments/row', await evl(`document.body.dataset.cols`) === '3');
await evl(`document.querySelector('.szbar .key[data-cols="1"]').click()`);
const w1col = await evl(`document.querySelector('.card').getBoundingClientRect().width`);
const cols1 = await evl(`getComputedStyle(document.querySelector('.grid')).gridTemplateColumns.split(' ').length`);
@@ -189,7 +189,7 @@ try {
const cols4 = await evl(`getComputedStyle(document.querySelector('.grid')).gridTemplateColumns.split(' ').length`);
ok('1/row is much wider than 4/row', w1col > w4col * 2.5, `w1=${Math.round(w1col)} w4=${Math.round(w4col)}`);
ok('the grid renders the chosen column count', cols1 === 1 && cols4 === 4, `1->${cols1} 4->${cols4}`);
- ok('widgets/row chip flips state', await evl(`document.body.dataset.cols`) === '4');
+ ok('instruments/row chip flips state', await evl(`document.body.dataset.cols`) === '4');
// 3. behavioral, zoomed: fader drag on card 03 changes readout. Run at 2/row,
// not 1/row: at one-per-row the card is wide enough that its controls fall
@@ -245,7 +245,7 @@ try {
// disengaged state instead. dim offText lets an off toggle recede in a dense
// panel. Craig's call, 2026-07-16.
const atoms = await evl(`(()=>{
- const S = GW.slideToggle.STYLES;
+ const S = DUPRE.slideToggle.STYLES;
const want = { on: ['amber','green','dark','red','warn'], onText: ['panel','cream','green'],
off: ['dark','red'], offText: ['white','red','black','dim'],
thumb: ['light','dark','chrome','brass'] };
@@ -260,9 +260,9 @@ try {
// must resolve in STYLES — a typo here would silently fall through to the
// stylesheet default and look "nearly right", which is the worst outcome.
const presets = await evl(`(()=>{
- const P = GW.slideToggle.PRESETS, S = GW.slideToggle.STYLES;
+ const P = DUPRE.slideToggle.PRESETS, S = DUPRE.slideToggle.STYLES;
if (!P) return 'no PRESETS';
- const AX = GW.slideToggle.AXIS_ORDER;
+ const AX = DUPRE.slideToggle.AXIS_ORDER;
const bad = [];
for (const [name, p] of Object.entries(P)) {
for (const ax of AX) {
@@ -279,7 +279,7 @@ try {
// the only thing carrying state. Both backgrounds must stay dark while the
// inks diverge — if either pill lights, the preset has lost its point.
const dark = await evl(`(()=>{
- const P = GW.slideToggle.PRESETS.dark, S = GW.slideToggle.STYLES;
+ const P = DUPRE.slideToggle.PRESETS.dark, S = DUPRE.slideToggle.STYLES;
const onBg = S.on[P.on].vars['--sw-on-bg'], offBg = S.off[P.off].vars['--sw-off-bg'];
const onInk = S.onText[P.onText].vars['--sw-on-ink'], offInk = S.offText[P.offText].vars['--sw-off-ink'];
if (onBg !== offBg) return 'pills differ: on=' + onBg + ' off=' + offBg;
@@ -288,7 +288,7 @@ try {
})()`);
ok('dark preset keeps both pills dark and the inks distinct', dark === 'ok', dark);
- // 7b. the card claims the preset it is actually in. The widget defaults to
+ // 7b. the card claims the preset it is actually in. The instrument defaults to
// `panel`, so a preset group with nothing lit would assert "no preset
// active" — false, and exactly the kind of quiet mislabel this walk exists
// to catch. Nothing above this point touches card 01's chips (checks 2-4
@@ -304,8 +304,8 @@ try {
})()`);
ok('card 01 defaults to the panel preset', defaultPreset === 'panel', defaultPreset);
- // 8. a preset chip drives the widget AND re-syncs the axis chips, so the card
- // never shows a combination the widget isn't in.
+ // 8. a preset chip drives the instrument AND re-syncs the axis chips, so the card
+ // never shows a combination the instrument isn't in.
const applied = await evl(`(()=>{
const card = document.getElementById('card-01');
const groups = [...card.querySelectorAll('.fgroup')];
@@ -319,7 +319,7 @@ try {
const brd = card.querySelector('.switch').style.getPropertyValue('--sw-on-brd');
return (lit.length === 1 ? lit[0].title : 'lit=' + lit.length) + '|' + brd;
})()`);
- ok('armed preset drives widget and syncs the on chip', applied === 'red|var(--fail)', applied);
+ ok('armed preset drives instrument and syncs the on chip', applied === 'red|var(--fail)', applied);
// 8b. axes are not independent: onText overrides the ink `on` sets, so changing
// `on` after picking an onText must not silently revert the legend while its
@@ -505,10 +505,10 @@ try {
// 13. KEYS is a declarative TABLE, not a function over a DOM event. The Emacs
// port installs this same table into a keymap — it never sees a keydown —
- // so a function here would force it to re-derive the widget's intent and
+ // so a function here would force it to re-derive the instrument's intent and
// the two bindings would drift. (README, keyboard contract.)
const keysTable = await evl(`(()=>{
- const K = GW.abcKeypad.KEYS;
+ const K = DUPRE.abcKeypad.KEYS;
if (!K) return 'no KEYS';
if (typeof K !== 'object' || Array.isArray(K)) return 'KEYS is not a plain table: ' + typeof K;
const missing = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'].filter(c => K[c] !== c);
@@ -519,7 +519,7 @@ try {
})()`);
ok('R57 KEYS is a declarative table covering the plate', keysTable === 'ok', keysTable);
- // 13b. THE contract's first rule: no document-level listener. A widget that
+ // 13b. THE contract's first rule: no document-level listener. An instrument that
// binds globally types into itself from anywhere on a 110-card page.
// Typing at the body with the card unfocused must do nothing at all.
const unfocused = await evl(`(()=>{
@@ -560,12 +560,12 @@ try {
const seq = ['A','B','SPC','7'];
key('CLR').dispatchEvent(new MouseEvent('click', {bubbles:true}));
seq.forEach(k => key(k).dispatchEvent(new MouseEvent('click', {bubbles:true})));
- const byClick = rd() + '/' + document.getElementById('card-R57').gw.get();
+ const byClick = rd() + '/' + document.getElementById('card-R57').dupre.get();
key('CLR').dispatchEvent(new MouseEvent('click', {bubbles:true}));
pad.focus();
['A','B',' ','7'].forEach(k =>
pad.dispatchEvent(new KeyboardEvent('keydown', { key: k, bubbles: true, cancelable: true })));
- const byKey = rd() + '/' + document.getElementById('card-R57').gw.get();
+ const byKey = rd() + '/' + document.getElementById('card-R57').dupre.get();
return byClick === byKey ? 'ok' : 'drift: click=' + byClick + ' key=' + byKey;
})()`);
ok('R57 click and key land in the same place', drift === 'ok', drift);
@@ -576,7 +576,7 @@ try {
// caller is a hole in exactly the target the table exists for.
const pressGuard = await evl(`(()=>{
const card = document.getElementById('card-R57');
- const h = card.gw;
+ const h = card.dupre;
if (!h || !h.press) return 'no handle';
const key = k => [...card.querySelectorAll('.kp-key')].find(e => e.dataset.k === k);
key('CLR').dispatchEvent(new MouseEvent('click', {bubbles:true}));
@@ -590,7 +590,7 @@ try {
// 13c-4. Every key the table maps must be a real plate action, or the Emacs
// port installs a binding that silently does nothing.
const keysSubset = await evl(`(()=>{
- const bad = Object.entries(GW.abcKeypad.KEYS).filter(([,v]) => !GW.abcKeypad.ACTIONS.has(v));
+ const bad = Object.entries(DUPRE.abcKeypad.KEYS).filter(([,v]) => !DUPRE.abcKeypad.ACTIONS.has(v));
return bad.length ? 'KEYS maps to non-actions: ' + JSON.stringify(bad) : 'ok';
})()`);
ok('R57 every KEYS value is a real plate action', keysSubset === 'ok', keysSubset);
@@ -598,7 +598,7 @@ try {
// 13d. preventDefault is spent only where there is a default worth killing.
// Space scrolls and Backspace navigates back, so those are claimed; Tab is
// how the page is navigable and Escape belongs to the audit stepper, so a
- // widget that swallows either breaks something it cannot see.
+ // instrument that swallows either breaks something it cannot see.
const defaults = await evl(`(()=>{
const pad = document.querySelector('#card-R57 .kp-pad');
pad.focus();
@@ -637,11 +637,11 @@ try {
// click ever prints, the card has silently become a keypad with extra steps.
// Reads the BUFFER, not the card readout: the readout is supposed to change
// while hunting ("stylus over g"), and asserting on it would fail a correct
- // widget for showing the operator where the pointer is.
+ // instrument for showing the operator where the pointer is.
const grammar = await evl(`(()=>{
const cell = c => document.querySelector('#card-R58 .ix-cell[data-c="' + c + '"]');
const lever = document.querySelector('#card-R58 .ix-lever');
- const buf = () => document.getElementById('card-R58').gw.get();
+ const buf = () => document.getElementById('card-R58').dupre.get();
document.querySelector('#card-R58 .ix-clear').dispatchEvent(new MouseEvent('click', {bubbles:true}));
const empty = buf();
['M','i','g'].forEach(c => cell(c).dispatchEvent(new MouseEvent('click', {bubbles:true})));
@@ -674,7 +674,7 @@ try {
cell('s').dispatchEvent(new MouseEvent('click', {bubbles:true}));
lever.dispatchEvent(new MouseEvent('click', {bubbles:true}));
lever.dispatchEvent(new MouseEvent('click', {bubbles:true}));
- return document.getElementById('card-R58').gw.get();
+ return document.getElementById('card-R58').dupre.get();
})()`);
/* Exact, on the buffer. /ss$/ on the readout also matched 'sss', so it passed
even if selecting printed — unable to fail on the one bug this card is about. */
@@ -722,7 +722,7 @@ try {
// redraw. Craig has already said the keys will change; a layout welded into
// the drawing is one that never does.
const layoutData = await evl(`(()=>{
- const L = GW.indexPlate && GW.indexPlate.LAYOUT;
+ const L = DUPRE.indexPlate && DUPRE.indexPlate.LAYOUT;
if (!L) return 'no LAYOUT';
if (!Array.isArray(L) || !L.every(Array.isArray)) return 'LAYOUT is not a grid';
const cells = document.querySelectorAll('#card-R58 .ix-cell').length;
@@ -778,7 +778,7 @@ try {
// jump the stylus across the plate to the second. 14e can't see it — a
// duplicate inflates the drawn count and the declared count equally.
const dupes = await evl(`(()=>{
- const flat = GW.indexPlate.LAYOUT.flat().filter(Boolean);
+ const flat = DUPRE.indexPlate.LAYOUT.flat().filter(Boolean);
const seen = new Set(), dup = new Set();
for (const c of flat) (seen.has(c) ? dup : seen).add(c);
return dup.size ? 'duplicated on the plate: ' + [...dup].join(' ') : 'ok';
@@ -788,7 +788,7 @@ try {
// 14i. press() is the allowlist for R58 too. R57 has this check; without it,
// press('F1') reaching select() unfiltered would ship green.
const ixPress = await evl(`(()=>{
- const h = document.getElementById('card-R58').gw;
+ const h = document.getElementById('card-R58').dupre;
h.press('CLR');
['F1', 'PRINT ', '', 'constructor', 'ZZ'].forEach(k => h.press(k));
return JSON.stringify([h.get(), h.selected()]);
@@ -799,7 +799,7 @@ try {
// relaying the plate can't leave a keybinding pointing at a character the
// plate no longer carries. Enter is the lever.
const ixKeys = await evl(`(()=>{
- const K = GW.indexPlate.KEYS, L = GW.indexPlate.LAYOUT;
+ const K = DUPRE.indexPlate.KEYS, L = DUPRE.indexPlate.LAYOUT;
if (!K) return 'no KEYS';
const chars = L.flat().filter(Boolean);
const missing = chars.filter(c => K[c] !== c);
@@ -816,7 +816,7 @@ try {
// demonstrate is gone.
const ixType = await evl(`(()=>{
const pad = document.querySelector('#card-R58 .ix-pad');
- const h = document.getElementById('card-R58').gw;
+ const h = document.getElementById('card-R58').dupre;
const send = k => pad.dispatchEvent(new KeyboardEvent('keydown', { key: k, bubbles: true, cancelable: true }));
document.querySelector('#card-R58 .ix-clear').dispatchEvent(new MouseEvent('click', {bubbles:true}));
pad.focus();
@@ -834,7 +834,7 @@ try {
// argument for having no shift key.
const ixCase = await evl(`(()=>{
const pad = document.querySelector('#card-R58 .ix-pad');
- const h = document.getElementById('card-R58').gw;
+ const h = document.getElementById('card-R58').dupre;
const send = k => pad.dispatchEvent(new KeyboardEvent('keydown', { key: k, bubbles: true, cancelable: true }));
document.querySelector('#card-R58 .ix-clear').dispatchEvent(new MouseEvent('click', {bubbles:true}));
pad.focus();
@@ -857,12 +857,12 @@ try {
ok('R58 leaves Space alone (not on the plate)', ixSpace === 'ok', ixSpace);
// 15e. Unfocused, it hears nothing — the contract's first rule, on the second
- // widget to take keys.
+ // instrument to take keys.
// Asserts nothing CHANGED, rather than expecting a cleared selection: CLR
// is fresh paper, and fresh paper doesn't move the operator's hand, so the
// stylus legitimately stays where the previous check left it.
const ixBlur = await evl(`(()=>{
- const h = document.getElementById('card-R58').gw;
+ const h = document.getElementById('card-R58').dupre;
document.querySelector('#card-R58 .ix-clear').dispatchEvent(new MouseEvent('click', {bubbles:true}));
document.activeElement.blur();
const before = JSON.stringify([h.get(), h.selected()]);
@@ -877,7 +877,7 @@ try {
// through the declared charset one flap at a time, retarget cleanly, and
// animate:false jumps straight to the target.
const flapApi = await evl(`(() => {
- const h = document.querySelector('#card-N20').gw;
+ const h = document.querySelector('#card-N20').dupre;
return { reading: typeof h.reading, chars: typeof h.chars, set: typeof h.set };
})()`);
ok('N20 handle exposes reading() and chars', flapApi.reading === 'function' && flapApi.chars === 'string',
@@ -889,12 +889,12 @@ try {
// Let the board settle on a known grid first — the demo ticker left it
// anywhere, and every command cascades (there is deliberately no teleport
// path). setText is the deterministic seam; next() is random by design.
- await evl(`document.querySelector('#card-N20').gw.setText('SYSTEM\\nVOLUME\\nSIGNAL')`);
+ await evl(`document.querySelector('#card-N20').dupre.setText('SYSTEM\\nVOLUME\\nSIGNAL')`);
await sleep(6500);
// Start a cascade and sample the displayed reading every 15ms until stable.
await evl(`(() => {
- const h = document.querySelector('#card-N20').gw;
+ const h = document.querySelector('#card-N20').dupre;
window.__n20 = { samples: [], anims: 0 };
h.setText('VOLUME\\nSIGNAL\\nRECORD'); // multi-step distances on several cells
window.__n20.n = 0;
@@ -911,7 +911,7 @@ try {
await sleep(6500);
const n20 = await evl(`(() => {
clearInterval(window.__n20.timer);
- const h = document.querySelector('#card-N20').gw;
+ const h = document.querySelector('#card-N20').dupre;
return { samples: [...new Set(window.__n20.samples)], final: h.reading(), anims: window.__n20.anims,
chars: h.chars };
})()`);
@@ -933,7 +933,7 @@ try {
// Retarget mid-cascade: aim at word 2, then word 3 while still spinning; must land on 3.
await evl(`(() => {
- const h = document.querySelector('#card-N20').gw;
+ const h = document.querySelector('#card-N20').dupre;
window.__n20r = { samples: [] };
window.__n20r.timer = setInterval(() => window.__n20r.samples.push(h.reading()), 15);
h.setText('SIGNAL\\nRECORD\\nONLINE'); setTimeout(() => h.setText('RECORD\\nONLINE\\nSTEREO'), 180);
@@ -941,7 +941,7 @@ try {
await sleep(7000);
const n20b = await evl(`(() => {
clearInterval(window.__n20r.timer);
- const h = document.querySelector('#card-N20').gw;
+ const h = document.querySelector('#card-N20').dupre;
return { final: h.reading().split('\\n')[0], target: 'RECORD',
sawSync: window.__n20r.samples.some(s => s.split('\\n')[0] === 'SIGNAL'),
anims: document.querySelector('#card-N20').getAnimations({ subtree: true }).length };
@@ -955,7 +955,7 @@ try {
const n20s = await evl(`(() => {
const el = document.createElement('div'); document.body.appendChild(el);
const pool = ['SYSTEM','VOLUME','SIGNAL','RECORD','ONLINE','STEREO','FILTER','OUTPUT'];
- const h = GW.splitFlap(el, { rows: 3, cells: 6, words: pool, animate: false });
+ const h = DUPRE.splitFlap(el, { rows: 3, cells: 6, words: pool, animate: false });
const grids = [h.reading().split('\\n')];
h.next(); grids.push(h.reading().split('\\n'));
h.next(); grids.push(h.reading().split('\\n'));
@@ -975,7 +975,7 @@ try {
const n20c = await evl(`(() => {
const el = document.createElement('div'); document.body.appendChild(el);
const settles = [];
- const h = GW.splitFlap(el, { animate: false, onSettle: r => settles.push(r) });
+ const h = DUPRE.splitFlap(el, { animate: false, onSettle: r => settles.push(r) });
h.set(1);
const r = h.reading(); el.remove();
return { r, settles };
@@ -986,7 +986,7 @@ try {
// The card is a three-row six-letter board; the demo draws random pool words.
const n20d = await evl(`(() => {
- const h = document.querySelector('#card-N20').gw;
+ const h = document.querySelector('#card-N20').dupre;
return { cells: document.querySelectorAll('#card-N20 .flapd').length,
lines: document.querySelectorAll('#card-N20 .flapline').length,
readLines: h.reading().split('\\n').length };
@@ -997,9 +997,9 @@ try {
// Skin axis: dark (cream on black) and the inverse light (black on cream),
// switched by the chip rig via setStyle, computed ink actually inverting.
const n20e = await evl(`(() => {
- const h = document.querySelector('#card-N20').gw;
- if (typeof h.setStyle !== 'function' || !GW.splitFlap.STYLES || !GW.splitFlap.STYLES.skin) return 'no skin axis';
- const skins = Object.keys(GW.splitFlap.STYLES.skin);
+ const h = document.querySelector('#card-N20').dupre;
+ if (typeof h.setStyle !== 'function' || !DUPRE.splitFlap.STYLES || !DUPRE.splitFlap.STYLES.skin) return 'no skin axis';
+ const skins = Object.keys(DUPRE.splitFlap.STYLES.skin);
if (skins[0] !== 'paper') return 'paper must list first, got: ' + skins.join(',');
if (!['dark','white','light','paper'].every(k => skins.includes(k))) return 'skins are: ' + skins.join(',');
const ink = () => getComputedStyle(document.querySelector('#card-N20 .fh')).color;
@@ -1025,8 +1025,8 @@ try {
ok('N20 skins: paper default first, white ink, cream, ivory — all switch and restore', n20e === 'ok', n20e);
const n20fnt = await evl(`(() => {
- const h = document.querySelector('#card-N20').gw;
- if (!GW.splitFlap.STYLES.font) return 'no font axis';
+ const h = document.querySelector('#card-N20').dupre;
+ if (!DUPRE.splitFlap.STYLES.font) return 'no font axis';
const fam = () => getComputedStyle(document.querySelector('#card-N20 .fh')).fontFamily;
const helv = fam(); // the card's default face is Helvetica
if (!/helvetica/i.test(helv)) return 'default face is not helvetica: ' + helv;
@@ -1056,7 +1056,7 @@ try {
// Flap speed: the handle takes setFlapMs and the card's slider drives it.
const n20v = await evl(`(() => {
- const h = document.querySelector('#card-N20').gw;
+ const h = document.querySelector('#card-N20').dupre;
if (typeof h.setFlapMs !== 'function' || typeof h.flapMs !== 'function') return 'no speed api';
const before = h.flapMs();
if (before !== 100) return 'default flap rate is ' + before + ', want 100';
@@ -1075,7 +1075,7 @@ try {
// board must settle, hold the reading ~2s, and only then take the next update.
await evl(`(() => {
IH.flap = window.__realFlap;
- const h = document.querySelector('#card-N20').gw;
+ const h = document.querySelector('#card-N20').dupre;
window.__n20dwell = { settleAt: 0, changeAt: 0, settled: '' };
h.onSettle(r => { if (!window.__n20dwell.settleAt) { window.__n20dwell.settleAt = performance.now(); window.__n20dwell.settled = r; } });
window.__n20dwell.timer = setInterval(() => {
@@ -1114,9 +1114,9 @@ try {
await send('Emulation.setEmulatedMedia', { features: [{ name: 'prefers-reduced-motion', value: 'reduce' }] });
await send('Page.reload');
await sleep(2500);
- const rm1 = await evl(`document.querySelector('#card-N20').gw.reading()`);
+ const rm1 = await evl(`document.querySelector('#card-N20').dupre.reading()`);
await sleep(5200);
- const rm2 = await evl(`(() => ({ r: document.querySelector('#card-N20').gw.reading(), anims: document.getAnimations().length }))()`);
+ const rm2 = await evl(`(() => ({ r: document.querySelector('#card-N20').dupre.reading(), anims: document.getAnimations().length }))()`);
ok('N20 rests under reduced motion (no self-advancing dwell chain)',
rm1 === rm2.r && rm2.anims === 0, JSON.stringify({ before: rm1, after: rm2.r, anims: rm2.anims }));