Show the established animate-spin throbber next to the Check filesystem
button while the scan (tree load + probe-all + report) is in flight, and
hide it when it settles. Frontend 1126 tests green.
Rework the FS check from FID-level to path-exact:
- SERVICES_FS_MAP now maps a feature to symbolic roles (registry names),
not FIDs; the (name, root) -> path index resolves each role to its
unambiguous canonical path (same-FID files in other DFs - EF.SAI vs
EF.PBR, EF.AST - resolve separately).
- Per-tree declaration: SST -> MF, UST/EST -> ADF.USIM, IST -> ADF.ISIM,
with a DF.TELECOM fallback for USIM/ISIM roles whose DFs live under
DF.TELECOM (MMS storage, MCPTT, V2X, A2X), except the GSM-application
mapped copies (ADN/EXT1/ECCP).
- The report carries the path (and declaring tree) for missing and
present-but-undeclared, plus an honest "unchecked" bucket for roles
with no path yet (DF.GRAPHICS/IMG, SIM DF.SoLSA/MExE copies,
EF.3GPPPSDATAOFFservicelist).
- efDfFromPath maps A000000087100B -> ADF.USIM-NON-IMSI.
Frontend 1126 + Python 1091 tests green. (Version/sw bump still
deferred for the decoded-editing batch.)
Add a supplement to the cardless registry generator for the 13 files the
Services editor's FS check references but pySim does not emit (optional,
service-gated): EF.LRPLMNSI/IAL/IPD/3GPPPSDATAOFF/TVCONFIG, DF.MExE +
EF.MExE-ST/ORPK/ARPK/TPRPK, DF.SoLSA + EF.SAI/EF.SLL, DF.BCAST,
DF.ACDC + EF.ACDC_LIST, DF.TV, and DF.A2X + EF.AST/EF.A2X_CONFIG (under
DF.TELECOM). Emitted with distinct (name, path) so same-FID files in
other DFs (EF.SAI vs EF.PBR, EF.AST) stay separate; ADF.USIM rows are
mirrored to ADF.USIM-NON-IMSI. 490 -> 527 entries.
Regenerated the asset; test_uicc_files.py pins the 20 new paths, the
FID-collision cases and (name, root) uniqueness. 1091 Python tests.
Add a "Check filesystem" action to the Services pill:
- SERVICES_FS_MAP: the feature -> FID list transcribed from
docs/services-editor.md §A (127 file-backed + 34 capability features,
157 distinct FIDs; a completeness/data test pins it).
- servicesFsReport: pure two-direction check - declared features whose
mapped files are absent, and present files whose features are
undeclared. FID-level (not DF-exact) for this first pass; documented.
- The Check runs the tree probe (once per session) and renders the
missing/extra lists or the "matches" line, with an FID-level note.
Also fix a doc typo (eCall Data's EF.FDN was written as 6F3A = EF.ADN).
Frontend 1120 + Python 1090 tests green. (Version/sw bump still
deferred for the decoded-editing batch.)
Add a Services pill to the Card reader tab that shows the card's service
tables side by side:
- SERVICES_CROSS_REF: the 161-row feature cross-reference (SST/UST/EST/IST
numbers + class) transcribed from docs/services-editor.md §A; a
completeness test pins every non-RFU service number to exactly one row.
- Reads the four files (DF.GSM/6F38, ADF.USIM/6F38, ADF.USIM/6F56,
ADF.ISIM/6F07) via /api/read, cached per card session; a column renders
only for a present file (no ISIM -> no IST column).
- SST cell is on/allocated-off only (never de-allocates); UST/EST/IST are
checkboxes; per-file Save buttons write via /api/write, preserving
unmodelled bits and reporting each result.
Frontend 1105 + Python 1090 tests green. (Version/sw bump deferred;
FS-check is the next step.)
Give EF.SST its own per-file editor for the 2-bit SIM service table
(TS 51.011 10.3.7: allocated + activated per service):
- efEncSst encodes the pair layout, preserving unmodelled pairs and the
file length; validates service number, fit and state.
- A new `bits2` field kind renders a three-state select per service and
collects {n, name, allocated, activated}; registered as EF_SST_FIELDS
with the encoder (df '7F20'), and SST leaves EF_READONLY_NAMES.
- i18n: Deleted/Off/On + the state-error and hint strings (EN/RU).
Docs/help updated: SST is no longer read-only. Frontend 1093 + Python
1090 tests green. (Version/sw bump still deferred for the unreleased
decoded-editing batch.)
Make the DF context from the resolver actually reach the live views:
- pysimFsSelected now stores the canonical path (pysimFsNodePath), not
the node name; pysimFsRenderNode emits the path in the click/toggle
handlers; every consumer resolves via pysimFsFindNodeByPath. The
name-based pysimFsFindNode is removed - two same-named files under
different DFs (EF.ECC/EF.AD, DF.PHONEBOOK copies) are now distinct.
- efDfFromPath maps ADF-rooted parent segments by RID (A0000000871002
-> ADF.USIM, A0000000871004 -> ADF.ISIM), so snapshot/profile paths
rooted by AID resolve to the same DF key as the named form.
- efDecEcc shares efEccCategory with efDecEccUsim (TS 24.008
Table 10.5.135d), so the SIM and USIM category displays can't drift.
Frontend 1087 + Python 1090 tests green. (Version/sw bump deferred -
this is part of the unreleased decoded-editing batch.)
The decoder/encoder lookup now takes an optional DF context and resolves
DF-first, then name, then FID (the spec keys files by FID within their
DF; the name is a display label). efDfFromPath derives the parent DF
segment of a canonical path.
EF.ECC is split into its two DF-specific decoders: DF.GSM (7F20)
transparent 3n (existing) and ADF.USIM linear-fixed X+4 - the new
efDecEccUsim reads the 3-byte Emergency Call Code, the Annex A alpha and
the Emergency Service Category (decoded per TS 24.008 Table 10.5.135d).
EF.AD is registered as DF-keyed entries (7F20 / ADF.USIM / ADF.ISIM),
reusing the existing encoder.
The df is threaded through the file-manager decoded-view/edit/save and
the snapshot/compare decode. No ECC editor yet (read-only).
Frontend 1081 + Python 1090 tests green.
The PWA's EF_UST_SERVICES stopped at 146; TS 31.102 §4.2.8 pins 147-150
(5MBS UE pre-configuration, Operator Controlled Signal Threshold per
Access Technology, A2X, IMS Data Channel Indication). Add them and fix
the two test fixtures that assumed the 146-service table.
Resolve the three Services-editor FS mappings against the spec:
- eCall Data (89) -> EF.SDN 6F49 (+ EF.FDN 6F3A)
- eCall Data over IMS (112) -> EF.SDNURI 6FEF (+ EF.FDNURI 6FED)
- EARFCN list for MTC/NB-IOT (121) -> EF.EARFCNList 6FFD; "Extended
EARFCN support" (100) is a capability, not a file.
Record the FID-first rule: the resolver and FS check key by (DF, FID),
the symbolic name is only a label; and clarify that the FS check is
card-only (independent of the cardless uicc_files.json list).
Feature cross-reference (SST/UST/IST/EST) and feature->filesystem
mapping for the planned cross-file Services editor. Reconciliation
exact: 160 feature rows, every service number exactly once.
Flags for review: UST list is 4 short of the spec (147-150 missing
from EF_UST_SERVICES), GSM Access gates DF.GSM-ACCESS 5F3B (not DF.GSM),
CCP/CCP2/ECCP equivalence, and three unresolved file mappings.
The first line was hardcoded to http://127.0.0.1:8080 while the flags
were passed through, so ./start.sh --http-host 10.0.28.161 printed
127.0.0.1 although the server bound 10.0.28.161 (its own listening banner
named it). Both start scripts now parse --http-host/--http-port (space
and = forms, the last value wins like argparse) and echo the effective
address; start.bat parses with shift (%* is unaffected, so the server
still receives every argument).
Bump 3.38.2 (server, pyproject, PWA header, sw simple-v483) and add the
CHANGELOG entry.
Tests: tests/test_pcsc_startup.py pins the parsing/echo (2 new).
1090 Python + 1075 frontend.
A headless/SSH box can show a world-writable /run/pcscd/pcscd.comm
(srw-rw-rw- root root) and still be denied: pcscd enforces its PolicyKit
policy, whose defaults allow only active local sessions. The startup
report, the start.sh pre-flight and the README troubleshooting now name
the exact rule (99-pcscd-allow.rules, org.debian.pcsc-lite.access_pcsc
and access_card) and explain why the socket mode does not matter. The
pcscd-group instruction is gone: there may be no such group, and it is
not the gate. The group route stays only as a note for a pcscd built
without PolicyKit.
Tests: tests/test_pcsc_startup.py asserts the polkit action IDs, the
rule filename and the 0666-socket explanation.
1088 Python + 1075 frontend.
A failed venv creation (e.g. the Debian/Ubuntu python3-venv package
missing) left the .venv directory behind; the next run saw it, skipped
creation and failed to source/activate a venv without activate/pip.
setup.sh now validates bin/activate, bin/python and bin/pip, removes an
incomplete directory and creates it again; a creation failure removes the
partial directory and names the venv package. setup.bat does the same
with Scripts\activate.bat/python.exe/pip.exe and !errorlevel!.
READMEs (prerequisites + scripts table), CHANGELOG (folded into the
unreleased 3.38.1) and AGENTS updated.
Tests: tests/test_setup_scripts.py (5: the shell/bat source pins and
bash -n). 1088 Python + 1075 frontend.
When no reader was named and readers() failed (e.g. SCARD_E_NO_ACCESS -
the user may not use pcscd), the auto-detect swallowed the error and pySim
fell back to the Serial transport, failing on /dev/ttyUSB0 while the
server still printed "listening". The startup now probes PC/SC,
classifies the failure by hresult (access/service/reader), reports the
cause with the pcscd group/polkit and pcsc_scan guidance and exits 2; the
python -m guard uses sys.exit(main()). An explicit -d/--device keeps the
serial path; a reader without a card stays a normal cardless start; a
readerless start exits with its own message.
start.sh runs the same access pre-flight before launching (readers() with
a 3x retry for socket activation; -d is detected and skips it). READMEs,
CHANGELOG (folded into the unreleased 3.38.1) and AGENTS updated.
Tests: tests/test_pcsc_startup.py (16: the error classifier, the probe,
the report guidance, the serial-arg detection and the source pins).
1083 Python + 1075 frontend.
pySim's scc.read_binary/read_record call select_path(ef) first, so the
ICCID, network-state, EF.ARR and EF.PBR readers - which had already made a
parent-scoped selection and hold its FCP - sent a second SELECT per file
and per record. _read_selected_ef() issues READ BINARY (chunked at
max_cmd_len from the FCP file size) / READ RECORD (b2<rec>04<len> at the
FCP record length) directly; the pySim helpers stay as the fallback when
the FCP lacks the value.
The startup TERMINAL PROFILE drain now breaks on the chain's idle SW
instead of polling STATUS once more.
Measured on the last startup: ~3.4 s traced init (netstate/ARR/PBR 271
APDUs / 1.26 s incl. 169 SELECTs; the card's own TP processing 1.58 s);
the change removes ~0.7-0.8 s of the read phase without touching any data
or the TP.
v3.38.1; tests: tests/test_read_selected.py (direct reads, chunking, the
fallback and the drain source pin) and the ARR/PBR/ICCID/netstate fakes.
1067 Python + 1075 frontend. sw cache simple-v482.
The test-run banner (visible on every Test-script view) now carries the
same live stats as the script head and the suite summary: elapsed so far,
the average per completed iteration and the estimated time left. The
text is built by a pure helper (testRunBannerText) so it is testable;
testRender only calls it while a run is active, and non-load runs keep
their position text (step or suite-member progress).
The existing banner-source assertion moved to the helper. Tests: the
banner text for a load suite and a plain run. 1061 Python + 1075
frontend; sw cache simple-v481. CHANGELOG/help/docs/AGENTS updated.
The script-run head and the suite summary now display, while a load run
repeats: the elapsed time so far, the average per completed iteration and
the estimated time left (testLoadTiming / testDurationText /
testIterAvgText), recomputed on every 2 s status poll. A finished run
keeps the server's final elapsed_ms/avg_ms (no estimate), and before the
first completed iteration only the elapsed time is shown - no bogus
average or ETA.
The timing is client-side over the existing load block (started, done,
iterations), so it works with the running server as well; no server
change is needed.
Tests: the timing math and the two formatters, the script head render and
the suite summary render (3 new). 1061 Python + 1074 frontend.
sw cache simple-v480; CHANGELOG [3.38.0], help EN/RU,
docs/test-scripts.md and the AGENTS notes.
Both layers: the server's _TEST_LOAD_MAX and the PWA's
TEST_ITERATIONS_MAX plus the two input max attributes. The value stays
inside 2^53-1 (the JavaScript safe-integer range), so the counters and the
progress display survive the JSON/JS round trip exactly; the practical
ceiling is the run duration (50 ms/iteration ~1.6 years at the cap).
The server's range error names the new cap automatically. The frontend
tests read the cap from index.html instead of stubbing a literal, and
clamp at it (above the cap, exactly at it, and the input max).
Docs: api.md, test-scripts.md, help EN/RU, the CHANGELOG (folded into the
unreleased 3.38.0) and the AGENTS notes. sw cache simple-v479.
1061 Python + 1071 frontend.
The UPDATE file / READ file step editor's path field gains the selector
the APDU builder's SELECT rows use: the loaded file-manager tree, the
custom files and the standard uicc_files.json list, merged by
rfmFileEntries, grouped by root with a filter. Picking a file fills
test-f-path; the text field stays authoritative (a name path or an
unlisted file can still be typed) and the current path is preselected
when known. Only EFs are offered - the action reads/writes a file, so a
DF/ADF is not a target.
v3.38.0: server.py VERSION, pyproject.toml and the PWA header;
sw cache simple-v478 (help/index changed); a CHANGELOG [3.38.0] section,
the help EN/RU, docs/test-scripts.md and the AGENTS notes.
Tests: frontend/tests/testscripts.test.js - the option list/grouping, the
EF-only filter, the filter input, the pick->path fill and the form's
picker render for write and read. 1061 Python + 1071 frontend.
The PLI answer used the 5-byte form 13 05 <MCC/MNC> <LAC> with LAC 0000
and no Cell ID. That is the *Network Rejection event download* form
(TS 31.111 8.19.1); a PLI location for GERAN is 7 bytes - MCC/MNC, LAC
and Cell ID. A card applet that re-reads its location (the Alfa
MultiIMSI applet, after a *LOCI status change) treated it as empty and
re-requested PLI on every STATUS tick.
The default is now 13 07 00F110 0001 0001: PLMN 001/01 (the test
network), LAC 0001, Cell ID 0001. _build_tr injects it after the
Result; the TR data is pinned by a test.
While verifying a live PLI, the manual /api/status-poll chain turned
out to be unbounded: it looped on the PLI storm while holding
_CARD_LOCK, so the request never returned and every card operation
waited. _handle_proactive_chain's max_rounds now defaults to
_POLL_CHAIN_MAX (the background poll always passed it; the manual poll,
ENVELOPE, menu and TP chains did not), and the manual poll passes it
explicitly. A card that re-queues a command can no longer wedge the
card lock from any call site.
Tests: the PLI default + the built TR, the default chain bound and the
manual-poll call pin. 1061 Python + 1069 frontend; sw cache simple-v477.
A card that stops answering (no SW, no driver error) leaves the runner
inside a blocking SCardTransmit while it holds the card lock - Stop
cannot take effect, the presence monitor waits for the lock and a load
test hangs in that iteration without explanation.
The runner now beats a progress heartbeat (each step start/finish and
each attempt of a bounded STATUS/drain loop); after _TEST_STEP_TIMEOUT
(30 s) the watchdog flags the run `stuck`, records the step and the idle
time, sets the stop flag and tries to cancel the blocked transmit by
disconnecting the PC/SC link on a daemon thread. The disconnect must
run in the background: pcsc-lite serializes it behind the pending
transmit (both take the per-context lock), so a synchronous call would
block the watchdog instead of cancelling anything. Where a platform
does cancel, the step fails through the ordinary on_fail path; either
way the finalisers map `stuck` to `error` - a stuck card is not a user
stop - and the pending drain is skipped on the dead link. The
transport is marked stale once the disconnect returns, so the next equip
rebuilds it.
The run status gains `stuck` and `heartbeat`. Help EN/RU,
docs/test-scripts.md, docs/api.md, the CHANGELOG (folded into the
unreleased 3.37.0) and both AGENTS describe the behaviour and the
background-disconnect caveat; the sw cache is simple-v476 (help is
precached).
Tests: tests/test_test_watchdog.py (17: the check rule, the abort, the
final status of the single/load/suite runs and a blocked-runner
integration case). 1058 Python + 1069 frontend.
POST /api/test/run takes an optional iterations count (the PWA's
Iterations field): the run repeats, successful iterations are discarded
(steps/log cleared, only load.done and the timing grow), the first error
stops the loop and keeps that iteration's report, a stop ends it after
the current iteration, and a card session change between iterations is
an error. The SCP80 counter state is shared across iterations, so
packets continue from what the previous run consumed.
The status gains a load block (iteration/done/failed_iteration/
elapsed_ms/avg_ms; disabled for a normal run). The suite runner was
split into a per-iteration body (_test_suite_execute_once) + the
single-run finaliser, and two load workers own the loops.
Review fixes: a raised runner error finalises as error (no stuck
running state), a suite-internal session change is an error, a stopped
member ends the suite loop, the final status keeps a warning from any
iteration, avg_ms covers the attempted iterations, and each suite
iteration's counters_before is read fresh.
1041 Python + 1069 frontend; simple-v475.
The card's contact pad sat mid-card; it now sits near the bottom edge,
away from the top-right cut corner (the cut side is the card's top).
No other geometry or animation changes.
1024 Python + 1063 frontend; simple-v473.
Redraw the inline card-into-reader SVG after sim.svg: a rounded card
with the top-right cut corner and the 3x2 contact pad, sliding behind
the reader bar. The viewBox grew to 72x48 and the resting position is
0, so the card is no longer clipped at the top; the insert slide is
20px. The currentColor/Tailwind theming and the reduced-motion
fallback are unchanged.
1024 Python + 1063 frontend; simple-v472.
A delete run whose DELETE answered 6A88/6A82 is still a success (the
object is gone, the goal is met) but it is no longer reported as a plain
"OK": the attempt result is 'already_absent', the counts gain that
bucket ("Deleted: N - Already absent: M"), the verdict reads "Already
absent" and the notice says the card was already absent. A reinstall's
tolerated pre-delete keeps the install outcome (the install is the
point).
1024 Python + 1063 frontend; simple-v471.
The waiting phase is the only moment the operator must act, so it now:
- shows a card-into-reader SVG animation in the status modal (inline
SVG, CSS keyframes in src/style.css; reduced-motion users get the
static card-above-the-slot picture);
- plays a short Web Audio chime once per card, edge-triggered in the
2 s poll so it never repeats (the AudioContext is unlocked by the
Start click);
- sets the tab title to "Insert next card" while the window is hidden;
- offers a speaker toggle next to the status (persisted as
simple_ram_batch_sound, on by default).
The prebuilt style.css was rebuilt: it had gone stale and lacked
utilities the UI already used (bg-blue-50 on the batch notice,
inline-flex, mx-2, max-h-48, w-44, min-w-24).
1022 Python + 1062 frontend green; simple-v470.
v3.34.0 was committed but never tagged; its changes ship in the v3.35.0
release, so the changelog names only released versions (the same rule
applied to 3.32.3 -> 3.32.4 and 3.32.6/3.32.7 -> 3.33.0).
The Read decoded pill used the same grey fill as a disabled control, so
an enabled pill looked unavailable (reported live). The inactive-but-
enabled state is now an outlined button (blue on white/slate) and the
grey fill plus dimming are reserved for the actually disabled state.
The active pill keeps a border of the same width so the row does not
shift.
Pinned in fs_edit.test.js. 1022 Python + 1058 frontend; simple-v469.
TS 31.102 4.2.27 scope: alpha; TP-DA and TS-SCA with their own length
conventions (DA = digits, SCA = octets, 0 = default SMSC) and a TON
select; TP-PID; TP-DCS with TS 23.038 hints; the relative TP-VP with the
TS 23.040 ladder; a per-parameter "absent (the ME default applies)"
state. Unchanged slots are copied verbatim (the live fixture's
DCS-absent 00 and present VP FF survive), alphanumeric TON-5 addresses
stay read-only, and every checked record is validated before any write.
Decoder fixes: the parameter-indicator bits were shifted (SCA on 5,
PID on 2, DCS on 3, VP on 4; the spec is DA/SCA/PID/DCS/VP on bits 1-5
with 6-8 reserved, 1 = absent), and the TP-DA length was read as octets
instead of digits (TS 23.040 9.1.2.5).
Live fixture: ffffffffffffffffffffffffffe9ffffffffffffffffffffffff07919733824009f0ffffffff0000ff
(SCA +79332804900, PID 00, DCS absent/00, VP FF) round-trips
byte-identically.
1022 Python + 1058 frontend green; simple-v468.
New client-side editors: the service tables EF.UST/IST/EST (a filterable
bit grid; unmodelled bits and the file length preserved), EF.ACC,
EF.PHASE, EF.HPPLMN, EF.AD (op-mode enum, additional info, MNC length
incl. 0 and the 3-byte SIM form), the PLMN-only lists
EF.PLMNsel/FPLMN/EHPLMN, EF.ICCID/EF.IMSI behind a confirmation, and the
cell-broadcast lists EF.CBMI/CBMID/CBMIR.
Framework: a bits form kind, a per-entry model(dec) hook, a per-entry
confirm prompt and hex fields.
Decoder fixes: EF.ACC class numbering (byte1 b8..b1 = 15..8, byte2 =
7..0), EF.AD operation mode (whole byte) and its MNC length rules,
EF.SST decoded with the SIM 2-bit allocated/activated layout (editing
stays disabled until that UI exists), and the EF.IMSI parity nibble
(9 odd / 1 even). A no-edit save of the new list files no longer
writes all-FF (the collected rows and the encoders disagreed on the
value shape).
Decoded editing for EF.DIR/EF.ARR/EF.SMS/EF.PBR is postponed by
decision (read-only viewing is enough); the survey records it.
1022 Python + 1033 frontend green; simple-v467.
Both versions were committed but never tagged; their changes ship in the
v3.33.0 release, so the changelog names only released versions (the same
rule applied to 3.32.3 -> 3.32.4).
The file manager gains a client-side decoded editor. EF_ENCODERS pairs
each editable EF with a pure encoder and a form schema (fields / list /
items); the file is read raw, the decoder fills the form, the browser
re-encodes it and the existing update path writes it.
Shapes: SPN (display-condition bits + Annex A text), PLMNwAcT/
OPLMNwAcT/HPLMNwAcT (rows with PLMN + ACT, add/remove),
ADN/FDN/SDN/MSISDN/LND/BDN (one form per record with a checkbox that
auto-checks on change; BCD, length and EXT handling; BDN's own X+15
layout) and PNN (per-record TLV items 43/45/80, add/remove, unknown
TLVs preserved).
Unchanged fields are copied verbatim (SSC/extended-BCD numbers, PLMN
rows including unmodelled satellite ACT bits, PNN codings/AddCI bits,
all-FF files, the BDN comparison-method byte), every checked record is
validated and encoded before anything is written, and edits that would
break a reference are refused.
Also: docs/decoded-editing.md (the spec survey behind the design and
the release waves), the efPlmnAct EC-GSM-IoT-only 0x88 fix, the PNN
spare-bit decode fix, and the /api/write|read cmd2-exception
false-success fix.
1022 Python + 1013 frontend green; simple-v466.
The decoded view is the client-side EF decoder's table over the raw
read's bytes. The collapsed "pySim JSON (server)" block was an
unrelated second decode - opening it ran another /api/read with mode
'decoded' (pySim's read_binary_decoded/read_records_decoded on the
card) - and it existed only when our decoder covered the file, so it
could not serve as a cross-check where one would matter.
/api/read keeps mode 'decoded' for API callers. Help, READMEs and the
project notes now state the table is built in the browser from the same
raw bytes; ef_decode.test.js pins the view's output and that no
mode 'decoded' read remains in the PWA.
1016 Python + 955 frontend green; simple-v465.
ramOpChanged() (v3.28.0) un-hid #ram-preset-warning by the operation
kind, but the banner's text and visibility belong to
spPresetWarningRender() (which already suppresses it for the batch
ops). With no warning text the empty amber box stayed visible until
the next status poll re-hid it - a flash on every RAM view / operation
switch. The visibility is content-driven again: the setHidden line is
gone and the renderer is called.
Pinned in frontend/tests/ram.test.js with the real renderer (the
pre-fix code fails the test). 1016 Python + 954 frontend green;
simple-v464.
The v3.32.3 changes (the traceback tail and the link-release wording)
were committed together with v3.32.4 and never tagged separately, so
the changelog named a version no release exists for. The entry is
merged into [3.32.4] and the published v3.32.4 release body matches it.
Live log: a card was pulled while the server started and the TERMINAL
PROFILE send raised CardConnectionException 0x80100016
(SCARD_E_NOT_TRANSACTED). The init's except caught it (no crash), but
the code then continued with the card object it had already built:
card_present and _CARD_CONNECTED became True - a phantom equipped card -
and the auto-equip's _CARD_CONNECTED early return then skipped every
later insertion, leaving only a manual Equip or a restart.
A PC/SC error in the startup init now prints one line (a traceback is
noise for a normal removal), clears the locals that feed those flags and
calls _clear_app_card_state(app); the presence monitor equips the next
card. Non-PC/SC errors keep their traceback.
Pinned in tests/test_startup_cardless.py (the branch and the flag
sources). 1016 Python + 953 frontend green; simple-v463.
v3.32.2 fixed the endless re-arm, but on Windows the first attempt
still failed on the old link (seen in the confirmation log: the
"self._con.connect()" traceback) and only the retry recovered - a failed
init, a 2-4 s delay and a traceback in the log.
The monitor's insert path now marks the transport stale under
_CARD_LOCK, so the first equip attempt rebuilds the PC/SC link. It also
covers a link created at a cardless startup (never connected). The
rebuild still happens in the equip worker, never in the pyscard monitor
thread, and _ensure_transport releases the old link first.
Docs: the READMEs' troubleshooting bullet.
Tests: an insertion marks the transport for the rebuild.
1014 Python + 953 frontend green; simple-v462.
After a card swap (or the first insertion after startup) on Windows the
auto-equip looped forever: "card inserted, initializing" / "card gone
during initialization", with no "retry 2/3" lines.
An equip with the card still present produced no card (app.card None),
and that branch reported "card gone" and returned True - a no-op success
- so the three retries never ran and the watchdog re-armed every ~5 s.
- The card-gone no-op is now only for a real removal (card_present
False); a card-present init that produced no card raises a failure.
- Any failed attempt marks the transport stale, so the next attempt
rebuilds the PC/SC link (a Windows swap can leave the old link unable
to connect; _ensure_transport releases the old link before building
the new one, so the 2026-09-24 stale-handle bug cannot recur).
- The error pySim swallowed (cmd2) is printed as "AUTO-EQUIP: init
output:" with its traceback - the failure is diagnosable from the log.
- Docs: the READMEs' troubleshooting bullet.
- Version strings + the PWA cache bumped together (simple-v460).
Tests: a card-present init without a card is a failure, a mid-init
removal stays a silent no-op, any failure marks the transport, the
stale-handle traceback reaches the log, and an end-to-end retry: a
failed init, the old link released, a fresh one built, the retry
succeeds. 1012 Python + 953 frontend green.
The import created presets with the store's default counter (1). Cards
whose counters are already past it had to be edited preset by preset
afterwards.
- The outfile preview form gains a "Start counter value" field (default
1, the store's value). The value is written into every created keyset
of every imported card - parse_outfile's `start_counter`, 1-10 hex
digits, whitespace stripped and uppercased; a bad value raises and the
endpoint answers 400. Without it the store's default still applies
(the field is omitted), so the API keeps its old behaviour.
- The preview shows the counter; an import stores the protocol's
10-digit form (counter_hex).
- Docs: api.md (the field), help EN/RU (the form).
- Version strings + the PWA cache bumped together (simple-v459).
Tests: the parser (every keyset, normalisation, unset, the bad values),
the endpoint (the preview's keysets, the 400, the stored padded form),
the PWA (the field's default, the controls' normalisation, both bodies).
1008 Python + 953 frontend green.
Reinstalling the package was slow because setup rebuilds pysim's
prerequisites (pyscard compiles from source on Linux), while a clone
updates with a plain `git pull`.
- tools/update.py (+ update.sh / update.bat in the package, and the
release workflow shipping them) updates a packaged install in place:
it downloads the latest release package, extracts it (zip-slip guard,
permission bits restored - zipfile drops them), replaces the app files
over the app directory and re-installs the app into the existing
.venv. The .venv and simple_lastrun.log are not in the package, so
pysim and its prerequisites survive: seconds, not a rebuild.
--check / --version / --zip (offline) / --no-pip / --force; the same
version is a no-op.
- CHANGELOG.md (Keep a Changelog, from the first packaged release;
earlier history is the git log). The release workflow now uses the
version's CHANGELOG section as the release body
(tools/changelog_section.py, falling back to generated notes).
- The READMEs document the update path and the `git clone --depth 1`
option for the clone path; setup prints the update hint.
- Version strings + the PWA cache bumped together (simple-v458).
Verified: 1007 Python + 953 frontend; the release-package test covers
the new files; the updater was exercised end to end (a v3.31.2 install
with a real venv updated to the 3.32.0 package: files replaced, the
venv marker kept, pip metadata 3.32.0, exec bits kept, a second run a
no-op, --check correct).
ssl's TLS-PSK callbacks (PskTlsServer) exist only since Python 3.13 - the
first CI run on 3.12 errored in all 20 PSK tests, and the docs confirm
"Added in version 3.13".
- PskTlsServer._make_context raises a clear RuntimeError naming the
requirement instead of AttributeError; a source pin keeps the guard.
- tests/test_scp81.py skips PskTlsServerTest where the callbacks are
missing (it runs on 3.13+), so contributors on 3.9-3.12 no longer see
20 failures.
- The READMEs state the requirement, and the Windows notes now strongly
recommend Python 3.13: pyscard has wheels up to 3.13, and on 3.14
pyscard and some pysim dependency wheels are unavailable and build
from source (MSVC). setup.bat and setup.sh print the same guidance.
- Version strings + the PWA cache bumped together (simple-v457).
998 Python + 953 frontend green.
The v3.31.1 run's frontend suite failed on sp.test.js: require('des.js')
(aes-js likewise) needs the npm dependencies, which exist in a working
tree but not on a fresh runner. The workflow now runs npm ci in
frontend/ (the lockfile is checked in) before node --test.
The v3.31.1 run's Python suite failed: ssl.SSLContext.set_psk_server_callback
(SCP81's PskTlsServer) was added in Python 3.13 - the job used 3.12 and all
20 SCP81 PSK tests errored. The workflow now pins 3.13; the package itself
is the tagged source and is unaffected by this CI-only change.
The first release run failed installing the test dependencies:
pyscard 2.3.1 publishes macOS and Windows wheels only (no manylinux),
so on Linux it always builds from source and needs the PC/SC headers -
the job stopped at "winscard.h: No such file or directory".
- The release workflow installs libpcsclite-dev before the dependencies.
- The READMEs and setup.sh name the header package next to pcscd/ccid:
libpcsclite-dev (Debian/Ubuntu), pcsc-lite-devel (Fedora), included
in Arch's pcsclite. This affects every Linux user running setup, not
just CI.
- Version strings + the PWA cache bumped together (simple-v456).
No runtime change: 997 Python + 953 frontend green; the release package
test builds with the new version.
The READMEs now point users at a release package instead of a clone; this
adds the machinery behind it.
- tools/build_release.py builds dist/simple-<version>.zip: the runtime
tree (the server package incl. its bundled MCC/MNC list, the PWA
runtime files, docs/, tools/, the setup/start scripts, pyproject,
requirements-pysim.txt, the READMEs, LICENSE and THIRD-PARTY-NOTICES)
under a single top-level directory, with no tests, no frontend
sources/build config, no bytecode and no git metadata. The version
comes from server.py; --expect-version guards a mislabeled tag.
- .github/workflows/release.yml runs both test suites, builds the
package and publishes it on a v* tag (workflow_dispatch for an
existing tag); a re-run uploads with --clobber. pysim is checked out
next to the repo for the Python suite and is NOT bundled.
- tests/test_release_package.py pins the package's shape: the runtime
files, the absence of the development files, and that sw.js's
precache list is fully covered - so a new asset cannot silently go
missing.
- dist/ is gitignored; the READMEs name the artifact.
Verified: 997 Python + 953 frontend green; the built zip (53 files,
~0.95 MB) was unpacked in a clean directory and its setup.sh installed it
into a fresh venv - the console script runs and the installed server
resolves web_dir to the unpacked frontend/ (index.html, sw.js,
uicc_files.json present). The workflow YAML parses; GitHub-side
execution is unverified until the first tag is pushed.
The READMEs told users to clone the repository even though most just
want to run the app. The Quick start now leads with the release
package (a snapshot of the tagged source: no git history, no
development files) and keeps the clone path for development or
unreleased changes; git is still required because setup installs pysim
from its upstream repository.
Windows notes refreshed against the actual tooling and PyPI:
- pyscard 2.3.1 ships cp39-cp313 wheels (no cp314) and requires
Python >= 3.9, so the pip/pyproject floor is 3.9 now (3.8 could
never install the dependency) and the "3.9 builds from source" note
is gone - only 3.14 does.
- The reader sentence now describes what actually happens: start.sh
asks for the first PC/SC reader when pcscd runs, the server
auto-detects a PC/SC reader itself, and with none present pysim
falls back to its serial default /dev/ttyUSB0 (use -d for a
serial/FTDI reader).
- The manual-install block is marked Linux/macOS: on Windows pysim is
installed via requirements-pysim.txt + --no-deps (setup.bat), since
the plain pip route pulls the SMPP bridge and its MSVC chain.
Docs/metadata only: 953 frontend + 989 Python green.