The native browser dialogs block the page and look foreign inside the
app. One shared modal (#pysim-dialog-modal) with the promise-based
helpers pysimAlert/pysimConfirm/pysimPrompt replaces all 71 call sites
(60 alert, 10 confirm, 1 prompt in profilerNew); deferredPrompt.prompt()
is the PWA install prompt and is untouched.
The helpers serialize through ONE promise queue (overlapping dialogs
appear in order, no message is overwritten). Escape cancels; Enter
accepts except with the Cancel button focused (native behaviour); key
repeats/IME are ignored; a POINTER answer within 500 ms of a dialog
opening is dropped (the buttons keep the same position, so a
double-click's second click would otherwise answer the next queued
dialog - e.g. an alert queued before a destructive confirm). Backdrop
clicks are ignored. The dialog is role="dialog" aria-modal="true" and
traps Tab (the handler cycles the controls the dialog shows: alert OK;
confirm Cancel/OK; prompt Cancel/input/OK), so the focus can neither
leave the modal nor reach a background control (Space would activate
it). When the modal is absent the helpers fall back to the native
dialogs - the Node tests extract single functions without the markup.
Alerts are fire-and-forget (a notification, not a decision); the 10
confirms and the prompt are awaited, which made five functions async
(scriptsDelete, pysimCustomRemove, profilerNew, profilerDelete,
snapshotDelete). Because the modal no longer freezes the page, the
delete flows that mutated by index after the confirmation mutate by
identity now (profilerDelete/snapshotDelete/pysimCustomRemove/
testSuiteDeleteScript/testSuiteDelete): a list reorder during the open
dialog would otherwise delete the wrong entry.
The nine dialog messages that were still raw English literals are
t()-wrapped - three of them (No secured packet to verify/send, Card
reader server is not connected) activated RU entries that had existed
unused because they were never wrapped; Import error and Snapshot save
failed are new keys.
Drive-by: the batch modal's #ram-op-batch-info used whitespace-pre-line,
which has no rule in the prebuilt style.css (the v3.30.0 stale-style
trap), so its notice never line-broke - now whitespace-pre-wrap.
Tests: a new frontend/tests/dialogs.test.js (15 tests: the modal path,
the queue, the double-answer guard, the keyboard, the grace, the trap,
the fallback, the sweep pin, the stylesheet pin) plus the async/helper
updates in 8 existing test files. 945 frontend + 952 Python;
sw cache simple-v454.
Import many card presets at once from a card vendor's plain-text dump.
The new pure pysim_simple_server/outfile.py (parse_outfile) plus
POST /api/presets/import-outfile: without `mode` the call is a dry-run
preview returning the parsed presets and a report (nothing is stored),
with `merge`/`replace` the same parse runs and the presets go through
the preset store.
The parser: the data starts at the first line whose whitespace tokens
all look like column names and include ICCID (a leading `word:` prefix
is stripped, so a `var_out:IMSI ICCID ...` header line matches while a
config line naming a value - `ICCID:897...` or `ICCID 897...` - cannot);
one row per card follows. Recognised columns (case-insensitive): ICCID,
PIN1/PIN2, PUK1/PUK2, ADM (the ASCII string, hex-encoded), PSK (32 hex;
an optional PSKID/PSK_ID names the identity, defaulting to the card's
ICCID) and KICn/KIDn - one keyset per numbered pair, up to 15;
IMSI*/EKI/KIKn are ignored. The KIc/KID bytes are inferred: the keyset
number is the column number, the algorithm nibble follows the key size
(8 B DES, 16 B 3DES2 or AES - the preview's choice, 3DES2 default,
24 B 3DES3; anything else skips the keyset with a note), both bytes
carrying the same number (TS 102 225 A.2). The TARs never come from the
file: the caller's table (default the three role entries) is applied
with one global default keyset written into every entry (the card's
first keyset when unset). An invalid optional code is dropped with a
report line instead of failing the card; the report names the columns,
the rows read, the parsed keysets and every skip.
The ICCID normaliser fix (both sides, drift-guarded): the trailing F
(or Fs) is the odd-nibble padding - when the rest is digits only it is
the plain digit form (the vendors print the ICCID as digits+F),
otherwise the value is the raw nibble-swapped EF hex; a raw value never
ends in F, so the rule is unambiguous.
PWA: the Import outfile button in the Cards list, the hidden file input
and the preview panel (the common name, the 16-byte algorithm select,
the TAR table with the role defaults editable and + TAR / remove for
extras, the global default keyset select over the parsed keysets, the
merge/replace mode, the parsed cards table, the report, Refresh
preview, Import). The panel joins refreshDynamicI18n for the language
switch.
Docs: help EN/RU (the term outfile stays untranslated in RU), api.md's
new section, the repo AGENTS. Tests: the parser (both fixture shapes,
the column detection, the key-size inference, the options/TAR table,
the PSK identity, the skips, the store round-trip), the endpoint (the
preview, the import, the duplicate skip, the 400s), the normaliser on
both sides, the PWA helpers/wiring and the algorithm16 drift guard
against outfile.py. 904 frontend + 903 Python; sw cache simple-v436.
- the ADM input capped the key at 8 chars, but the server's _verify_adm
takes 4-16 hex digits (padded to 8 CHV bytes) and the old input had no
limit; maxlength=16 now, with a static guard in html.test.js.
- cardsTarAdd/cardsTarRemove re-rendered the table from the save-path
reader, so an emptied role field was silently refilled with its default.
The renderer no longer invents defaults; a raw reader (cardsTarsFromTable)
feeds Add/Remove and the language-switch refresh, while the save path
(cardsTarsFromForm) keeps the default-filling for the mandatory roles.
- the editor's dynamic TAR/keyset rows now rebuild on a language switch
(refreshDynamicI18n) from what is typed - their placeholders and titles
carry t() strings. (The title/Save label were already refreshed there.)
- dropped the now-unused cardsRoleMsl helper and its test references.
- docs: the ADM is documented as 4-16 hex digits (help EN/RU, api.md,
AGENTS). sw cache simple-v362.
Tests: 665 Python + 784 frontend green; the whole inline PWA script
syntax-checked.
- /api/ram-install-app referenced undefined spi1/tar (the resolution block
was only added to /api/ram-install and /api/cap-compat), so every request
answered 500. The same preset/MSL resolution is now in place, with
regression tests for both modes (install / make selectable) and for the
no-MSL refusal before any card I/O.
- esc() now escapes " and ' too: the TAR editor renders free-text
descriptions (and other values) into attribute contexts, so a crafted
preset import could inject attributes. Three tests asserting the old
unescaped output updated; a dedicated esc test added.
- _spi1_for_tar names the no-TAR case in its hint (a pre-built packet
without tar/spi1 used to report "TAR ?").
- preset free TAR rows no longer store an empty "role" key (the documented
shape); a non-list `tars` in an update is refused (400) instead of being
silently ignored.
Live: the no-MSL request answers 400 with the hint; a make_selectable with
a bogus AID runs the format probe and the INSTALL (derived SPI1 16 from the
preset MSL), the card answers remote SW 6A88 and the counter advances - no
500. Suites: 658 Python + 775 frontend.
No version bump: completes the unreleased v3.10.0 (6360dc5 was never
pushed). sw cache simple-v359.
Presets drop the card-wide SPI1/SPI2 and the fixed tar/uiccTar/usimTar
fields: a preset now carries `tars` - the three mandatory role entries
(isd/uiccRfm/usimRfm, spec defaults pre-settled) plus free TARs with an
optional description, each with its MSL (Minimum SPI1, TS 102 226
8.2.1.3.2.4). v3.9.1 presets convert on load/import (the spi1 seeds all
three MSLs); duplicate TARs and empty TAR/MSL values are refused; the
store schema version is 3.
Operations derive a packet's SPI1 from its TAR's MSL (_spi1_for_tar): the
RAM ops, the test runner (per-step overrides still win), the counter probe
and each TAR-probe row; the plain send keeps an explicit SPI1 with a
`warning` when it is below the MSL, and a TAR outside the preset without an
explicit SPI1 is refused with a hint. The TAR probe resolves per checked
TAR before sending and reports the effective spi1 per row. The secured
packet form's SPI2 default is 00 (the RAM/probe operations pin their own).
PWA: the Cards form edits the TAR table (role rows with MSL inputs plus
Add/Remove free rows with a description), the list shows a TARs (MSL)
column, the SCP80 TAR field suggests the preset's entries (datalist) and a
preset apply seeds the SPI1 from the operation TAR's MSL with an amber
warning below it; the preset-wide SPI1/SPI2 fields and the header marker's
SPI requirement are gone (completeness = a keyset + the three role entries
with their MSLs). Help EN/RU, docs/api.md and AGENTS updated; sw cache
simple-v358.
Live: TAR probe 000000 at its MSL 16 (registered, por_ok, counter
advanced); a send without spi1 derived 16 (por_ok, counter advanced); a
send with spi1 0A returned the warning and the card refused it (6200, no
counter consumed); an unknown TAR without spi1 was refused with the hint.
The Cards list now marks the row whose ICCID matches the equipped card
(cardsFindByIccid handles digits / spaced / raw EF hex alike): an emerald row
tint plus a small dot with a "The equipped card's preset" tooltip in the Name
cell. There is no highlight while the ICCID is unknown (no card, unreadable
EF.ICCID) or when no preset matches - the amber banner already covers that.
pysimCardStateUpdate() re-renders the list when the ICCID actually changes, so
a visible Cards tab follows equip/removal live (opening the tab re-renders
anyway through switchTab).
Tests: cards_iccid.test.js drives cardsRender with a fake tbody - the matching
preset is highlighted (both ICCID forms), the others stay plain and no ICCID
means no highlight; card_state.test.js stubs cardsRender. Help EN/RU and the
repo AGENTS updated; sw cache simple-v344.
Two selector bugs in the new TAR-probe view and one stale-list bug in the RAM
view, all from the lazy population of the view-specific preset selects:
- cardsAutoSelectByIccid() set sp-card-sel and ram-card-sel only, so the TAR
probe view never got the equipped card's preset (and tarExecute() then ran
with preset_id: '', i.e. without persisting the counters it consumed);
- tarRender()'s keep-fallback was -1 instead of the remembered _ramCardIdx, so
the select stayed on "— Select card —" even when the auto-selection had
remembered the preset; the TAR pill switch now also calls tarRender();
- the keyset selectors showed a stale "N — not defined in the preset":
spKeysetSync() runs during the auto-selection, when ram/tar-card-sel have no
options yet, so those lists were built with no preset; ramRender() now
rebuilds them (spKeysetSync) once its preset is known - tarRender() already
did.
Tests: the auto-select covers tar-card-sel; a DOM-driven regression test
(ramRender with a remembered preset and a two-keyset preset must show
value="2" selected and no "not defined" entry); source guards for ramRender's
sync, tarRender's keep and the TAR pill switch. sw cache simple-v339.
- The card create/edit form gets a From card button next to the ICCID
field: it fills the field with the equipped card's EF.ICCID. It uses the
availability model through a new 'card-iccid' requirement - enabled only
while /api/status reports a connected card with a readable iccid; with a
card but no readable ICCID the tooltip says so, otherwise the usual
'insert and equip' hint applies. The last status ICCID is kept in
_pysimCardIccid (cleared on disconnect) so the click needs no round-trip.
- Duplicate ICCID refusal now compares normalized values via
cardsNormIccid/cardsFindDuplicateIccid (digits, spaced digits and raw EF
hex all identify the same card), skips the row being edited, ignores an
empty field, and names the conflicting preset in the alert (translated).
- i18n RU: From card -> 'С карты', duplicate/unreadable alerts.
- Tests: cardsFindDuplicateIccid across formats and edit-skip,
cardsIccidFromCard fill + alert path, form wiring guards, _pysimCardIccid
tracking in the card-state harness, card-iccid availability gating in the
indicator harness. 261 Python + 400 frontend tests green.
- Docs: help EN+RU Cards field table, READMEs, AGENTS. SW otaman-v185.
- Server: _decode_iccid (nibble-swapped E.118 digits, trailing-F pad) and
_read_iccid (best-effort MF/2FE2 read through the parent-scoped select
helper; the previous selection is restored, the read never raises). The
equip path records the digit string before the TERMINAL PROFILE, i.e.
before any CAT session is active; a startup with a card does the same.
The value is cleared on card removal and exposed as /api/status 'iccid'
(only while connected).
- PWA: when a connected status update reports a *new* ICCID, the matching
card preset is selected in both SCP80 views - Secured Packet (sp-card-sel
+ form fill) and RAM (ram-card-sel + _ramCardIdx). Matching normalizes
digits and accepts the raw EF hex form, leading zeros ignored; a manual
choice for the same card is kept until the next equip, and a card removal
re-arms the auto-selection. The status line shows the ICCID.
- Tests: tests/test_iccid.py (decode variants, model + probe read, equip
recording, disconnect clearing) and frontend/tests/cards_iccid.test.js
(normalize, find, select, no-override, card swap); the card_state test
harness stubs the new hook and covers the guard reset.
- Docs: api.md /api/status fields, help EN+RU (SCP80 intro + Cards tab),
READMEs, AGENTS. SW cache otaman-v183.