From b900fb585f0401aa5f046bf1892e468de2263d43 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Wed, 7 Oct 2026 13:26:29 +0000 Subject: [PATCH] Prove page switch fix (the project lead's 0.3.19 Mac screenshot, 7 October 2026): the thin white rectangle above-left of "Prove on this machine" was the DAG legend's bare .lg rule hitting label.switch.lg, the knob outside the track at off was the inspector's bare .track rule (flex, 10 px margin) hitting every switch track; both scoped (.legend .lg, #i-track), the switch's size class is its own (big), the native input is hidden the accessible way (1 px clip, focusable, label kept) under a positioned label; three view tests, known-failed first on the old CSS (bare rules, input hiding, knob inside the track at off and on in both sizes with the theme blocks untouched) Co-Authored-By: Claude Fable 5.1 --- app/igneum-app/ui/app.css | 35 ++++++++++++++----------- app/igneum-app/ui/index.html | 2 +- app/igneum-app/ui/view.test.mjs | 46 +++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 16 deletions(-) diff --git a/app/igneum-app/ui/app.css b/app/igneum-app/ui/app.css index ca4f63c7b..cd608c44b 100644 --- a/app/igneum-app/ui/app.css +++ b/app/igneum-app/ui/app.css @@ -474,18 +474,21 @@ table{border-collapse:collapse;width:100%;font-size:var(--t-base)} .row{display:flex;gap:10px;align-items:center;min-width:0} .row.wrap{flex-wrap:wrap} .row .addr-input{margin-top:0;min-width:0} -.switch{display:flex;align-items:flex-start;gap:var(--s-3);font-size:var(--t-md);cursor:pointer;padding:9px 0;line-height:1.4} +/* the custom switch (Prove page fix, 7 October 2026): the label is the positioned ancestor, the native input is hidden + the accessible way (1 px, clipped, still focusable, the label association kept), the knob lives inside the track at + both states; the size class is the switch's own (big), never the DAG legend's swatch class */ +.switch{display:flex;align-items:flex-start;gap:var(--s-3);font-size:var(--t-md);cursor:pointer;padding:9px 0;line-height:1.4;position:relative} .switch+.switch{border-top:1px solid var(--line)} -.switch input{position:absolute;opacity:0;width:0;height:0} +.switch input{position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap} .switch .track{width:40px;height:22px;border-radius:999px;background:var(--line-2);position:relative;flex:0 0 40px;transition:background .15s ease;margin-top:1px} .switch .track::after{content:"";position:absolute;top:3px;left:3px;width:16px;height:16px;border-radius:50%;background:#F4F1EC;transition:transform .15s ease} .switch input:checked+.track{background:var(--ember)} .switch input:checked+.track::after{transform:translateX(18px)} .switch input:focus-visible+.track{outline:2px solid var(--ember);outline-offset:3px} .switch input:disabled+.track{opacity:.4} -.switch.lg .track{width:52px;height:30px;flex-basis:52px} -.switch.lg .track::after{width:24px;height:24px} -.switch.lg input:checked+.track::after{transform:translateX(22px)} +.switch.big .track{width:52px;height:30px;flex-basis:52px} +.switch.big .track::after{width:24px;height:24px} +.switch.big input:checked+.track::after{transform:translateX(22px)} .sw-text{min-width:0} .sw-text b{font-weight:600} .sw-text .dim{font-size:var(--t-base)} @@ -664,13 +667,14 @@ body.drawer-open .toast{bottom:calc(var(--drawer-h) + 16px)} .dag-chip{position:absolute;right:10px;top:10px;font-size:var(--t-xs);color:var(--ash);background:var(--graphite);border:1px solid var(--line-2);border-radius:999px;padding:3px 10px;pointer-events:none;z-index:2} .legend{display:flex;flex-wrap:wrap;gap:8px 16px;margin-top:var(--s-3);font-size:var(--t-sm);color:var(--ash);align-items:center} .legend span{display:inline-flex;align-items:center;gap:7px} -.lg{width:10px;height:10px;border-radius:3px;display:inline-block;border:1.5px solid var(--ash);background:var(--row)} -.lg.you{border-color:var(--molten);box-shadow:0 0 0 2px var(--molten-40)} -.lg.pending{border-color:var(--ash)} -.lg.included{border-color:var(--ember)} -.lg.excluded{border-color:var(--ash);opacity:.45} -.lg.proven{background:var(--ember);border-color:var(--ember)} -.lg.locked{border-color:var(--ember);border-radius:50%;box-shadow:0 0 0 2px var(--ember-40)} +/* the DAG legend swatches, scoped to the legend (a bare .lg reached label.switch.lg until 7 October 2026) */ +.legend .lg{width:10px;height:10px;border-radius:3px;display:inline-block;border:1.5px solid var(--ash);background:var(--row)} +.legend .lg.you{border-color:var(--molten);box-shadow:0 0 0 2px var(--molten-40)} +.legend .lg.pending{border-color:var(--ash)} +.legend .lg.included{border-color:var(--ember)} +.legend .lg.excluded{border-color:var(--ash);opacity:.45} +.legend .lg.proven{background:var(--ember);border-color:var(--ember)} +.legend .lg.locked{border-color:var(--ember);border-radius:50%;box-shadow:0 0 0 2px var(--ember-40)} /* Cards: the Tuning strip above the list */ .tune-strip{padding:var(--s-4) var(--card-pad)} .ts-row{display:flex;align-items:center;gap:var(--s-4);flex-wrap:wrap} @@ -799,9 +803,10 @@ body.drawer-open .toast{bottom:calc(var(--drawer-h) + 16px)} .proof-scene{height:180px;border:1px solid var(--line);border-radius:6px;background:var(--row);margin:10px 0;overflow:hidden} .proof-scene canvas{display:block;width:100%;height:100%} .proof-head{display:flex;justify-content:space-between;align-items:baseline;font-weight:500;color:var(--bone);margin-bottom:8px} -.track{display:flex;gap:3px;height:3px;margin-bottom:10px} -.track i{flex:1 1 0;background:var(--line);border-radius:2px} -.track i.proving{background:var(--ember)}.track i.verified{background:var(--bone)}.track i.paid{background:var(--molten)} +/* the inspector's shard track, scoped to its element (a bare .track reached every switch's track until 7 October 2026) */ +#i-track{display:flex;gap:3px;height:3px;margin-bottom:10px} +#i-track i{flex:1 1 0;background:var(--line);border-radius:2px} +#i-track i.proving{background:var(--ember)}#i-track i.verified{background:var(--bone)}#i-track i.paid{background:var(--molten)} .shards{list-style:none;margin:0 0 12px;padding:0;font-size:var(--t-xs);line-height:1.7;max-height:120px;overflow:auto} .shards li{display:flex;justify-content:space-between;gap:8px;color:var(--ink-2)} .shards li::before{content:"\25CF";font-size:7px;margin-right:6px;color:var(--line-2)} diff --git a/app/igneum-app/ui/index.html b/app/igneum-app/ui/index.html index 4e74d13c2..3e05e8f9f 100644 --- a/app/igneum-app/ui/index.html +++ b/app/igneum-app/ui/index.html @@ -429,7 +429,7 @@

Prove on this machine

Every block is turned into a short proof, in pieces called shards. Your cards prove shards and earn IGN for each one.

- +

diff --git a/app/igneum-app/ui/view.test.mjs b/app/igneum-app/ui/view.test.mjs index 216680f2b..a67e94c8a 100644 --- a/app/igneum-app/ui/view.test.mjs +++ b/app/igneum-app/ui/view.test.mjs @@ -811,3 +811,49 @@ test('the light theme contrast ratio of every vendor colour on its well is at le assert.equal(/\.badge\.(nvidia|amd|intel|apple|gpu)\{[^}]*var\(--ember/.test(css), false, 'no badge rule tints with ember'); assert.equal(/\.badge[^{]*\{[^}]*animation/.test(css), false, 'nothing on the mark animates'); }); + +// ---------- the custom switch (the Prove page fix, 7 October 2026) ---------- +// the project lead's screenshot from the live 0.3.19 Mac app: a thin white rectangle above-left of the "Prove on this machine" +// switch and the knob drawn outside the track's left edge at off. Two generic classes reached the switch (the DAG +// legend swatch .lg hit label.switch.lg, the shard track .track hit .switch .track) and the native input was an +// unpositioned absolute. This test reads app.css: no bare .lg or .track rule may exist (every use is scoped), the +// native input is hidden the accessible way (1 px clip, still focusable), and the knob's box sits inside the track's +// box at off and at on for both sizes, in the base rules and untouched by both theme blocks. +const cssRules = (text) => { const out = []; const re = /([^{}]+)\{([^{}]*)\}/g; let m; text = text.replace(/\/\*[\s\S]*?\*\//g, ''); while ((m = re.exec(text))) out.push({ sel: m[1].trim(), body: m[2] }); return out; }; +const ruleOf = (sel) => cssRules(css).filter((r) => r.sel.split(',').map((x) => x.trim()).includes(sel)).map((r) => r.body).join(';'); +const px = (body, prop) => { const m = new RegExp('(?:^|;)\\s*' + prop + ':\\s*(-?[\\d.]+)px').exec(body); return m ? parseFloat(m[1]) : null; }; +const translate = (body) => { const m = /transform:\s*translateX\((-?[\d.]+)px\)/.exec(body); return m ? parseFloat(m[1]) : 0; }; + +test('no generic .lg or .track rule reaches a custom switch: every use of those class names is scoped (known-failed on the 0.3.19 CSS)', () => { + const bare = cssRules(css).flatMap((r) => r.sel.split(',').map((x) => x.trim())).filter((sel) => /^\.(lg|track)(\.[\w-]+)*(\s+[\w.:-]+)*$/.test(sel) && !/^\.switch\b/.test(sel)); + assert.deepEqual(bare, [], 'selectors that start with a bare .lg or .track: ' + bare.join(' | ')); + assert.equal(/