Change details: labelled APDU/time in the header, no zero counts (v2.7.4)

- the header's trailing meta is labelled instead of bare numbers:
  "ADF/EF_IMSI (6F07) — READ BINARY · APDU #490 · 0251.199 sec" (the record
  part already read "Record 2 / 5")
- the summary counts only what happened: "{c} bytes changed · {n} bytes newly
  observed" drops whichever part is zero ("4 bytes changed" alone when nothing
  was first observed) and an empty diff reads "No changes" instead of
  "0 bytes changed, 0 bytes newly observed"
- i18n: the combined key splits into '{c} bytes changed' / '{n} bytes newly
  observed' plus 'No changes', 'APDU' and 'sec' ('{c}','{n}' placeholders keep
  the existing replace-based substitution; the Russian keeps the established
  wording: «Изменено байт: {c}», «Впервые наблюдается байт: {n}», «с»)
- tests: the header and summary assertions follow the labels, a short-file
  case pins the omitted zero, and an empty-diff case pins 'No changes';
  527 Python + 76 Node tests
- PWA_VERSION/pyproject/version.py 2.7.4, SW cache simtrace-analyser-v84
This commit is contained in:
catarrh committed 2026-10-18 00:46:42 +03:00
1 parent 71a94f5f50
commit 06c6e952a9
6 files changed
+38 -13

No files matched your search

+17 -6
View File
@@ -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 ? `<span class="hex-legend-item"><span class="hex-cell hex-new">aa</span> ${t('newly observed')}</span>` : '',
used.unknown ? `<span class="hex-legend-item"><span class="hex-cell hex-unknown">--</span> ${t('never observed')}</span>` : '',
].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 =
`<div class="hex-summary">${t('{c} bytes changed, {n} bytes newly observed')
.replace('{c}', changed).replace('{n}', fresh)}</div>
`<div class="hex-summary">${counts.length ? counts.join(' · ') : t('No changes')}</div>
<div class="hex-pair">
<div><h4>${t('Before')}</h4>${before.html}</div>
<div><h4>${t('After')}</h4>${after.html}</div>
@@ -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': 'Закрыть',
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'simtrace-analyser-v83';
const CACHE = 'simtrace-analyser-v84';
const PRECACHE = [
'index.html',
+17 -3
View File
@@ -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, /^<div class="hex-summary">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,
/^<div class="hex-summary">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.