The step editor's result select offers the full TS 102 223 8.12.0 list; the
spec's applicability table (Tables 6.1/6.2/6.3, "Proactive commands versus
possible terminal response") now groups it by the selected command: the
command-relevant values first (an <optgroup> "relevant for <command>"), the
rest after ("all other results").
The grouping is ordering-only - nothing is hidden, the manual hex entry
covers everything, and ANY (the drain's respond/first too) stays a flat list,
so a stored result always matches an option and real-card deviations remain
reachable.
TR_RESULT_BY_COMMAND is parsed from the pinned PDF (41 commands) and pinned
by frontend/tests/tr_result_by_command.json; trCommandCode() resolves the
editor's command names to their type codes (the first code per name,
matching testCommandOptions) and testFormSelect gained the optgroup support.
Tests: the map vs the parsed fixture, the grouping and the union (nothing
lost), the optgroup rendering and the call-site pin. 861 frontend + 842
Python; sw cache simple-v421.
A script's `event` value may be a code (a hex byte or a number) or one of the
server's name keywords (location_status, access_tech, network_rejection,
data_connection, ... - events.py's EVENT_NAMES); the API accepts both. The
PWA parsed every value as a hex string: the step list showed
'EVENT 0xaccess_tech' (the unparsed fallback), the editor fell back to event
03 (Location status) with the location field set, and saving then rewrote
the step to that event - silently corrupting name-authored scripts (the two
Alfa2G trigger scripts use access_tech/data_connection).
A mirrored EVENT_ALIASES map plus an eventCode() resolver (number / hex byte
/ name -> code) now feed the step summary, the editor's event block and the
event check; the summary's fallback no longer prefixes a non-numeric value
with 0x, and the editor keeps its canonical hex form on save (the same code).
Drift guard: the aliases live in frontend/tests/event_vectors.json, asserted
by the Node test (the PWA's map + resolver) and the Python test
(events.EVENT_NAMES + resolve_event). Tests: 856 frontend + 839 Python;
sw cache simple-v419.
The step editor's result select (the expect's respond, the proactive-drain's
respond/first) offered five symbolic values with no hex. It now offers the
full TS 102 223 v18.3.0 8.12.0 list (00-3F), labelled '<hex> - <alias>' (or
'<hex> - <spec name>'), plus a manual hex entry, and an optional
additional-information byte for the general result (83 02 <result> <info>,
default 00; the 8.12.2 terminal-problem values are suggested through a
datalist).
The script aliases were corrected to the spec: refused 03->22,
not_understood 04->32, modified 06->07, no_response 22->12 - a stored script
using one of them now answers with the corrected value; no_response stays
accepted for compatibility but is no longer offered by the editor.
The server's TR decode tables (_RESULT_NAMES_BASIC/_GENERAL, a GSM 11.14
mix displayed in the proactive log) are replaced by the spec table plus the
8.12.2 additional-information names; the dcs field parses as hex (0A used to
be refused and 10 read as decimal 10); the drain summary's 22 label is fixed.
The result labels are not translated (spec text); the unused RU keys were
removed. Shared fixture frontend/tests/tr_results.json, drift-guarded by
both suites. 855 frontend + 838 Python; sw cache simple-v418.
The kind string `proactive` read like an expectation of a proactive command
(`expect` is the step type for that) although the action consumes and answers
them. Renamed everywhere:
- ACTION_KINDS / TEST_ACTION_KINDS (drift-tested), the normaliser branch and
its error strings, the dispatch and the helper names (_test_run_drain,
_drain_require_match/_label);
- the report label is DRAIN and the PWA summary reads "DRAIN x5 require ...
TR ok";
- the editor/collect/summary/problem branches, docs/test-scripts.md
(Proactive drain), docs/api.md, help EN/RU ("Очистка очереди proactive");
- the tests (TestRunnerProactiveDrain, the normalisation/data-check tests).
No stored scripts used the old kind (unpublished series), so there is no
alias and no migration. sw cache simple-v404.
Tests: 839 frontend + 804 Python green.
The suite/script ownership invariant could break silently: a suites import
with mode "replace" wiped the suites while their scripts stayed behind
(suite_id pointing at gone suites - invisible in the UI), a scripts import
with mode "replace" left every suite's member list dangling, and a script
whose suite_id disagreed with its listing suite stayed inconsistent.
- server: reconcile_scripts_to_suites() repairs the invariant - a script a
suite lists adopts that suite (the member list is authoritative), a script
no suite lists joins "Imported scripts"; migrate_scripts_to_suites()
delegates to it (the startup migration is the same pass) and the suites
import runs it after loading, so a "replace" adopts the wiped suites'
scripts instead of losing them.
- the scripts import refuses mode "replace" while any suite still lists
scripts (400 naming the dangling references; merge is the normal path).
- nits from the review: a legacy numeric `event` is no longer parsed as hex
in the step summary/error check (0x0A displayed as 0x10), and an integral
float memory quota (a JSON client's 200.0) is accepted.
- tests: the reconciliation (missing suite, listed-but-mismatched,
idempotent), the scripts-replace guard (refused/allowed), the
suites-replace adoption, the float quota, the numeric-event summary.
Tests: 838 frontend + 800 Python; sw cache simple-v402.
The test-script event action edited its fields as a JSON textarea and only
accepted the four events events.py builds; every other event needed the raw
envelope action with hand-copied hex. It now renders the same EVENT_FORMS
form the Phone tab's send dialog uses, for every modelled event (29; no
release gating - a script may target another card).
- PWA: the field renderer/collector is shared (eventFormFieldsHtml /
eventFormValues / eventFormPatternError / eventFormSrc / eventFieldGroupsUpdate);
the Phone tab keeps its ids, its release gating and its onchange updaters,
the step editor uses test-f-ev- ids, the generic group updater and a live
Event data (hex) preview with the one-ENVELOPE check. The event select
lists all EVENT_FORMS events (the numeric keys are decimal strings -
parseInt(k, 10)); the action stores the collected fields, the built data
and the event's source (fixed cfg.src or the cfg.srcField input - the
action never carried src before, so MT call / IMS registration / slices
could not be network-sourced). The JSON textarea is gone.
- engine: the `event` action keeps `fields` validation for the four
server-built events (a stray data hex is ignored) and accepts the
form-built `data` for every other event - hex-validated and checked
against events.EVENT_DATA_MAX (244, the one-ENVELOPE budget; server.py's
_EVENT_DATA_MAX now reads it from events.py). The runner builds the four
at run time (fresh host clock) and sends the stored data for the rest.
- tests: the engine's data path (accepted / missing / malformed / oversized,
the four still fields-built), the runner sending the data (fake scc), the
editor's collect/prefill/patterns/source (Node), the shared helpers.
Docs: test-scripts.md, api.md, help EN/RU. Tests: 838 frontend +
797 Python; sw cache simple-v401.
A suite is the root object: every script belongs to exactly one suite, the
suite holds the ordered member list by reference - the optional setup first,
the members, the optional teardown last - and runs them in one card session,
without resets and without resume.
- test_suites.py: the suite store (test_suites.json, --test-suites), validated
and atomic like the script store: roles normalised (setup first, teardown
last, at most one of each, a script appears once per suite), per-member
on_fail stop/continue and the suite-level require_adm.
- test_scripts.py schema v2: suite_id/require_adm; list_by_suite()/orphans();
the import reports the id_map a bundle import remaps with. v3.21.0 scripts
without a suite are attached once to an auto-created "Imported scripts"
suite (migrate_scripts_to_suites, called from __main__).
- API: /api/test/suites (+ update|delete|import - delete refused while
non-empty; the bundle import imports the scripts first, remaps the suite
references and skips unresolvable ones); the scripts endpoints gain
suite_id/require_adm plus move|copy (delete detaches from the suite first;
a suite update refuses a membership change).
- runner: /api/test/run {suite_id} resolves the members and the preset, runs
the ADM gate, then setup -> members (per-member on_fail) -> teardown. The
teardown always runs on a stop or a failure (skipped only when the card is
gone; it clears the stop flag for its duration); a card reset stops the
suite, skips the remaining members and is flagged. Members' live steps land
in the shared snapshot, their records (status/steps/note/log excerpt/
counters) in _TEST_RUN['suite']; the summary carries the counts, wall time,
preset, the SCP80 counters before/after per keyset and the session flags.
_test_log buffers the semantic lines (bounded) for the report's per-member
excerpts.
- ADM gate (_ensure_adm): a suite/script require_adm verifies the matched
preset's key once per card session (the /api/verify-adm badge sets the same
latch); a failure refuses the start, stamps the session (no automatic
retry) and needs a manual verify.
- PWA: the Test script pill becomes four views - the suite list (default),
the suite editor (the script list: role badges, on_fail, up/down, per-script
Run/Edit/Move/Copy/Delete, Add script with the templates), the script editor
(the step list; a single run shows its result under the steps and is not
preserved) and the suite report (per-member blocks with the step table,
notes and run log; summary; export JSON/Markdown). Suite bundles export/
import; the legacy script export imports into "Imported scripts". EN/RU.
- tests: the suite store/migration, the runner orchestration (order, on_fail,
setup failure, stop, card reset, teardown, per-member logs) and the ADM gate
(Python); the suite HTTP paths; the PWA report/Markdown/wiring (Node).
Tests: 833 frontend + 794 Python; sw cache simple-v397.
Five reported issues, plus the action that makes an explicit drain possible:
- on_fail is honoured by every step failure: an expectation with no pending
command (or a FETCH that returns nothing) fails as a Command/Fetch check at
the step's level instead of raising past it; the default still stops the
run, on_fail "warning" continues (the tolerant consume-if-pending form).
- the run cleanup answers ok (0x00) and keeps draining while the TR answers
91XX (bounded); a cancel is only sent when a script asks for it - it tells
the card the user aborted the session and pushes assertive applets onto an
error path.
- new `proactive` action: STATUS rounds consume every announced command
(respond/first TERMINAL RESPONSE results, attempts/interval_ms bound) until
the card answers 9000; an optional require asserts at least one drained
command (an empty drain fails it), the final SW is the step's SW check and
a still-pending command is handed to the next step. PWA editor + summary +
validation, EN/RU help, docs, action-kind drift test.
- the PoR response form follows the command's TAR: _tar_is_rm (preset role
entries + the TS 101 220 Annex D ISD/RFM allocations) decides; a non-RM
TAR's reply is always raw (TS 102 226 4) - no fabricated status word even
when the bytes happen to parse (01 00 0B 91 ... used to read as SW 000B).
- a secured data too short for the compact structure (a 2-byte applet reply)
is recovered by _por_header_parse (the decode minus the compact parse; a
malformed packet or a failed RC/CC/DS check still yields no por) and
reported raw, so short applet replies are assertable via por.data.
Docs: test-scripts.md, api.md, help EN/RU, repo AGENTS. sw cache simple-v396.
Tests: 825 frontend + 755 Python green.
The script author read por.sw as the envelope SW, so a por.sw expectation
on an applet's own TAR failed with (none).
- docs/test-scripts.md: a 'The three status words (envelope SW / PoR
status / R-APDU SW)' table in the authoring section - where each one
comes from, the script field and the report row - plus a cross-reference
from the por check paragraph; help EN/RU 8.9 points at it.
- a failed por.sw check on a response_type: 'raw' decode carries the check
detail 'the response is application data - an applet's own TAR has no
R-APDU SW; assert it with data' (the PWA renders check details).
- the runner's log prints APP DATA=<hex> (no R-APDU SW) for application
responses instead of the misleading R-APDU SW=(none) data=...; the
compact/scripting lines are unchanged.
- the step editor's custom-PoR block gets a one-line hint about the same.
Tests: 821 frontend + 730 Python; sw cache simple-v391.
Review of 7250dbd:
- the step editor's menu-select form gains a Match case select
(Case-insensitive (default) / Case-sensitive); the step summary flags a
case-sensitive step with (case), the saved params carry
case_sensitive: true only when chosen, and the docs say the editor
authors the flag.
- docs/api.md: fix the 'with an optionally' grammar.
- the Variant-2 menu test's comment claimed a contains match while it is an
exact one; a real contains leg is added ('ыбор' -> item 128).
- timeless rationale for the case-insensitive default; the format note now
dates the change.
sw cache simple-v389 (index.html/help changed).
Tests: 821 frontend + 726 Python.
The test scripts get the pieces an applet test needed:
- scp80 check.por accepts an object {status?, sw?, data?} asserting the
decoded PoR of this very exchange (inline or via SEND SHORT MESSAGE);
the evaluation is shared with the expect-step por check
(_por_check_results). The editor's PoR select gains 'custom' with the
three fields (an empty object refuses on save).
- a new alpha content check reads the command's Alpha identifier (05/85,
Annex A coded) - the text check stays bound to the 8D Text string, so a
command carrying both keeps them distinct.
- a new sms content check asserts a SEND SHORT MESSAGE's submit TPDU:
DA / PID / DCS / UDL / UD (the parsed fields are logged and reported;
an applet's own SMS, not the OTA PoR the por check decodes).
- scp80.params.format wraps the C-APDU in the TS 102 226 5.2.1 Command
Scripting template (expanded = AA..., expanded-ae = AE 80...00 00); the
log shows WRAPPED=, the report C-APDU=<effective> format=<fmt>.
- docs: Worksheet 1 addresses an RFM TAR (B00000/B00001/B00010 compact,
B00120/B00140/B00130 expanded) with the own-TAR and MSL/spi1 notes, and
file-write is documented as a direct APDU that does not fire
EVENT_REMOTE_FILE_UPDATE.
Tests: 819 frontend + 724 Python; sw cache simple-v387.
The four findings from the review of the 3.15-3.18 batch:
- The event step's `_eventFieldsError` flag is cleared at the start of each
collect: fixing malformed JSON used to leave the save refused until the
editor was closed and reopened. Regression test covers invalid -> fixed.
- The script store keeps an entry that no longer validates on load (a
tightened engine rule or a hand edit): it is served, fails at run start with
the validator's message and can be fixed in the editor, instead of being
dropped from memory and erased by the next save. Mutation paths still
refuse it. (The presets store already had this behaviour.)
- Writebacks are serialized per script (`_testWriteChains`): two quick saves
of an unsaved draft no longer create two server-side scripts - the window
could be minutes while a RAM operation holds the card lock. Regression
test asserts one create + one update.
- Deleting a script cancels its pending debounced save and marks it deleted,
so a queued writeback never resurrects it; a create already in flight is
undone with a delete. Regression test covers the pending-save case.
- The event vectors moved to a shared fixture
(frontend/tests/event_vectors.json) asserted by BOTH suites - a one-sided
builder change now fails the other suite instead of drifting silently.
Tests: 816 frontend + 716 Python; sw cache simple-v383.
The events without a semantic builder (MT call, IMS registration, slices, ...)
are network-sourced, but the raw `envelope` action had no `src` override - the
only way to express them was the semantic set. `envelope` now accepts
`src` (82 terminal / 83 network) exactly like the `event` action; the step
editor exposes it and the authoring guide gets the row.
Also: the guide marks `por` as SCP80-only (the normaliser refuses it
elsewhere) and the semantic-events section names the shared src override.
Tests: 813 frontend + 717 Python; sw cache simple-v382.
The other-applet batch: a test script can send the common event downloads
semantically instead of raw hex.
- events.py: server-side mirrors of the PWA's event forms for Location status
(0x03), Access technology change (0x0B), Network rejection (0x12 - the
LA/RA/TA object selected by the registration group, the extended EMM cause
and the extended information object) and Data connection status change
(0x1D - status/type/TI plus the optional cause, host-clock 8.39 date-time,
location information, access technology, APN and PDP/PDN/PDU type). Fields
are validated before the run (a bad value is a script error, not a step
failure); `src` overrides the device-identities source (default terminal,
matching the Phone forms).
- The engine gains the `event` action kind; the runner builds and sends it.
The PWA's action-kind list stays in sync (drift-guarded - the guard itself
caught its own regex bug on the first run).
- Byte parity: the vectors are generated from the PWA's own builders and
pinned in tests/test_events.py; the frontend suites assert the same values.
PLMN and SCTS helpers are mirrored too.
- The Phone tab's event form shows the built "Event data (hex)" (updated as
the fields change), so every event without a builder can be used through
the raw `envelope` action.
- The test-step editor offers the event select + a JSON fields textarea;
malformed JSON refuses the save with a clear message.
- docs/test-scripts.md (the semantic-event table, an event example, the
format stamp), docs/api.md and help EN/RU updated.
Tests: 812 frontend + 717 Python; sw cache simple-v381.
The applet-testing batch agreed after v3.15/3.16:
- menu-select by text: the step names the item by `text` (+ `mode`
exact/contains, `case_sensitive`) instead of the install-dependent id
(both or neither is refused); the runner resolves it against the card's
cached menu and raises a step error naming the available items when nothing
(or several items) matches. The cache is refreshed on every SET UP MENU
fetch - the proactive chain and the expect step - so an applet install (new
items/ids after its REFRESH) no longer leaves a stale menu behind. The
step editor offers the id or the text + mode and the client-side pre-check
bounds them.
- TERMINAL RESPONSE object order: both builders emitted the Item identifier
before the Result (and the scripted builder its Text string before it too);
TS 102 223 6.8.0 mandates Command details, Device identities, Result,
Duration, Text string, Item identifier. `testscript.build_tr` and
`_menu_send_response` now follow it; `respond.raw` is documented as
appended after the standard objects, and the exact-byte tests pin the order.
- Text codings: the scripted response codes the GSM default alphabet via the
GSM 03.38 codec ('00' packed, '04' 8-bit) instead of latin-1, keeps '08'
UCS2 and sends the spec's null text string for an empty answer; the packing
helper is shared with the interactive path (`testscript.pack_gsm7`). A
UCS2 GET INPUT answer is documented and tested.
- docs/test-scripts.md gained the handoff brief for the applet agent: the
value-collection table, fill-in worksheets for the two applet scenarios, a
menu-by-text example, the validation checklist and a format stamp; help
EN/RU and docs/api.md updated.
Tests: 809 frontend + 705 Python; sw cache simple-v380.
The scripts lived in the browser's localStorage although only the local
server can run them. The new test_scripts.py store
(~/.pysim-simple-server/test_scripts.json, --test-scripts PATH) sits next to
the card presets: stable uuids and timestamps, atomic writes, and every
mutation is validated with the runner's own engine (the submitted shape is
kept - the normaliser's output is not re-normalisable: it fills defaults
like an 'any' PoR check that the check normaliser then rejects), so a stored
script cannot fail at run start for a validation reason. Import accepts the
old localStorage export as-is (export/import remains the migration path; the
PWA import also takes an exported array).
- /api/test/scripts (GET card-free: path/count + scripts), POST create,
/update, /delete and /import serve the store; the endpoints answer 503
without a store and 500 on a write failure, like the preset endpoints.
- /api/test/run takes a stored script_id (resolved when no inline script is
sent); the PWA sends the edited copy inline plus the id for the report,
and the run snapshot carries script_id.
- The PWA's Test script pill loads from the store, writes through on edits
(debounced name, immediate step/new/copy/delete), reports load/save/delete
failures inline, and exports the portable {name, steps} shape.
Tests: 808 frontend + 701 Python; sw cache simple-v379.
Three gaps blocked SCP80 applet testing from the PWA:
- testRunPreset() still marshalled the pre-v3.8 flat preset fields
(kic/kid/keys/counter/tar/spi1/spi2, all empty on a stored preset), so
every SCP80 run failed with "SCP80 preset is incomplete: no keyset is
defined". The PWA sends the matched preset's id now and the server
resolves it from the store (_test_run_preset_from_body; the inline
`preset` body stays for API callers), so a run always uses the stored
keysets, TARs and counters. Regression tests on both sides.
- testscript._normalise_params dropped the scp80 step's `kvn` (the editor
exposed it and the runner and validation consumed it, so scripts
silently used the preset's first keyset). kvn is validated (1-15) and
passed through; the client-side problem check bounds it too.
- The PoR delivered as a SEND SHORT MESSAGE (PoR-in-submit, SPI2 bit 0x20)
is decoded for the expect step that fetches it: the new `por` content
check asserts the response status / R-APDU SW / response data, decoded
with the preceding scp80 step's context (concatenated SMS accumulate
across expectations). The inline transport stays on the action
(check.por / check.data).
- The new `files` content check asserts the File List of a REFRESH
(TS 102 223 8.18, tag 12/92) - e.g. qualifier 00 = NAA initialization
with full file change notification - as an order-insensitive set of FID
paths.
The step editor exposes both checks; help EN/RU, docs/api.md and the new
authoring reference docs/test-scripts.md document the format. Tests: 803
frontend + 680 Python; sw cache simple-v378.
The presets lived in the browser's localStorage and the PWA wrote the SCP80
counter back after every operation, so an increment could be lost when the
response never reached the tab, when two browser contexts (or the demo origin
plus a local one) held separate copies, or when the tab was closed mid-run.
The counter is card state and the server performs every card operation, so the
store now lives next to the server:
- pysim_simple_server/presets.py: one JSON file
(~/.pysim-simple-server/card_presets.json, --card-presets overrides; Path.home()
resolves the same way on Windows), atomic writes (temp + fsync + os.replace),
an RLock, stable uuid ids, the ICCID normalisation the PWA uses (digits /
spaced / raw EF hex), a duplicate-ICCID refusal and a monotonic set_counter;
every counter change is appended to card_presets.json.audit.jsonl.
- API: GET /api/presets (card-free), POST /api/presets and
/api/presets/update|delete|import; the import also takes the old localStorage
export shape - that is the migration path, there is no automatic migration.
- Operations name a preset_id and the server persists the counter they
consumed: /api/send-ota (the plain SCP80 path reports final_cntr now too),
the RAM install/install-app/compat steps (_ram_send_gp_apdu) and the test
runner's SCP80 steps (_preset_counter_persist).
- PWA: the Cards tab is a thin client of the store (load on connect, CRUD via
the API, export/import), the counter write-backs became local display syncs
(the SCP80 form's counter input stays a deliberate human edit) and the
SCP80/RAM bodies carry preset_id; a new pysimFetchOk() surfaces the server's
error bodies instead of silently using them.
Policy: card keys in logs and local files are not a risk for this workbench
tool (AGENTS.md) - the store is not encrypted and no redaction is required.
Tests: tests/test_presets.py (store: ICCID forms, counter monotonicity and
wrap, atomic writes, corrupt-file recovery, import), the preset endpoints in
tests/test_fetch_routes.py (incl. the counter persistence via /api/send-ota)
and the updated PWA tests. sw cache simple-v334.
BIP was only reachable through the SCP81 listener; cards that use TCP for
other purposes (an applet's OPEN CHANNEL, a push trigger without HTTP OTA)
now get a generic control surface, independent of SCP81.
Server:
- /api/bip/control starts a plain BIP session in three modes: sink
(default; a local TcpDumpServer that accepts the card's channels and only
logs conn/sink-rx/conn-close, never answering; port 0 = ephemeral, the
bound port is reported), passthru (dial the OPEN CHANNEL destination, TCP
client only) and redirect (fixed host:port).
- /api/bip/status returns {owner, bip, listener}; /api/bip/log and
/api/bip/log-clear share the BIP event log with /api/scp81/log.
- The session state is shared with the SCP81 listener and only one session
runs at a time: _bip_session_stop() stops whichever control started it
and the control responses report it as `replaced` (both directions,
SCP81 <-> BIP). State renamed _SCP81_MODE/_TARGET/_LISTENER/_LINK_EVENTS
-> _BIP_* plus _BIP_OWNER.
- TcpDumpServer logs conn-close and counts accepted connections; stop()
joins the accept thread so the port is really free on restart.
- The new control endpoints are blocked during test-script runs.
PWA:
- New Simulator pill BIP (after TR Config): mode select with notes,
Host/Port rules, Start/Stop with a "replaced" notice, status line (mode,
bound address, owner, channels), a Channels box and the shared BIP log
(reuses the SCP81 log renderer, now showing `peer`).
- The SCP81 status line marks a session owned by the BIP pill.
Help EN/RU 8.10, docs/api.md, AGENTS; CAT_TP/UDP recorded as not
implemented.
Tests: tests/test_bip.py (9) and frontend/tests/bip.test.js (6).
600 frontend / 482 Python green; version 3.6.0; sw simple-v273.
- The SCP80 counter write-back resolves the preset by NAME first (the run
snapshot prefers the name) and only falls back to an ICCID-looking
value: presets with digits in their names are found after a page reload
(the previous fallback ran cardsNormIccid on the name and gave up).
- testScriptProblem validates the *selected* SCP80 source (like the form
and the server), so source=apdu with only sp filled is caught locally
instead of failing with a server 400.
- _test_run_start cleans up (_TEST_RUNNING=False, run state error) when
the worker thread cannot be created/started, and the endpoint answers
500 with a clear error instead of leaving the card blocked behind a run
that never started.
- The 5 s poll writes the counter back whenever a run is finished
(idempotent), so a reloaded page saves it without visiting the pill.
- Mask wildcards ('?') are stripped from the hex fields that cannot carry
a mask (APDU, secured packet, event/file data, TAR/SPI overrides, DCS,
extra TLVs); check values keep them.
Tests: frontend +3 (write-back by name / by ICCID, mask-free fields,
source-aware script check), Python +1 (failed thread start unblocks).
586 frontend / 471 Python green; version 3.5.14; sw simple-v267.
PWA:
- The SCP80 "Source" switch sticks: the choice is stored in
`params.source`, switching keeps both values (the server honors the
explicit source when both are present, and the form validates only the
selected one).
- The SW check field is empty by default (placeholder "default: 9000
(91?? when polling)"), so the server defaults apply - previously the
pre-filled 9000 defeated the polling STATUS default and a card that
announced a command made the step fail with "expected 9000".
- Item text checks offer contains/exact only (the mask mode was rejected
by the server validation).
- The SCP80 counter is written back to the preset even when the run is
observed after a page reload or was started elsewhere (resolved by
ICCID or preset name from the run snapshot).
Server:
- Step-entry mutations happen under `_TEST_LOCK`
(`_test_entry_update`/`_test_finish_entry`): the status endpoint
serializes the state with json.dumps, so entries must not change while
it iterates them.
- The pending-command drains (unexpected command, error, stop) hold
`_CARD_LOCK` like every other card conversation.
- `_int` accepts plain decimals with leading zeros and 0x hex.
- The scripted TERMINAL RESPONSE text string uses the CR-set tag `8D`
(consistent with the other TR TLVs; both are legal).
- A script-driven menu selection mirrors `server.menu_active`.
Tests: frontend +4 (source switch/round-trip, status SW default, render
checks, item modes), Python +3 (integer parsing, menu_active, source
selection). Help/docs unaffected beyond api.md's `source` note.
583 frontend / 470 Python green.
- Top-level tab label "Phone simulator" -> "Simulator" (RU "Симулятор");
the DOM ids stay tab-phone / phone-sub-*.
- The eSIM pill inside the Simulator tab is now "eSIM LPA" (EN/RU),
matching the section's ES10/LPA scope; the panel id stays phone-sub-esim.
- Documentation updated: help EN/RU (section 8 Simulator, list of the four
pills including Test script, tab lists and cross-references),
README/README_RUS (tab lists, Simulator section, eSIM LPA), AGENTS
repo/root.
- Test titles updated; version 3.5.12; sw cache simple-v265.
579 frontend / 467 Python green.
- Phone simulator tab: a fourth pill **Test script** - script list
(localStorage `simple_tests`) with New/Clone/Delete/Export/Import, a
form-based step editor (modal) and a run panel.
- Actions: ENVELOPE (Event Download), Menu Selection, UPDATE/READ file by
path, raw APDU, SCP80 (C-APDU or a pre-built packet, per-step TAR/SPI1/
SPI2 overrides; the preset is matched by the equipped card's ICCID and
the final counter is written back) and STATUS with an optional poll.
Every action has SW/data checks (exact or ?-mask) and a PoR check for
SCP80, with an error (stops) / warning (continues) fail level.
- Expectations: FETCH the command announced by the previous step, verify
type/qualifier/text/item/raw and answer with the scripted TERMINAL
RESPONSE (result, item id, text, extra TLVs); templates for STK menu
browsing and an applet via SCP80.
- Run panel: progress banner (plus a marker on the pill), per-step
OK/Warning/Error badges with expected vs actual values, sent/received
hex, decoded commands and the SCP80 counter; 500 ms polling while a run
is active, and a running script disables other card controls
(data-needs gate; the server answers 409 to other card endpoints).
- Help EN/RU (8.9 Test script), AGENTS; version 3.5.11; sw simple-v264.
579 frontend / 467 Python green.