diff --git a/frontend/index.html b/frontend/index.html
index 2829984..77e83d0 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -2320,8 +2320,12 @@ function renderFileDiff(image) {
meta.push(`${t('Record')} ${image.record}` +
(image.n_records ? ` / ${image.n_records}` : ''));
}
- if (image.index !== null && image.index !== undefined) meta.push(`#${image.index}`);
- if (image.elapsed !== null && image.elapsed !== undefined) meta.push(fmt_elapsed(image.elapsed));
+ if (image.index !== null && image.index !== undefined) {
+ meta.push(`${t('APDU')} #${image.index}`);
+ }
+ if (image.elapsed !== null && image.elapsed !== undefined) {
+ meta.push(`${fmt_elapsed(image.elapsed)} ${t('sec')}`);
+ }
el('file-diff-sub').textContent = `${image.path || image.name || ''} — ${meta.join(' · ')}`;
const before = hexImageHtml(image, 'before');
const after = hexImageHtml(image, 'after');
@@ -2337,9 +2341,12 @@ function renderFileDiff(image) {
used.fresh ? `aa ${t('newly observed')}` : '',
used.unknown ? `-- ${t('never observed')}` : '',
].join('');
+ // Mention only what happened: "0 bytes newly observed" is noise.
+ const counts = [];
+ if (changed) counts.push(t('{c} bytes changed').replace('{c}', changed));
+ if (fresh) counts.push(t('{n} bytes newly observed').replace('{n}', fresh));
el('file-diff-body').innerHTML =
- `
${t('{c} bytes changed, {n} bytes newly observed')
- .replace('{c}', changed).replace('{n}', fresh)}
+ `
${t('Before')}
${before.html}
${t('After')}
${after.html}
@@ -3373,7 +3380,7 @@ function onSearchChange() {
}
// ──────────────────── Versioning ────────────────────
-const PWA_VERSION = '2.7.3';
+const PWA_VERSION = '2.7.4';
const MIN_SERVER_VERSION = '2.3.0';
function parseVersion(v) {
@@ -3430,7 +3437,11 @@ const LANG_RU = {
'changed': 'изменено',
'newly observed': 'впервые наблюдается',
'never observed': 'не наблюдалось',
- '{c} bytes changed, {n} bytes newly observed': 'Изменено байт: {c}; впервые наблюдается байт: {n}',
+ '{c} bytes changed': 'Изменено байт: {c}',
+ '{n} bytes newly observed': 'Впервые наблюдается байт: {n}',
+ 'No changes': 'Изменений нет',
+ 'APDU': 'APDU',
+ 'sec': 'с',
'Showing the first {n} bytes of the image': 'Показаны первые {n} байт образа',
'The image is frozen (size cap reached) — later changes are not tracked.': 'Образ заморожен (достигнут предел размера) — последующие изменения не отслеживаются.',
'Close': 'Закрыть',
diff --git a/frontend/sw.js b/frontend/sw.js
index 1e685ad..c8285eb 100644
--- a/frontend/sw.js
+++ b/frontend/sw.js
@@ -1,4 +1,4 @@
-const CACHE = 'simtrace-analyser-v83';
+const CACHE = 'simtrace-analyser-v84';
const PRECACHE = [
'index.html',
diff --git a/frontend/tests/comprion.test.js b/frontend/tests/comprion.test.js
index 0d877e2..7541c0c 100644
--- a/frontend/tests/comprion.test.js
+++ b/frontend/tests/comprion.test.js
@@ -379,7 +379,7 @@ test('Files view: strings carry Russian translations', () => {
for (const s of ['Files', 'Read', 'Update', 'Changed on card!', 'Change details',
'Show diff', 'Close', 'Offset', 'Record', 'Before', 'After', 'Show the change',
'changed', 'newly observed', 'never observed',
- '{c} bytes changed, {n} bytes newly observed',
+ '{c} bytes changed', '{n} bytes newly observed', 'No changes', 'sec',
'Showing the first {n} bytes of the image',
'The image is frozen (size cap reached) — later changes are not tracked.',
'No file operations recorded', 'Failed to load files', 'Loading…']) {
@@ -508,9 +508,10 @@ test('Change details modal: header, summary, legend and both views', () => {
});
const sub = el('file-diff-sub').textContent;
assert.match(sub, /MF\/EF_ICCID \(2FE2\) — READ BINARY/);
- assert.match(sub, /#12/);
+ assert.match(sub, /APDU #12/);
+ assert.match(sub, /0003\.250 sec/);
const body = el('file-diff-body').innerHTML;
- assert.match(body, /2 bytes changed, 4 bytes newly observed/);
+ assert.match(body, /2 bytes changed · 4 bytes newly observed/);
assert.match(body, /hex-cell hex-changed/); // legend + the changed byte
assert.match(body, /hex-cell hex-new/);
assert.match(body, /hex-cell hex-unknown/);
@@ -542,6 +543,8 @@ test('Change details: a short file shows no filler, no offset, no stray legend',
const body = el('file-diff-body').innerHTML;
assert.doesNotMatch(body, /hex-unknown/); // no '--' cells at all
assert.doesNotMatch(body, /hex-addr/); // a single row: no offset column
+ // The summary mentions only what happened.
+ assert.match(body, /^
1 bytes changed<\/div>/);
assert.match(body, /hex-cell hex-changed/);
// The legend lists only what the views show: 'changed' (the summary line
// above still counts the zero "newly observed" bytes).
@@ -551,6 +554,17 @@ test('Change details: a short file shows no filler, no offset, no stray legend',
assert.doesNotMatch(legend, /newly observed|never observed/);
});
+test('Change details: an empty diff says so instead of counting zeros', () => {
+ renderFileDiff({
+ name: 'EF_ICCID', path: 'MF/EF_ICCID (2FE2)', op: 'READ BINARY',
+ record: null, size: 2, index: 1, elapsed: 0.5, ranges: [],
+ observed_ranges: [], before: [{offset: 0, hex: 'aaaa'}],
+ after: [{offset: 0, hex: 'aaaa'}], image_truncated: false,
+ });
+ assert.match(el('file-diff-body').innerHTML,
+ /^
No changes<\/div>/);
+});
+
test('Change details: a short record keeps its never-observed tail', () => {
// A 9-byte record with 4 bytes observed: the row ends at 9 (no filler
// past it) and '--' marks the bytes that were never observed.
diff --git a/pyproject.toml b/pyproject.toml
index 85956f0..fadff98 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.backends._legacy:_Backend"
[project]
name = "simtrace-analyser"
-version = "2.7.3"
+version = "2.7.4"
description = "SIMtrace Analyser — APDU trace analyser for GSMTAP/PCAP captures (bundles the simtrace2-pysniff utility)"
readme = {file = "README.md", content-type = "text/markdown"}
license = {text = "GPL-2.0-or-later"}
diff --git a/simtrace_analyser/version.py b/simtrace_analyser/version.py
index abfb2fc..77c9610 100644
--- a/simtrace_analyser/version.py
+++ b/simtrace_analyser/version.py
@@ -1,3 +1,3 @@
"""Single source of truth for the SIMtrace Analyser version."""
-__version__ = '2.7.3'
+__version__ = '2.7.4'
diff --git a/tests/test_pcap_import.py b/tests/test_pcap_import.py
index 3b2a183..91f0156 100644
--- a/tests/test_pcap_import.py
+++ b/tests/test_pcap_import.py
@@ -224,7 +224,7 @@ class TestImportEndpoint(unittest.TestCase):
class TestVersion(unittest.TestCase):
def test_package_version(self):
from simtrace2_pysniff import __version__
- self.assertEqual(__version__, '2.7.3')
+ self.assertEqual(__version__, '2.7.4')
def test_status_includes_version(self):
import os