A red ✕ next to the filter selector appears whenever the filter is not
"All" and resets it to "All" in one click (pysimFsFilterClear). Bump
VERSION/pyproject/PWA header to 3.40.2 and the SW cache to simple-v487.
Drop the invented "SMS-related" / "CB-related" filter categories: those
files are service-declared and are already reached through their
per-service options (per-spec service names). Bump VERSION/pyproject/
PWA header to 3.40.1 and the SW cache to simple-v486.
Bump VERSION/pyproject/PWA header to 3.40.0 and the SW cache to
simple-v485 (index.html changed). CHANGELOG [3.40.0] documents the
file-tree filter (checkbox multiselect, path-exact DF hiding, expand/
collapse all, auto-expand, and the "To EF filter" cross-link).
- "Expand all" / "Collapse all" links in the sort row: expand-all walks
the directory tree loading children on demand (skipping DFs the probe
found absent), collapse-all resets every directory.
- Selecting any filter but "All" auto-expands the tree so the matched
files are visible (pysimFsAutoExpandForFilter, wired into the two
filter setters).
- i18n EN/RU for the two links.
Frontend 1163 tests green (+2). (Version/sw bump still deferred.)
A non-'All' selection turns the collapsed filter button blue (like the
active sort pill) so it stands out; 'All' keeps the default style.
Frontend 1161 tests green.
When a filter is active (not "All"), only the matching files, their
ancestor directories and the MF root stay; every other directory is
hidden. The ancestors come from the standard file list (name -> paths):
pysimFsFilterMatchingPaths unions the registry paths of every matching
name, and pysimFsFilterShow keeps a directory iff it matches itself
(e.g. DF.GSM for svc:GPRS) or its canonical path prefixes a matching
path. Custom files contribute their own paths; the not-loaded window
keeps directories rather than hiding everything.
Frontend 1160 tests green (+8). (Version/sw bump still deferred.)
The per-service cross-link is now a single compact glyph (U+25BE) with
'To EF filter' as the title/aria-label, instead of a text button that
shifted the grid. Frontend 1152 tests green.
In the SST/UST/IST/EST decoded editors, each service row backed by files
gains a small "To EF filter" action that sets the file-tree filter to
exactly that service's option:
- The four service-table fields carry a `table` key (sst/ust/est/ist);
servicesFeatureByNumber() inverts SERVICES_CROSS_REF into per-table
number->feature maps (lazy, cached).
- pysimFsFilterBtnHtml(table, n) renders the button only for file-backed
features; pysimFsSetFilterToService(table, n) sets pysimFsFilter to
['svc:<feature>'], persists, and re-renders the dropdown + tree.
- i18n: "To EF filter" (EN/RU). No CSS rebuild needed.
Frontend 1152 tests green (+6). (Version/sw bump still deferred.)
Replace the <select multiple size=4> filter with a single-row button that
expands into a scrollable checkbox menu (All + Custom files, then the
Identifiers/PLMN/Services headings with the categories and per-service
options), mirroring simtrace-analyser's command filter.
- pysimFsFilterSummary/Label, pysimFsFilterToggle, pysimFsFilterSetEntry
(all-exclusive, empty -> all) and a rewritten pysimFsFilterRender;
pysimFsSetFilter is gone.
- Closes on outside click and Escape. i18n: "Selected: {n}".
- No CSS rebuild needed (swapped to utility classes already in the
prebuilt style.css).
Frontend 1146 tests green. (Version/sw bump still deferred.)
Fill in the scaffolded file-tree filter:
- FS_FILTER_GROUPS: Identifiers, PLMN selection, Provider names,
WLAN/I-WLAN, SMS-related and CB-related (cell broadcast), each with
its exact file list; plus one option per file-backed feature of
SERVICES_FS_MAP (svc:<feature>).
- "All" is the default and exclusive; a top-level "Custom files"
option matches the injected custom-file nodes by their flag.
- fsFilterIndexBuild inverts name -> criterion set; pysimFsFilterMatch
matches by name (and custom by flag); pysimFsFilterShow keeps a
directory while it has a matching (loaded) descendant, and keeps
never-expanded directories. The dropdown renders All / Custom files
and three optgroups (Identifiers / PLMN / Services).
- i18n EN/RU for the new labels.
Frontend 1145 tests green (fs_filter.test.js 19).
Add a Filter multiselect next to the sort pills in the file tree toolbar.
The option list is large and undecided, so FS_FILTER_OPTIONS is empty and
the per-node match is a no-op for now; the mechanism is wired end to end:
- FS_FILTER_OPTIONS const (empty), pysimFsFilter state persisted to
simple_fs_filter, pysimFsSetFilter / pysimFsFilterMatch /
pysimFsFilterRender, and the render loop consults pysimFsFilterMatch.
- The dropdown re-renders on language switch; "Filter:" and
"(no filters yet)" are translated (EN/RU).
Frontend 1132 tests green (6 new scaffold pins).
Bump VERSION/pyproject/PWA header to 3.39.0 and the SW cache to
simple-v484 (index.html + help changed). CHANGELOG [3.39.0] folds the
unreleased 3.38.2 start-script fix and documents the decoded-editing
batch: DF-context resolution, EF.ECC/EF.AD split, path-based selection,
EF.SST tri-state editor, the Services editor + path-exact FS check, and
the UST 147-150 completion.
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 "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).
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.
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.
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.
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.
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.
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 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.
EF_PBR ('4F30', TS 31.102 4.4.2.1) lists the phonebook's files as
BER-TLVs, and those files are not in pySim's model - so they were
invisible in the file tree.
- The PWA gains the decoder (efDecPbr, registered as EF.PBR/4f30): the
'A8'/'A9'/'AA' type-1/2/3 containers, the 'C0'-'CC' kinds, the 02/03
value lengths (FID / FID+SFI) and the master EF (the first FID after
the first 'A8'), tolerant of FF padding, truncation, unknown tags and
the spec's vague 'AA' path form (kept raw + noted, never a throw).
- The server reads the three PBR copies once per card session
(best-effort, exactly like EF.ARR) into server.pbr and serves the raw
records from the card-free GET /api/pbr.
- The file tree is populated SERVER-SIDE (no frontend involvement): a
small Python parser reads the cached records and /api/tree appends the
referenced files as children of the DF that holds the PBR
({'name','fid','isDir':false,'pbr':true,'sfi'}, deduplicated against
the model), and /api/tree|select|read|write auto-allow the probe for
exactly those FIDs, so the injected nodes are clickable without any
PWA flag. The node-to-PBR match is by the requested node's normalised
path: a leading MF before an ADF segment is dropped and an ADF
addressed by AID (the Profiler's walk) folds onto its name, so both
DF_PHONEBOOK copies resolve to their own PBR; a full path never falls
back to another DF's copy, and an ambiguous bare 5F3A injects nothing.
- Drift guard: the JS and Python parsers share
frontend/tests/pbr_vectors.json, which includes the TS 31.102 Annex G
record (its printed A8 length is internally inconsistent - 10
primitives x 5 bytes = 0x32, not the printed 0x2D - so the corrected
length is used, noted in the case name).
Docs: api.md (/api/pbr, the tree children, the auto-probe, the AA path
form), the repo AGENTS. 953 frontend + 989 Python; sw simple-v455.
The independent re-check of the v3.30.4 identity fixes found a
pre-existing gap in testSuiteDelete: it cleared the selection only when
the deleted suite WAS the selected one, so deleting a suite BEFORE the
selection left _testSuiteIdx pointing at whatever moved into that slot
(or out of range when the list shrank).
The delete now captures the selected suite's id just before the store
refresh and re-resolves the index after it, which also keeps a selection
the user made while the dialog was open (the modal no longer freezes the
page).
Test: a behavioural test drives the real function through the four cases
(the selected suite deleted; a suite before it deleted; the selection
moved during the dialog; nothing selected) - mutation-checked to fail
without the re-resolution. 946 frontend + 952 Python.
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.
Under start.bat the server's stdout/stderr are pipes into PowerShell's
Tee-Object, so Python encodes with the ANSI code page instead of the
wide console API. cp1251 (Russian Windows) cannot encode the banner's
box-drawing rule (U+2500), and that print killed the server with
UnicodeEncodeError before it could listen.
- _guard_console_streams() (the first statement of main()) sets
errors="replace" on both streams: an unencodable character prints as
'?' and can never raise.
- _console_rule() picks the widest banner rule its stream can encode -
U+2500 where the code page has it (utf-8, cp437), ASCII dashes
otherwise (cp1251, ascii).
- The seven text-layer trace strings are ASCII now: the APDU arrows
(U+2192) and the INIT/CAP-COMPAT em dashes (U+2014 - cp437 cannot
encode them either), so the traces stay readable on any code page.
The raw os.write paths (_log_stdout, the APDU tracer) bypass the text
layer, cannot crash, and were left as-is.
Tests: tests/test_console_encoding.py - the strict-cp1251 control, the
rule's fallback/pretty cases, the guarded banner+traces, a stream
without reconfigure, the main() source pin, and an AST pin that no
print/stdout/stderr literal in the package is non-ASCII.
tests/test_startup_cardless.py's CARDLESS_LINE follows the new ASCII
line.
Verified with PYTHONUNBUFFERED=1 PYTHONIOENCODING=cp1251: the pre-fix
code died at the banner, the fixed code reaches the listening banner
and serves a command (CMD: -> OK, no encode errors).
930 frontend + 952 Python; sw cache simple-v453.