diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dbb80d..738b764 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,18 @@ every change carries a descriptive commit message. ## [Unreleased] +## [3.32.6] — 2026-10-23 + +### Fixed + +- The empty amber **preset-mismatch banner** no longer flashes on the SCP80/RAM view. + `ramOpChanged()` (v3.28.0) un-hid `#ram-preset-warning` by the operation kind, but the + banner's text *and* visibility belong to `spPresetWarningRender()` (which also handles + the batch suppression): with no warning text the empty bordered box stayed visible until + the next status poll re-hid it — on every RAM view and operation switch. The visibility + is content-driven again; `frontend/tests/ram.test.js` pins it (the pre-fix code fails + the test). + ## [3.32.5] — 2026-10-21 ### Fixed diff --git a/frontend/index.html b/frontend/index.html index ef3c129..9173435 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1943,7 +1943,7 @@ // ===== Version ===== // Single source of truth for the PWA version: shown in the header and used // by the server version check in pysimConnect(). -const SIMPLE_VERSION = '3.32.5'; +const SIMPLE_VERSION = '3.32.6'; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; // ===== view history (the browser Back/Forward buttons) ===== @@ -10180,10 +10180,14 @@ function ramOpChanged() { // The batch resolves the preset per card (by ICCID): its selector and the // single-card Execute button do not apply, and the batch panel takes over. setHidden('ram-preset-cell', isBatch); - setHidden('ram-preset-warning', isBatch); setHidden('ram-exec-btn', isBatch); setHidden('ram-batch-panel', !isBatch); if (isBatch) ramBatchFetch(); + // The preset-mismatch banner is content-driven: spPresetWarningRender owns + // its text *and* its visibility (the batch suppression included). Toggling + // `hidden` by the op kind here showed an empty amber box until the next + // render - the flash on every RAM view / op switch (v3.32.6). + spPresetWarningRender(); // The Execute button's .cap gate applies to the install-cap and compat ops. if (typeof pysimApplyAvailability === 'function') pysimApplyAvailability(); } diff --git a/frontend/sw.js b/frontend/sw.js index 191d388..683f17b 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v463'; +const CACHE = 'simple-v464'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/ram.test.js b/frontend/tests/ram.test.js index 498270c..e17e009 100644 --- a/frontend/tests/ram.test.js +++ b/frontend/tests/ram.test.js @@ -553,6 +553,59 @@ test('ramOpChanged clears the executed status only on a real op change', () => { assert.ok(!els['ram-install-params'].classList.contains('hidden')); }); +test('the RAM preset banner is content-driven (no empty amber flash)', () => { + // A view / op switch used to un-hide #ram-preset-warning by the op kind + // (v3.28.0): with no warning text the empty bordered box stayed visible + // until the next status poll rendered it away - the amber block flashing + // on every SCP80/RAM view change. Its visibility belongs to the renderer. + const els = fakeRamDocument(['ram-op', 'ram-card-sel', 'ram-preset-warning', + 'ram-install-params', 'ram-cap-rows', 'ram-compat-rows', 'ram-app-rows', + 'ram-delete-rows', 'ram-del-package-row', 'ram-preset-cell', 'ram-exec-btn', + 'ram-batch-panel', 'ram-result', 'ram-explorer', 'ram-steps', 'ram-progress', + 'sp-preset-warning', 'tar-preset-warning']); + els['ram-preset-warning'].classList.add('hidden'); + const prev = { + cards: globalThis.cards, + t: globalThis.t, + iccid: globalThis._pysimCardIccid, + batchFetch: globalThis.ramBatchFetch, + }; + globalThis.cards = []; + globalThis._pysimCardIccid = null; // no card session: no warning text + globalThis.t = s => s; + globalThis.ramBatchFetch = () => {}; + // The real renderer, defined where the extracted ramOpChanged finds it + // (a plain function body sees only the globals). + const src = ['cardsNormIccid', 'spPresetWarning', 'ramIsBatchOp', 'ramPresetIdx', + 'spPresetIdx', 'tarPresetIdx', 'spPresetWarningRender'] + .map(f => extractFunc(html, f)).join('\n'); + const real = new Function(src + '\nreturn spPresetWarningRender;')(); + let calls = 0; + globalThis.spPresetWarningRender = () => { calls++; return real(); }; + try { + _ramOpLast = 'explore'; + els['ram-op'].value = 'explore'; + ramOpChanged(); + assert.strictEqual(calls, 1, 'the content-driven renderer must own the banner'); + assert.ok(els['ram-preset-warning'].classList.contains('hidden'), + 'an empty banner must not be un-hidden by the op dispatch'); + // a batch op keeps it hidden as well (the preset resolves per card) + els['ram-op'].value = 'batch'; + ramOpChanged(); + assert.ok(els['ram-preset-warning'].classList.contains('hidden')); + // the visibility is never toggled here by the op kind again + assert.ok(!extractFunc(html, 'ramOpChanged').includes("'ram-preset-warning'")); + } finally { + globalThis.spPresetWarningRender = () => {}; + if (prev.cards === undefined) delete globalThis.cards; else globalThis.cards = prev.cards; + if (prev.t === undefined) delete globalThis.t; else globalThis.t = prev.t; + if (prev.iccid === undefined) delete globalThis._pysimCardIccid; + else globalThis._pysimCardIccid = prev.iccid; + if (prev.batchFetch === undefined) delete globalThis.ramBatchFetch; + else globalThis.ramBatchFetch = prev.batchFetch; + } +}); + test('ramRender keeps the selected card preset across rebuilds', () => { const els = fakeRamDocument(['ram-card-sel', 'ram-op', 'ram-install-params', 'ram-result', 'ram-explorer', 'ram-steps', 'ram-progress']); globalThis.cards = [{ name: 'A' }, { name: 'B' }, { name: 'C' }]; diff --git a/pyproject.toml b/pyproject.toml index 6da310d..97605a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pysim-simple-server" -version = "3.32.5" +version = "3.32.6" description = "HTTP REST server wrapping pysim for the SIMple PWA" license = { text = "GPL-2.0-or-later" } requires-python = ">=3.9" diff --git a/pysim_simple_server/server.py b/pysim_simple_server/server.py index ba78627..1deab5d 100644 --- a/pysim_simple_server/server.py +++ b/pysim_simple_server/server.py @@ -37,7 +37,7 @@ from osmocom.tlv import BER_TLV_IE from osmocom.utils import rpad -VERSION = '3.32.5' +VERSION = '3.32.6' MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE