ui: show raw and decoded comparisons together, with labelled columns (v2.7.15)

Content mismatches in profile checks and snapshot comparisons used to
replace the raw expected/actual pair with the decoded per-field table
whenever a decoder could see a difference.

- The raw comparison is always shown first (heading + expected/actual
  rows); the decoded per-field table follows when the decoder finds
  differing fields.
- The decoded table gets a column-header row carrying the same
  expected/actual labels (profile/snapshot names in a comparison) that
  the raw rows and the FCI diff table use.
- Matching decoded values keep the 'Decoded values match; raw bytes
  differ' note and show no decoded table.
- tests: ef_decode report test extended (raw + decoded + column headers,
  custom labels, matching-decoded note); help EN/RU.
This commit is contained in:
2026-09-21 00:09:39 +03:00
parent bfb455b3c8
commit 4730e81ec2
7 changed files with 41 additions and 18 deletions
+22 -12
View File
@@ -1413,7 +1413,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 = '2.7.14';
const SIMPLE_VERSION = '2.7.15';
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
// ===== Tab switching =====
@@ -11780,25 +11780,34 @@ function profilerRenderReport(results, labels) {
const isContent = c.label === 'content' || /^content\.rec\d+$/.test(c.label);
const dd = isContent && typeof efContentDiff === 'function'
? efContentDiff(r.name, efFidFromPath(r.path), String(c.expected), String(c.actual)) : null;
const inputCls = 'flex-1 min-w-0 font-mono text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1 bg-gray-100 dark:bg-slate-800';
html += '<div class="mt-1">';
html += '<div class="text-xs text-red-600 font-mono pl-2">' + esc(c.label) + '</div>';
// Raw bytes first: the ground truth, always shown.
html += '<div class="text-xs text-gray-500 dark:text-slate-400 pl-2 mt-0.5">' + esc(t('Raw comparison')) + '</div>';
html += '<div class="flex items-center gap-2 mt-0.5 pl-2">' +
'<span class="' + labelCls + '">' + esc(expLabel) + '</span>' +
'<input readonly title="' + escHtml(expLabel) + '" value="' + escHtml(String(c.expected)) + '" class="' + inputCls + '"></div>';
html += '<div class="flex items-center gap-2 mt-0.5 pl-2">' +
'<span class="' + labelCls + '">' + esc(actLabel) + '</span>' +
'<input readonly title="' + escHtml(actLabel) + '" value="' + escHtml(String(c.actual)) + '" class="' + inputCls + '"></div>';
// Per-field decoded diff, when the decoder can see one; the
// expected/actual column headers carry the comparison labels.
if (dd && dd.rows.length) {
html += '<div class="text-xs text-gray-500 dark:text-slate-400 pl-2 mt-0.5">' + esc(t('Decoded comparison')) + ' — ' + esc(expLabel) + ' | ' + esc(actLabel) + '</div>';
html += '<div class="text-xs text-gray-500 dark:text-slate-400 pl-2 mt-0.5">' + esc(t('Decoded comparison')) + '</div>';
html += '<div class="flex items-center gap-2 mt-0.5 pl-2">' +
'<span class="' + labelCls + '"></span>' +
'<span class="flex-1 min-w-0 text-xs text-gray-500 dark:text-slate-400">' + esc(expLabel) + '</span>' +
'<span class="flex-1 min-w-0 text-xs text-gray-500 dark:text-slate-400">' + esc(actLabel) + '</span></div>';
for (const [label, ev, av] of dd.rows) {
html += '<div class="flex items-center gap-2 mt-0.5 pl-2">' +
'<span class="' + labelCls + '">' + esc(label) + '</span>' +
'<input readonly title="' + escHtml(expLabel) + '" value="' + escHtml(ev) + '" class="flex-1 min-w-0 font-mono text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1 bg-gray-100 dark:bg-slate-800">' +
'<input readonly title="' + escHtml(actLabel) + '" value="' + escHtml(av) + '" class="flex-1 min-w-0 font-mono text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1 bg-gray-100 dark:bg-slate-800">' +
'<input readonly title="' + escHtml(expLabel) + '" value="' + escHtml(ev) + '" class="' + inputCls + '">' +
'<input readonly title="' + escHtml(actLabel) + '" value="' + escHtml(av) + '" class="' + inputCls + '">' +
'</div>';
}
} else {
if (dd && dd.sameDecoded) html += '<div class="text-xs text-gray-500 dark:text-slate-400 pl-2">' + esc(t('Decoded values match; raw bytes differ')) + '</div>';
html += '<div class="flex items-center gap-2 mt-0.5 pl-2">' +
'<span class="' + labelCls + '">' + esc(expLabel) + '</span>' +
'<input readonly title="' + escHtml(String(c.expected)) + '" value="' + escHtml(String(c.expected)) + '" class="flex-1 min-w-0 font-mono text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1 bg-gray-100 dark:bg-slate-800"></div>';
html += '<div class="flex items-center gap-2 mt-0.5 pl-2">' +
'<span class="' + labelCls + '">' + esc(actLabel) + '</span>' +
'<input readonly title="' + escHtml(String(c.actual)) + '" value="' + escHtml(String(c.actual)) + '" class="flex-1 min-w-0 font-mono text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1 bg-gray-100 dark:bg-slate-800"></div>';
} else if (dd && dd.sameDecoded) {
html += '<div class="text-xs text-gray-500 dark:text-slate-400 pl-2 mt-0.5">' + esc(t('Decoded values match; raw bytes differ')) + '</div>';
}
if (c.label === 'fci') html += fcpDiffHtml(String(c.expected), String(c.actual), labels);
html += '</div>';
@@ -13327,6 +13336,7 @@ const LANG_RU = {
'showing raw data': 'показаны сырые данные',
'No decodable fields': 'Нет декодируемых полей',
'pySim JSON (server)': 'pySim JSON (сервер)',
'Raw comparison': 'Сравнение сырых данных',
'Decoded comparison': 'Декодированное сравнение',
'Decoded values match; raw bytes differ': 'Декодированные значения совпадают; отличаются сырые байты',
'Back to list': 'Назад к списку',