From 4906c91463a4080ae150369839c5a74b43b14a5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Wed, 30 Sep 2026 09:27:42 +0300 Subject: [PATCH] feat: install LOAD failures get the compatibility-test analysis (v3.6.53) ramShowInstallResult appends ramCompatDetailHtml(data, capMem) to the install modal (the modal content is HTML now - the step lines are escaped and ramOpModalFinish takes the asHtml mode), so a failed LOAD / INSTALL [for load] shows the same block analysis as the compatibility test: - the failing block's CAP section (load-file byte range and components, and what completes there), with the JC VM spec 4.5.2 note - or 'the import list already passed' when the failure is later in the file; - the remote status word with its name; - the import list at the CAP's own versions (a real install runs no probe); the closing hint points at the compatibility test ('Set all 0.0' first, then bisect), while a probe run keeps the lower-the-versions wording. ramInstallFailHint stays as the short inline hint; INSTALL [for install] and /api/ram-install-app failures show no block (the LOAD guard in the helper). sw cache simple-v326; tests: 550 Python / 704 Node. --- frontend/index.html | 22 ++++++++++++++++++---- frontend/sw.js | 2 +- frontend/tests/ram.test.js | 16 ++++++++++++++++ pyproject.toml | 2 +- pysim_simple_server/server.py | 2 +- 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index 49354ab..9920e1c 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1749,7 +1749,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.6.52'; +const SIMPLE_VERSION = '3.6.53'; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; // ===== Tab switching ===== @@ -9830,10 +9830,15 @@ function ramCompatFailureHtml(data, mem) { html += ramCompatRowsHtml(rows); const capRows = rows.filter(r => r.state === 'cap' || r.state === 'lowered'); const atCap = capRows.filter(r => r.state === 'cap').length; + const hasProbe = !!((data.probe_imports && data.probe_imports.length) || + (data.probe_unmatched && data.probe_unmatched.length)); if (capRows.length && !atCap) { html += '
' + esc(t('Every import was tested at 0.0 \u2014 a package the card does not have (or another load-file check, e.g. memory) is the cause.')) + '
'; - } else if (capRows.length) { + } else if (capRows.length && hasProbe) { html += '
' + esc(t('The card checks the whole list at that point \u2014 lower the versions (Set all 0.0) to test whether the list is the cause, then restore one line at a time to find the culprit.')) + '
'; + } else if (capRows.length) { + // a real install runs the CAP's own versions: point at the test + html += '
' + esc(t('The card checks the whole list at that point \u2014 the CAP compatibility test can confirm it: Set all 0.0 first, then restore one line at a time to find the package and version the card lacks.')) + '
'; } } html += ''; @@ -10032,7 +10037,10 @@ function ramNvLine(data) { } // Shared result rendering for /api/ram-install and /api/ram-install-app: the -// per-step lines, the counter the card consumed, and the verdict. +// per-step lines, the counter the card consumed, and the verdict. A failure +// at a LOAD / INSTALL [for load] step also gets the same block analysis as +// the compatibility test (which CAP section the block covers, what the card +// checks there, the import list and the status word) in the modal. function ramShowInstallResult(data, okText, capMem) { const resultEl = document.getElementById('ram-result'); const stepsEl = document.getElementById('ram-steps'); @@ -10064,7 +10072,12 @@ function ramShowInstallResult(data, okText, capMem) { probeNote + (hint ? ' \u2014 ' + t('Hint:') + ' ' + hint : ''); resultEl.classList.remove('hidden', 'text-green-600'); resultEl.classList.add('text-red-600'); } - ramOpModalFinish(resultEl.textContent, !!data.success, txt); + // The modal carries the step lines (escaped) plus the LOAD-failure + // analysis when there is one (ramCompatDetailHtml returns '' for a + // successful run and for failures that are not LOAD/INSTALL [for load]). + let modal = esc(txt); + modal += ramCompatDetailHtml(data, capMem || null); + ramOpModalFinish(resultEl.textContent, !!data.success, modal, true); } async function ramExecute() { @@ -17906,6 +17919,7 @@ const LANG_RU = { 'The import list already passed \u2014 the Import component completed at': 'Список импортов уже пройден \u2014 компонент Import завершён на', 'Every import was tested at 0.0 \u2014 a package the card does not have (or another load-file check, e.g. memory) is the cause.': 'Все импорты проверены с версией 0.0 \u2014 причина в пакете, которого нет на карте (или в другой проверке load-файла, например памяти).', 'The card checks the whole list at that point \u2014 lower the versions (Set all 0.0) to test whether the list is the cause, then restore one line at a time to find the culprit.': 'Карта проверяет весь список в этой точке \u2014 понизьте версии («Все 0.0»), чтобы проверить, в списке ли причина, затем возвращайте по одной строке, чтобы найти виновника.', + 'The card checks the whole list at that point \u2014 the CAP compatibility test can confirm it: Set all 0.0 first, then restore one line at a time to find the package and version the card lacks.': 'Карта проверяет весь список в этой точке \u2014 это можно подтвердить проверкой совместимости CAP: сначала «Все 0.0», затем возвращайте по одной строке, чтобы найти пакет и версию, которых не хватает карте.', 'completes': 'завершает', 'inside': 'внутри', 'the failed step completes': 'неудавшийся шаг завершает', diff --git a/frontend/sw.js b/frontend/sw.js index 34c9e3d..7c4d8e7 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v325'; +const CACHE = 'simple-v326'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/ram.test.js b/frontend/tests/ram.test.js index 9d582cd..3d0a70c 100644 --- a/frontend/tests/ram.test.js +++ b/frontend/tests/ram.test.js @@ -1113,6 +1113,13 @@ test('ramCompatFailureHtml names the failing CAP section and the import list', ( assert.ok(out3.includes('The import list already passed \u2014 the Import component completed at LOAD (1/2).'), out3); assert.ok(out3.includes('remote SW 6581'), out3); assert.ok(!out3.includes('4.5.2'), out3); + // a real install run (no probe): the hint points at the compatibility test + const install = { success: false, failed_step: 3, boundary_block: 1, total_blocks: 2, + load_block_size: 240, error: 'LOAD (1/2): remote SW 6985', steps: data.steps }; + const out4 = ramCompatFailureHtml(install, mem); + assert.ok(out4.includes('the CAP compatibility test can confirm it'), out4); + assert.ok(!out4.includes('lower the versions (Set all 0.0)'), out4); + assert.ok(out4.includes('Import list under test (4):'), out4); delete globalThis.t; // not a LOAD failure at all: no block analysis assert.strictEqual(ramCompatFailureHtml(Object.assign({}, data, { @@ -1142,6 +1149,15 @@ test('the compatibility test runs in the modal like the install', () => { assert.ok(!src.includes('ramShowProgress'), 'the inline progress bar is replaced by the modal'); }); +test('a failed install carries the LOAD failure analysis into the modal', () => { + const src = extractFunc(html, 'ramShowInstallResult'); + assert.ok(src.includes('ramCompatDetailHtml(data, capMem'), 'the analysis must reach the modal'); + assert.ok(/ramOpModalFinish\(resultEl\.textContent, !!data\.success, modal, true\)/.test(src), + 'the modal content is HTML now'); + assert.ok(src.includes('ramInstallFailHint'), 'the short inline hint stays'); + assert.ok(src.includes('ramNvLine'), 'the NV footprint line stays'); +}); + test('ramExecute dispatches the compatibility test op', () => { const src = extractFunc(html, 'ramExecute'); assert.ok(/op === 'compat'/.test(src), 'the compat op must be dispatched'); diff --git a/pyproject.toml b/pyproject.toml index dbda99c..bb40826 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pysim-simple-server" -version = "3.6.52" +version = "3.6.53" description = "HTTP REST server wrapping pysim for the SIMple PWA" requires-python = ">=3.8" # pysim is a git-only dependency installed explicitly by setup.bat/setup.sh. diff --git a/pysim_simple_server/server.py b/pysim_simple_server/server.py index 877bc93..3d61524 100644 --- a/pysim_simple_server/server.py +++ b/pysim_simple_server/server.py @@ -31,7 +31,7 @@ from osmocom.tlv import BER_TLV_IE from osmocom.utils import rpad -VERSION = '3.6.52' +VERSION = '3.6.53' MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE