v3.28.4: the Custom-mode FCI editor + the shared ADM pattern

The profile rule editor rendered the same "FCI hex (raw SELECT response)"
field for Exact and Custom, although the custom check never reads
`fciHex` (it compares the data objects against the card's live FCI) - so
the field looked like an expectation it was not.

- The raw-FCI field and its decoded preview are exact-mode-only now; the
  Custom mode shows just its data-object editor.
- Switching a rule to Custom seeds the data objects from its captured FCI
  when the list is empty (the same all-'value' seeding a captured profile
  gets); the "load from the captured FCI" link refills an emptied list,
  and a rule without a captured FCI still starts empty for manual rows.

From the v3.28.3 review: the ADM's byte-pair rule now lives in one
place, presets.ADM_PATTERN, used by /api/verify-adm and the outfile
import - two literals could drift and an imported ADM could silently
become unverifiable.  The RU help's ADM wording is "4-16 hex-цифр
(чётное число)".

Docs: help EN/RU, the repo AGENTS.  Tests: the mode switch (seed / keep /
no FCI / switch-away), the field-gating source pin, the pattern's shape
and the both-users pin.  917 frontend + 927 Python; sw cache simple-v442.
This commit is contained in:
2026-10-14 21:14:16 +03:00
parent b023719555
commit a66493f9b7
10 changed files with 88 additions and 12 deletions
+16 -3
View File
@@ -1910,7 +1910,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.28.3';
const SIMPLE_VERSION = '3.28.4';
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
// ===== view history (the browser Back/Forward buttons) =====
@@ -20336,7 +20336,7 @@ function profilerRenderRule(i, r) {
'<input type="number" value="' + (r.numRecords === null ? '' : r.numRecords) + '" oninput="profilerUpdateRule(' + i + ',\'numRecords\',this.value===\'\'?null:parseInt(this.value))" class="w-full font-mono text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1 dark:bg-slate-800" placeholder="—"></div>';
}
html += '</div>';
if (fciMode === 'exact' || fciMode === 'custom') {
if (fciMode === 'exact') {
html += '<div class="mb-2 flex gap-3 items-start">';
html += '<div class="flex-1 min-w-0"><label class="block text-xs text-gray-600 dark:text-slate-400 mb-0.5">' + esc(t('FCI hex (raw SELECT response)')) + '</label>' +
'<textarea oninput="profilerFciInput(' + i + ',this.value)" rows="4" class="w-full font-mono text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1 dark:bg-slate-800" placeholder="62...">' + escHtml(r.fciHex || '') + '</textarea></div>';
@@ -20344,6 +20344,10 @@ function profilerRenderRule(i, r) {
'<div id="profiler-fci-preview-' + i + '" class="text-xs font-mono leading-relaxed text-gray-600 dark:text-slate-400 break-all">' + profilerFciPreviewItems(r.fciHex || '') + '</div></div>';
html += '</div>';
}
// Custom mode (v3.28.4): the raw FCI is only the *source* for the data
// objects (the check compares the objects, never the raw bytes), so its
// field is exact-mode-only and the object list is seeded automatically when
// the rule is switched to Custom.
if (fciMode === 'custom') {
html += profilerRenderFciDosEditor(i, r);
}
@@ -20756,7 +20760,16 @@ function profilerUpdateRuleFileType(i, value) {
function profilerUpdateRuleFciMode(i, value) {
if (!profilerDraft) return;
profilerDraft.rules[i].fciMode = value;
const r = profilerDraft.rules[i];
r.fciMode = value;
// Custom mode has no raw-FCI field: seed the data objects from the rule's
// captured FCI when the list is still empty (the same all-'value' seeding
// a profile created from a card or a snapshot gets, v3.25.8). A rule
// without a captured FCI starts empty - rows are added by hand.
if (value === 'custom' && !(r.fciDos || []).length && r.fciHex) {
const dos = fcpDosFromFci(r.fciHex);
if (dos.length) r.fciDos = dos;
}
profilerRenderEditor();
}