The File: EF.DIR [LF] line doubled as the selection store: pysimFsClickFile
wrote a formatted display string there and pysimFsRead/Save parsed it back
(stripping '✗ ' and ' [TYPE]'). The pane now keeps the name in
pysimFsSelected, the redundant title is gone (FID/type/size/FCI remain in
the info block), and the dead pysimFsSelect() plus the unused 'File:' RU
entry are removed. Read/Save still re-select on the server first: pySim's
current selection is a shared cursor that tree expansion, scans and
commands move, so it cannot identify the file shown in the pane.
SW cache v121 -> v122; structural test updated.
The decoded FCI items now follow the FID/type/size line directly; the
snapshot detail view keeps its caption. Test updated to assert the
caption is absent; SW cache v120 -> v121.
- selecting an EF now renders an info block between the File: title and
the contents: FID, file type, size, record length/count (nulls skipped)
followed by the decoded FCI via the existing profilerFciPreviewItems()
decoder (no raw hex), matching the snapshot detail view
- pysimFsInfoHtml() is pure and reused by pysimFsClickFile(); the [LF]
shorthand stays in the title; custom files get the same metadata from
the temporary select probe
- tests: pysimFsInfoHtml in profiler.test.js (full response, missing
fci_hex, skipped nulls) and a structural check for #pysim-fs-info;
help EN/RU and READMEs updated; SW cache v119 -> v120.
- EN: Custom files are added in file manager tree and included in card scan.
- RU: Пользовательские файлы добавляются в менеджер файлов и учитываются
при чтении карт.
- SW cache v118 -> v119.
pySim's lchan.select() resolves names against global selectables (self +
parent chain + MF children + applications) and falls back to probe_file(),
which blindly SELECTs an unknown FID and permanently injects a dynamically
named DF.XXXX/EF.XXXX into the running filesystem model. Probing a whole tree
or scanning a snapshot with custom files therefore polluted the model, made
tree branches show children of the wrong object, and could persist phantom
files into snapshots.
- server: new _select_with_parent()/_select_path() walk the requested parent
path (new parent_path field, parent_sel kept as legacy fallback) strictly
through the model and call lchan.select_file() only; model-unknown 4-hex
segments are probed only with allow_probe and the temporary child pySim
adds is detached again via the cleanup callable that the four handlers
(/api/tree|select|read|write) now run in a finally block
- frontend: getParentPath() builds the segment chain (MF, ADF names, FIDs)
and all tree/select/read/write bodies plus the snapshot/profile walker send
parent_path; allow_probe is set only for custom files; the blind retries
in the file manager were dropped
- tests: tests/test_select_scope.py (duplicate-FID resolution, no APDU for
unknown non-custom files, probe+detach, model unchanged); fs_load/fs_probe
assertions for parent_path and allow_probe; docs/api.md and AGENTS.md
document the contract; SW cache v117 -> v118.
- pysimFsLoadChildren failure now only marks exists=false and re-renders;
loaded children stay in the node and are hidden by the renderer (red
cross, no toggle, no (empty)), so a failed DF is re-attempted on the
next probe/refresh instead of being short-circuited
- dropped the parent_sel-less /api/tree retry: error payloads now carry
exists:false, so it doubled requests for every absent DF and re-selected
the same FID without its parent (pySim probe_file fallback)
- probe walks strictly on exists===true and never collects files under a
non-existing DF; no longer clears children on a failed EF select
- tests updated: single request per failed load, children untouched,
200 exists:false marks absent without retry, render hides stale
children, probe never fetches/selects them; SW cache v116 -> v117.
- pysimFsLoadChildren now treats {success:false,error} (and any error
payload) as a failed listing: absent DFs/ADFs render as a red cross
without an expand arrow instead of silently opening empty; the
/api/tree 500 body also carries exists:false for shape consistency
with /api/select
- an expanded directory with zero children shows a gray (empty)
placeholder
- new Probe all files button (data-needs=card) in the tree header:
walks the whole model tree from MF, verifies every DF/ADF via
/api/tree and every file (incl. custom entries) via /api/select,
skips subtrees of absent dirs, shows N/total progress, toggles to
Stop, and reports present/absent counts with elapsed time; results
colour the current tree only
- tests: fs_load (error/retry/empty), fs_render (red cross, (empty)),
fs_probe (flags, absent-dir skip, custom files, stop, summary);
html structural check; help EN/RU, READMEs, AGENTS updated;
SW cache v115 -> v116.
- each row now shows Edit and Delete buttons (same styling as the
profiler list buttons) instead of the red X glyph
- Edit reloads the entry into the top form: the Add button becomes
Save (data-l10n updated too) and a Cancel button appears; submit
updates the entry in place, recomputing fid/parentFid and excluding
the edited row from the duplicate-path check
- Delete removes without confirmation; deleting the row being edited
cancels the edit and an earlier deletion shifts the edit index
- pysimCustomAdd renamed to pysimCustomSubmit; tests for the whole
flow; SW cache v114 -> v115.
- top-level tab order is now Remote APDU, SCP80, Profiler, Card reader,
Phone simulator; the profiler view moved with its button so the DOM
order matches the tab strip
- docs (help EN/RU, READMEs) and the tab-order test updated; SW cache
v113 -> v114.
- the SVG artwork occupies only ~46% x 63% of its 183x183 canvas, so
the 18px render showed ~8x11px of ink; 30px roughly doubles the
visible glyph while staying under the 32px h1 line-height, so the
header row height is unchanged
- guard test asserts the inline size stays within the 24-32px budget;
SW cache v112 -> v113.
- the wrapper is select-none with an inline cursor:default (the compiled
CSS has no cursor-default utility), so hovering the dot/icons no
longer shows the I-beam or allows text selection
- SW cache v111 -> v112.
- the dot carries its own title (Connecting... / No server connection),
matching the wrapper tooltip; it is cleared when the sim icons replace
the dot
- tests assert the dot title in both server states and its removal once
the server is up; SW cache v110 -> v111.
- #state-indicator is now a wrapper with a dot plus an 18px dark:invert
image; no new compiled Tailwind classes (h-4/w-5 are not in the
prebuilt CSS), so sizing uses an inline style and the header height
is unchanged
- gray dot while probing, red dot when the server is unreachable;
server up -> nosim.svg, animated sim_anim.svg while equipping, and
sim.svg when the card is equipped (`_pysimEquipping` tracked from
status.equipping, refreshed on every 2s poll)
- i18n reuses existing strings; tests for all five states, dot color
transitions and the markup; SW cache v109 -> v110.
- pysimFsSortChildren() groups DFs above EFs, then sorts each group by
FID (default) or case-insensitive symbolic name; missing names fall
back to the FID as the sort key; deterministic tie-break; the input
array is not mutated
- render uses the sorted copy, so lazily loaded directories and custom
file injections are sorted too
- FID / Name pills above the tree (pysimFsSetSort) persist the choice in
localStorage (otaman_fs_sort) and re-render without refetching
- tests for the comparator (DF priority, both keys, fallbacks, custom
entries, no mutation) and the pill wiring; help/README updated;
SW cache v108 -> v109.
The stuck-card pattern: after a Back TR the card re-issues the parent menu
as a new SELECT ITEM (91XX -> FETCH, paused, awaiting TR), but the UI's
back/timeout branch ignored that response and showed the cached top menu;
the next item click then sent ENVELOPE(Menu Selection) while the card was
waiting for the TR. The card answers such an ENVELOPE with 9000 (not the
usual 91XX), and menu-select cleared the pending command without a TR,
leaving an unfinished proactive session until reset/equip.
- server: _finish_pending_menu() answers a paused command with a cancel TR
(0x10) before /api/menu-select sends its ENVELOPE and drains a 91XX
follow-up, so a new selection can never shadow an unanswered FETCH
- frontend: stkMenuRespond('back'|'timeout') renders the follow-up command
from the server response (SELECT ITEM / DISPLAY TEXT) and shows the
cached top menu only when the TR answer carries no command (9000)
- tests: finish-pending cancel TR + chain drain (Python); stkMenuRespond
back/timeout/cancel/ok rendering (frontend); help/AGENTS updated;
SW cache v107 -> v108.
The message branch treated the static auto_equip config flag as if
initialization were in progress, so with auto-equip on (default) a
cardless server showed 'Card inserted — initializing...' instead of the
no-card message. The initializing state now requires equipping or an
actually present card with auto-equip enabled; regression test added.
SW cache v106 -> v107.
SCP80, Card reader and Profiler list pill rows now use the same centered
layout as Remote APDU and Phone simulator (flex-wrap justify-center).
SW cache v105 -> v106.
Profiles / Card snapshots / Custom files switch from top-bar style tabs
(border-b, rounded-t) to the rounded-full pill style used by the other
second-level switchers. SW cache v104 -> v105.
Availability model: server-down / server-up-no-card / card-equipped.
- header shows a symbolic indicator dot (red/yellow/green, tooltip)
- a 5s probe tracks /api/version+/api/status while not fully ready; the
2s status poll takes over once connected
- controls declare data-needs='server' or 'card'; disabled + tooltip when
the state does not satisfy them: equip (server), status/reset, file
manager read/save/edit, raw APDU, profile-from-card, new snapshot,
profiler list Check card, event Send, RAM install/explore delete,
STK menu, Send STATUS, Verify vs pySim (server), pySim execute (server),
PLI save/poll toggle (server)
- pysimConnect no longer conflates server and card: it records both and
Connect stays usable without a card
- ramSendOta/ramInstallCap now use pysimFetch instead of raw fetch
(relative /api URLs broke custom server URLs)
Custom files moved from the Card reader sub-tabs to the Profiler list
tabs (Profiles / Card snapshots / Custom files); help/README/AGENTS
updated. Tests: availability gating, indicator structure, moved tab.
SW cache v103 -> v104.
Snapshot comparison results now use the master/checked snapshot names
where profile checks keep expected/actual: the raw-data field rows, the
generic mismatch line and the decoded FCI comparison table headers
(profilerRenderReport/fcpDiffHtml take an optional {expected, actual}
labels object; the name column widens to w-40 and wraps). Labels are
stored with the results so the language switch and the Only mismatches
filter re-render correctly; Check card snapshot and live checks are
unchanged. Tests for both labeled and default rendering; help/README
updated; SW cache v101 -> v102.
Each snapshot row now renders '(N files, scanned in X.XX sec)' when the
snapshot has timing data (profilerSnapshotCountLabel); old/imported
snapshots without timing keep the plain '(N files)'. RU wording 'за X.XX
сек'. SW cache v100 -> v101.
Server measures every classified APDU (A4 select, B0 read binary, B2 read
record) from command to response: _collect_apdu_times() enables the tracer
(reattaching it if pySim nulled it) and /api/select + /api/read return
'apdu_times': [{type, ms}]. Collection is safe: handlers hold _CARD_LOCK.
Snapshots store per-file {select_ms, read_ms}, a ms value per record and
snapshot-level stats {select, read_binary, read_record}: {min, max, avg,
count} plus total_ms (wall time of the scan). The snapshot view gets a
summary under the title (files/records counts, scan time, min/avg/max per
command type) and shows select/read per file and read time per record.
Timings are display-only: checks, snapshots comparison and imports ignore
them (old snapshots simply show 'No timing data').
Tests: Python classifier/collection (tests/test_apdu_timing.py) and
frontend stats/accumulator/format/build-file/summary. SW cache v99 ->
v100; help, README and docs/api.md updated.
'Profile from card' and 'New snapshot' now focus the name field when the
dialog opens, and Enter in that field starts the scan
(profilerScanNameKeydown, ignored while the Scan button is disabled).
Tests for the key handler and the input wiring; SW cache v98 -> v99.
When a navigation fetch failed and the cache had no entry for the exact
URL (e.g. '/' while only 'index.html' is precached, common while the
local server restarts), the fetch handler resolved respondWith() to
undefined, producing 'TypeError: Failed to convert value to Response'.
The navigate fallback now chains caches.match(request) -> cached
index.html -> an explicit 503 offline Response. sw.js requests are
network-only with the same offline Response (previously they fell into
the navigate branch), and a new unit test loads sw.js in a VM sandbox
covering navigate/cache/asset/api paths. SW cache v97 -> v98.
After the gray text was raised to #cbd5e1 in dark mode, elements using
dark:text-slate-300 (inactive tabs, labels, list text) matched the muted
level. Map dark:text-slate-300 to #e2e8f0 (slate-200, the body's dark
text color) so normal text stays one step brighter than muted gray.
SW cache v96 -> v97.
Elements using bare text-gray-300/400/500/600 (proactive log ids and
timestamps, expand markers, event codes, ...) kept the light-theme gray
values in dark mode, which made them hard to read on slate-800/900.
- dark .text-gray-*:not([class*='dark:text-']) rules map them to the same
level as the other dark muted text (slate-300 #cbd5e1, slate-200/100 for
gray-700/800)
- dark:text-gray-400/600 and dark:text-slate-500 normalized to #cbd5e1
- dark:border-slate-600/700/800 (+ /50) brightened one step
- light theme unchanged
- the whole contrast block moved to src/contrast.css, appended by the
npm build scripts after Tailwind so it stays reproducible; SW cache
v95 -> v96
Wrap each block of the phone row in a bordered card (border, rounded,
p-3) with items-start and gap-4 so they read as distinct panels and wrap
cleanly. SW cache v94 -> v95.
- Phone pill: STK menu, STATUS and polling and the subscribed-events list
in one row, proactive command log full-width below (room for more
elements)
- TR Config pill: response data injected into TERMINAL RESPONSEs;
currently the PROVIDE LOCAL INFORMATION dictionary, structured as
heading + body blocks for future proactive-command responses
- phoneSwitchSubtab() mirrors the other sub-tab switchers and sets help
anchors stk-menu / pli-dict; switchTab('phone') always opens Phone
- refreshDynamicI18n renders only the visible phone panel
- help EN/RU section 6 regrouped (6.4 STATUS polling under Phone, 6.5
TR Config response data), READMEs and AGENTS.md updated
- structural and behavioral tests (html.test.js, phone_tabs.test.js);
SW cache v93 -> v94
- C-APDU tab renamed Remote APDU (label untranslated EN/RU); Response
parser moved from a top-level tab to a pill under it
- Profiler moved from the Card reader sub-tabs to a top-level tab
- Proactive UICC moved to a top-level tab and renamed Phone simulator
- Card reader keeps File manager, Custom files, pySim command line,
Raw APDU
- modals (event send, profiler scan/snapshot, STK menu) moved outside
the tab containers so they can open from any tab
- STK overlay disables the top-level tabs while waiting for user input
- OTA PoR jump now goes to Remote APDU > Response parser
- help EN/RU restructured (2.8 Response parser, 5 Profiler, 6 Phone
simulator, subsequent sections renumbered, anchors kept); READMEs and
AGENTS.md updated
- html.test.js asserts the new tab/pill structure; SW cache v92 -> v93
- _apply_equipped_card() centralizes the post-equip refresh + TERMINAL
PROFILE (shared by the /api/command equip branch and auto-equip)
- server tracks card_session (bumped on equip and disconnect) and
equipping; /api/status exposes connected, card_present, card_session,
equipping, auto_equip and is exempt from _CARD_LOCK (pure cached state)
- auto-equip is on by default (--no-auto-equip; off with --no-card-init):
the presence observer spawns a one-shot worker after insertion, which
runs equip under _CARD_LOCK and applies the same refresh; the monitor
starts after the startup init so pyscard's initial 'already present'
event does not re-equip a fresh session
- UI: /api/status polls every 2s (other views stay at 5s); when
card_session changes it runs pysimResetCardData() (STK overlay, file
tree, events, proactive log, PLI, status) — the same reset as a manual
Equip; messages: initializing / press Equip / no card
Tests for the observer and auto-equip rules, session bumps,
_apply_equipped_card, and the UI state machine. SW cache v91 -> v92.
The UI only noticed a removed card when some user action ran a real card
command (e.g. Check status); /api/status is a cached-state read that kept
returning the old card, and _handle_card_disconnect() did not clear
app.card/rs.
- start_card_monitor() registers a pyscard CardObserver for our reader;
it only polls SCardGetStatusChange (no APDU, no connection, no extra
process), and on removal sets server.card_present=False and calls
_handle_card_disconnect() under _CARD_LOCK
- /api/status now exposes connected (session usable) and card_present
(physically inserted) and masks card/profile/atr/selection when not
connected; _CARD_CONNECTED is initialized from card presence instead of
being unconditionally True
- the 2s UI poll includes /api/status; on disconnect it switches to the
existing 'No card detected. Insert card and click Equip' state, or the
new 'Card inserted — press Equip' hint when the card is back; the old
_hadData heuristic is gone
Tests for the observer (filtering, removal, insertion) and the UI state
transitions. SW cache v90 -> v91.
A probe can leave a card in a context where CLA-00 file access returns
6d00, so the software MF select fails (stock pysim init survives only
because it resets physically after probing). Fast init now recovers the
same way, on demand:
- FastRuntimeState.reset() falls back to hard_reset() on
SwMatchError/ProtocolError (logged as FAST-RESET)
- init_card_fast() retries once after sl.reset_card() (FAST-INIT)
- __main__ falls back to stock pysim init once and skips
TERMINAL PROFILE/drain when no card was initialized (no more
6d00/6985 noise after a failed init)
- do_equip_fast() no longer pre-unregisters command sets; PysimApp.equip
does that after a successful init, so a failed equip keeps the
previous card/rs instead of leaving the app unequipped
Tests for the escalation, the retry and failed-equip state retention.
No card-model special cases.
Fast init (fastinit.py) is now the default for startup and equip: all
profile probes run on one connection, RuntimeState uses a software reset
and only explicit equip/reset reconnect the card. Measured on the
reference reader: equip 1729ms vs 9850ms and zero RESET events vs 7-8.
--full-pysim-init restores pysim's stock init_card/equip for
compatibility/debugging; the former --fast-init flag is kept as a hidden
no-op alias so existing command lines keep parsing. Docs updated.
Every FETCHed proactive command must be answered, otherwise the card is
left in an unfinished session and stops issuing commands (e.g. it will
not deliver a PoR for SEND SM). The menu handlers now share
_menu_send_response, and a server-side watchdog (_arm_menu_timeout /
_menu_timeout_fire, --menu-timeout, default 60s, 0 disables) sends the
timeout result (0x12) when the user never answers. The timer is armed
while a command is pending and cancelled on any response, equip, rescue
and card disconnect.
Also fixes docs/api.md, which had back (0x11) and timeout (0x12) codes
swapped. Tests for arm/cancel/clamping and the flat timeout TR. SW cache
v89 -> v90.
pysim's init_card() resets the card once per profile candidate in
CardProfile.pick(), once in RuntimeState.__init__ and again in
PysimApp.equip(); on typical readers each reconnect costs ~1.3s and a
normal init/equip does 7-8 of them (measured: equip 9.85s, startup
~14.8s).
fastinit.py mirrors pySim.app.init_card() with the resets removed:
pick_profile_no_reset() runs all profile probes back-to-back on one
connection, FastRuntimeState.reset() is a software reset (select MF,
clear selected_adf/scp, ATR from the transport) and the equip/reset
commands are routed through do_equip_fast (one reconnect via
wait_for_card) and do_reset_fast (always a physical reset). Enabled
with --fast-init; stock behavior remains the default.
Tests for the reset-free pick, the soft reset and the explicit reset
paths. Docs updated; SW cache v88 -> v89.
--poll-interval 0 now really disables background STATUS polling (it was
clamped to 1s, and the equip branch force-enabled it anyway). Request
handlers and the poll thread now share _CARD_LOCK so a poll can never
interleave a FETCH/TERMINAL RESPONSE pair — the baseline log showed
AUTO-STATUS chains and duplicate FETCHes inside the equip TP chain.
--timing adds elapsed timestamps, per-reset logging (RESET #n) and
phase durations for startup (init_reader, card_init, pysim_app,
terminal_profile_drain) and equip (onecmd, terminal profile).
AGENTS.md documents the proactive-TR invariant: never fetch without
answering, never fetch twice, one APDU conversation at a time.
Add AGENTS.md to .gitignore so the local agent guide (goal, implemented
features, pySim usage) stays untracked. Correct the /api/ram-install
description: the .cap archive is parsed server-side by _cap_parse, not
by pySim.javacard/global_platform.
README(.md/_RUS.md) gain the sections that existed only in the in-app
help: C-APDU Parser, HTTP OTA (GP GPC v2.2 Amd B v1.1 4.7), and the
Profiler subtree (profile list, filesystem rules, scan options, check
report incl. Only mismatches, card snapshots, Check card snapshot and
Compare snapshots). Also correct the tab/sub-tab counts and add
Theme/Localization sections to the English README.
docs/api.md: remove the duplicate /api/ram-install section, document
fci_hex/file_size/record_len/num_of_rec in /api/select, the records
shape of /api/read, bump the version example, and add the missing
endpoint sections (events, event-send, proactive-log, status-poll,
rescue, poll-status, poll-toggle, pli-qualifiers, pli-dict).
New Compare snapshots button on the Card snapshots tab opens a dialog
with Master snapshot / Snapshot to check selects and the two mask
options (Match first 4 bytes for EF.IMSI / EF.ICCID, checked by
default). The comparison runs like a profile check where the master
snapshot takes the place of the profile: every file must match exactly
(exact FCI, contents), except the first 4 bytes of masked EF.IMSI/EF.ICCID.
profilerRulesFromSnapshot() synthesizes exact-match rules from the
master; profilerSnapshotSource() of the checked snapshot is the data
source; files present only in the checked snapshot are appended as
failed 'extra file' results via profilerExtraFileResults() and the new
optional extraResults param of profilerRunProfile(). Results reuse the
existing view, summary and Only mismatches filter; Back to list returns
to the snapshots tab. Tests for rule synthesis, masking, comparison and
extra files. SW cache v87 -> v88.
The results view header (live card and snapshot checks) gets an Only
mismatches checkbox that hides all passing files and keeps only failures
and errors; when everything passes under the filter, a 'No mismatches'
note is shown instead. The pass/fail/error summary always reflects all
results. Helps with large profiles on cards where most files match.
SW cache v86 -> v87.
Each profile row gets a Check card snapshot button next to Check card.
It opens a picker listing the stored snapshots (name, ICCID, date, file
count) and runs the profile rules offline against the selected snapshot,
showing the usual report titled 'profile — snapshot'.
profilerRunRule() accepts an optional data source: profilerLiveSource()
(default) wraps /api/select + /api/read, profilerSnapshotSource() serves
select/read from the snapshot files. Contents missing from the snapshot
are reported as unverifiable errors ('Content not captured in snapshot')
rather than mismatches. The shared check loop moved into
profilerRunProfile().
Tests for the snapshot source and snapshot-based checks; help docs
updated; SW cache v84 -> v85.
translatePage() only rewrites static [data-l10n] elements, so labels built
at render time (Check card/Edit/Export/Delete, Open, Remove, Send, ...)
stayed in the old language. toggleLang() now calls refreshDynamicI18n(),
which re-renders only the visible dynamic views:
- profiler sub-tab: current view (list, snapshot list, editor, results via
the new profilerRenderResultsView with profilerResults stored, snapshot
via profilerRenderSnapshotData which leaves the name input alone)
- open scan modal: profilerScanRefreshOptions re-translates the mask labels
without touching checkbox state
- cards table (cardsRender), proactive events/log/PLI (async, only when
the sub-tab is visible)
Also removed the duplicated translatePage() call in toggleLang.
New test for profilerScanRefreshOptions. SW cache v83 -> v84.
Rectangular top-rounded tabs with a bottom border, matching the app's
top-level tab bar (incl. dark:bg-blue-500/dark:text-white active state,
toggled by profilerListSwitch). SW cache v82 -> v83.
The list view now has a centered pill row (Profiles | Card snapshots) and
shows one list at a time; the last active tab persists while the app is
open. Snapshot flows (scan, import) switch to the snapshots tab, profile
import switches to profiles. Help docs reworded (tabs). New DOM test for
profilerListSwitch. SW cache v81 -> v82.
New read-only capture entity stored in localStorage 'otaman_snapshots':
- profiler list view is now two labelled columns: Profiles and Card
snapshots; each snapshot row shows name, decoded ICCID, date, file
count and Open / Export / Delete buttons
- New snapshot reuses the scan modal in a snapshot mode (name only, no
ignore/mask/FCP options) via profilerScanCard(..., 'snapshot') ->
profilerBuildSnapshotFile: metadata + raw FCI + exact contents for
every readable file (no ignore list, no masking). The scan returns to
the list afterwards.
- ICCID is decoded from EF.ICCID (2FE2) with decIccid() (nibble-swapped
E.118 digits, F pad per TS 102 221 13.2) and stored as an immutable
snapshot field, shown next to the name in the list and the view.
- Open shows all captured data read-only (attributes, raw FCI + decoded
FCI, contents or 'Not captured'); only the name is editable/saveable.
- Import/Export/Delete + profilerValidateSnapshot; quota-safe save.
Tests: decIccid, profilerSnapshotIccid, validation, snapshot builder
(exact contents/no mask/unreadable), snapshot-mode scan with ICCID.
Docs synced (help/help-ru). SW cache v79 -> v80.
Adds an 'All' header checkbox next to 'Ignore contents of files:' that
checks or unchecks every file; it reflects the list state (checked only
when all are checked, indeterminate on a mixed selection) and updates on
individual checkbox changes. RU label: 'Все'. Docs synced, tests for the
toggle/state helpers. SW cache v77 -> v78.