From 055aceb0ea41c394f32f49baa95b8d072f5f0fd0 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sat, 18 Jul 2026 18:05:55 -0500 Subject: feat(gallery): bring the CRT-through-engraved ten to the extraction bar roundCrt, attitudeIndicator, headingBug, chartRecorder, verticalTape, twinNeedle, comfortMeter, badges, tabularReadout, and engravedLabel each get the contract comment. The three DOM builders take dupre- prefixes across their markup and CSS blocks: badges (dupre-badge with the kit-wide dupre-red plus a new dupre-ghost), tabularReadout (dupre-readout and dupre-unit), and engravedLabel (dupre-engrave and dupre-cnt). The rename surfaced a dead rule: .readout .u selects a descendant, but the unit caption is a sibling, so its style never applied. The caption now styles through a standalone .dupre-unit rule. The builders now enforce their documented domains at build, not only in set(): twinNeedle and comfortMeter clamp their initial opts through set(), headingBug normalizes opts.value to 0..360, and badges coerces junk variants through one norm() shared by build and set(). Two probe checks cover the gates and drive the renamed DOM. Extraction audit: 94/111 contract comments. --- tests/gallery-probes/probe.mjs | 84 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) (limited to 'tests') diff --git a/tests/gallery-probes/probe.mjs b/tests/gallery-probes/probe.mjs index 0539da0..81ca016 100644 --- a/tests/gallery-probes/probe.mjs +++ b/tests/gallery-probes/probe.mjs @@ -1402,6 +1402,90 @@ try { fwdT: 'rotate(-42deg)', rflT: 'rotate(42deg)', thH: '75%', bdT: 'rotate(0deg)', cmT: 'rotate(0deg)', penT: '3px' }), b8dom); + // batch-9 domain gates on detached hosts: the flight instruments clamp (or + // normalize) both set() and their build opts, badges coerces junk variants, + // the readout wraps its clock. + const clamps9 = await evl(`(() => { + const ai = DUPRE.attitudeIndicator(document.createElement('div'), { bank: -999, pitch: 5 }); + const aiB = ai.get().bank; + ai.set(999, 999); const aiHi = ai.get(); + const hb = DUPRE.headingBug(document.createElement('div'), { value: 450 }); + const hbB = hb.get().cmd; + const hbNdl = hb.el.querySelector('g polygon').parentNode.getAttribute('transform'); + hb.set(-30); const hbSet = hb.get().cmd; + const vt = DUPRE.verticalTape(document.createElement('div'), { value: 99 }); + const vtB = vt.get(); vt.set(0); const vtLo = vt.get(); + const vtT = vt.el.querySelector('g g').getAttribute('transform'); + const tn = DUPRE.twinNeedle(document.createElement('div'), { fuel: 99, oil: -5 }); + const tnB = tn.get(); + let cmTxt; const cm = DUPRE.comfortMeter(document.createElement('div'), + { temp: 999, humidity: -40, onChange: (v, t) => cmTxt = t }); + const cmB = cm.get(); + const bg = DUPRE.badges(document.createElement('div'), { items: [['X', 7], ['Y', 'junk']] }); + const bgVars = bg.get(); + const tr = DUPRE.tabularReadout(document.createElement('div'), { secs: -10 }); + const trB = tr.get(); + return JSON.stringify({ aiB, aiHi, hbB, hbNdl, hbSet, vtB, vtLo, vtT, tnB, cmB, cmTxt, bgVars, trB }); + })()`); + ok('batch-9 domain gates: flight-instrument clamps at build and set, badges variant coercion, readout wrap', + clamps9 === JSON.stringify({ aiB: -60, aiHi: { bank: 60, pitch: 20 }, hbB: 90, + hbNdl: 'rotate(90.0,65,65)', hbSet: 330, vtB: 35, vtLo: 5, vtT: 'translate(0,92.5)', + tnB: [4, 0], cmB: [100, 0], cmTxt: '100F · 0% RH · TOO WARM', bgVars: [1, 0], trB: 3590 }), clamps9); + + // batch-9 state through the renamed DOM (catches rename stragglers): badges + // swap variant classes, the readout counts down through dupre-readout and + // carries a styled dupre-unit (the old .readout .u rule was dead — .u is a + // sibling, so the caption rendered unstyled), the engraved label counts + // through dupre-cnt, and the SVG flight instruments track set(). + const b9dom = await evl(`(() => { + const bg = DUPRE.badges(document.createElement('div')); + const nBadge = bg.el.querySelectorAll('.dupre-badge').length; + const red0 = bg.el.querySelectorAll('.dupre-badge.dupre-red').length; + const ghost0 = bg.el.querySelectorAll('.dupre-badge.dupre-ghost').length; + bg.set(0, 1); + const red1 = bg.el.querySelectorAll('.dupre-badge.dupre-red').length; + const trHost = document.createElement('div'); document.body.appendChild(trHost); + const tr = DUPRE.tabularReadout(trHost); + const trTxt = tr.el.querySelector('.dupre-readout').textContent; + const unit = tr.el.querySelector('.dupre-unit'); + const unitTxt = unit.textContent; + const unitDisp = getComputedStyle(unit).display; + tr.tick(); const trTick = tr.el.querySelector('.dupre-readout').textContent; + tr.el.querySelector('.dupre-readout').click(); tr.tick(); + const trPaused = tr.el.querySelector('.dupre-readout').textContent; + trHost.remove(); + const el = DUPRE.engravedLabel(document.createElement('div')); + const cnt0 = el.el.querySelector('.dupre-cnt').textContent; + el.set(7); const cnt7 = el.el.querySelector('.dupre-cnt').textContent; + el.el.click(); const cnt8 = el.el.querySelector('.dupre-cnt').textContent; + const tn = DUPRE.twinNeedle(document.createElement('div')); + tn.set(0, 4); + const tnF = tn.el.querySelector('polygon[fill="#e0523a"]').parentNode.getAttribute('transform'); + const tnO = tn.el.querySelector('polygon[fill="#bfc4d0"]').parentNode.getAttribute('transform'); + const cm = DUPRE.comfortMeter(document.createElement('div')); + cm.set(70, 80); + const humid = [...cm.el.querySelectorAll('text')].find(t => t.textContent === 'TOO HUMID').getAttribute('fill'); + const ai = DUPRE.attitudeIndicator(document.createElement('div')); + ai.set(30, -10); + const aiT = ai.el.querySelector('g g').getAttribute('transform'); + const rc = DUPRE.roundCrt(document.createElement('div')); + const p1 = rc.el.querySelector('polyline[stroke="#f4fcf4"]').getAttribute('points'); + rc.tick(); + const p2 = rc.el.querySelector('polyline[stroke="#f4fcf4"]').getAttribute('points'); + const cr = DUPRE.chartRecorder(document.createElement('div')); + cr.reset(); + const crH = cr.get(); + const crD = cr.el.querySelector('path[stroke="#8f2416"]').getAttribute('d'); + return JSON.stringify({ nBadge, red0, ghost0, red1, trTxt, unitTxt, unitDisp, trTick, trPaused, + cnt0, cnt7, cnt8, tnF, tnO, humid, aiT, crtMoves: p1 !== p2 && !p2.includes('NaN'), crH, crD }); + })()`); + ok('batch-9 renamed DOM tracks state: badge variants, readout clock + styled unit, engraved count, instruments follow set()', + b9dom === JSON.stringify({ nBadge: 3, red0: 1, ghost0: 1, red1: 2, trTxt: '24:10', + unitTxt: 'timer', unitDisp: 'block', trTick: '24:09', trPaused: '24:09', + cnt0: '· 3', cnt7: '· 7', cnt8: '· 8', tnF: 'rotate(-170.0,60,62)', tnO: 'rotate(10.0,60,62)', + humid: '#c23a28', aiT: 'rotate(-30.0,65,65) translate(0,-16.0)', + crtMoves: true, crH: 0, crD: '' }), b9dom); + // late exceptions from interactions const errs2 = events.filter(e => e.method === 'Runtime.exceptionThrown'); ok('no exceptions after interaction', errs2.length === 0); -- cgit v1.2.3