The actions sat below the note line and the (long, 45vh) bits grid, so they
needed scrolling to reach. They now sit to the right of the preset selector
and hex field, aligned with the preset row; the bit grid is unchanged.
The tree box was capped at a fixed 65vh, which ignored the chrome above it
(header, tabs, status, sub-tabs, sort/probe row), so the page itself grew a
scrollbar once the tree hit its cap. pysimFsFitTree() now caps it to
innerHeight - top - 12px at runtime (the inline 65vh stays as the no-JS
fallback), re-run on resize, on render, on sub-tab/tab entry and when the
probe status line appears.
TERMINAL PROFILE form (Phone simulator):
- The compact block no longer shows the hex value (no room); Send +
Configure + status remain. Preset selector sits above the hex field.
- Bits are never spread horizontally: each byte is one vertical list.
Byte blocks are placed by the fixed TP_LAYOUT groups - bytes 1-12 in
2 columns, 13-16 in 4, 17-18 in 2, 19-21 in 3, 22-25 in 2, 26-28 in 3,
29-30 in 2, 31+ one per row - in strict byte order.
Bit descriptions audited against the pinned specs:
- Table regenerated from ETSI TS 102 223 V18.3.0 5.2 (bytes 1-39) with
the 3GPP-defined bits named per ETSI TS 131 111 V18.12.0 5.2; the
"reserved by 3GPP" placeholders are gone (tests guard this).
- Fixes found in the audit: byte 7 b6 PERFORM CARD APDU (pySim had
RESET), MO short message control by USIM, USSD string DO support in
Call Control by USIM, the earlier "Data available" typo.
- Presets: project default + Quectel example + Samsung S21+ 5G, Samsung
A55 5G, Xiaomi Redmi Note 10 LTE, Sony Xperia Z5c LTE, Huawei
E5573c/M150 (LTE), Huawei E173 3G modem, Nokia 7210 2G (9 total).
Also fixes a page-load TDZ regression: the custom-files init (which
calls t()) now runs after the language init (guard test added).
SW cache otaman-v174; help EN/RU updated; tests 236 Python + 374 frontend.
TERMINAL PROFILE:
- GET /api/terminal-profile returns the profile in effect + the CLI default;
POST /api/terminal-profile validates ({profile}, hex, even, 1-255 bytes),
stores it in memory and re-sends it, resetting the STK session like
/api/rescue (the shared _resend_terminal_profile helper; rescue now
delegates to it). __main__ keeps server.cli_terminal_profile.
- Phone tab: a TERMINAL PROFILE block next to STATUS and Polling with the
current hex/byte count, Send (re-send) and Configure. The Configure
dialog has a device-model preset selector, a hex field and a per-bit
form generated from a 264-entry table for TS 102 223 V18.3.0 5.2 bytes
1-33 (pySim's table as scaffold, later bytes/3GPP bits added from the
spec; beyond the table generic RFU labels). Form <-> hex sync both
ways, hex authoritative, bits preserved. Apply posts the new value.
- Presets: Xiaomi Mi A1 (project default), Quectel GSM module example.
In-memory only, no persistence.
Custom files:
- Canonical paths rooted at MF / ADF.USIM / ADF.ISIM; entries are
{path, name, kind}. The editor now uses root + parent-DF selector +
4-hex FID + alias, requires the parent DF to be defined first, rejects
duplicates, rewrites descendants when a DF's path changes and cascades
deletes after a confirmation.
- Legacy forms are normalized on load/import (3F00/... -> MF/..., relative
a153/4954 resolved against the custom DFs); unresolvable entries are
dropped and reported in the list.
- Tree injection matches by exact parent path via the new pysimFsNodePath
(same-FID DFs under different parents no longer collide);
profilerCustomNameForPath uses the same normalization.
SW cache otaman-v167; help EN/RU + docs/api.md + AGENTS updated.
Tests: 236 Python + 371 frontend.
SCP81:
- New listener mode "passthru": no local listener - the card's BIP
channels connect straight to a configured external platform
(host/port required), which terminates TLS and runs the dialog.
The status API reports mode/target (_SCP81_MODE/_SCP81_TARGET) and
clears them on stop. PWA mode select, hint, Start validation; TLS PSK
stays the default.
Proactive command decoding (Phone tab log):
- SEND SHORT MESSAGE (0x13) is now decoded: alpha, address (TON/NPI +
number), 3GPP-SMS TPDU (type, TP-MR, TP-DA, TP-PID 0x7F flagged as
SIM data download, TP-DCS, TP-VP, TP-UDL), UDH concatenation IEs, and
the TP-UD as text (GSM-7 with a septet unpacker, UCS2, 8-bit) or as a
TS 31.115 secured packet for PID 0x7F; malformed TPDUs fall back to
the raw hex line.
- PROVIDE LOCAL INFORMATION qualifier names completed per TS 102 223
V18.3.0: ESN (07), MEID (0B), Supported RATs (1A); 05 relabelled
"Reserved for GSM (Timing Advance)". Fixed in the server dict and
both frontend tables.
Header:
- Compact ADM badge next to the card indicator: "ADM ✓" green when
pySim's adm_verified is set, "ADM ✗" red otherwise, hidden without a
card session or when the server is down; updated ahead of the card
state-key early return so it never disturbs the connect/reset flow.
File manager:
- Sort pills (FID/Name), Probe all files button and progress line are
pinned above the tree instead of scrolling with it; the tree box cap
grows from 420px to 65vh.
SW cache otaman-v165; help EN/RU updated; tests 234 Python + 361 frontend.
Cards / SCP81:
- Cards is a top-level tab; presets gain PSK identity + key, HTTP-OTA
column, Edit/Update and a live PSK-map push into a running listener.
- SCP81 has Listener/Scripts pills; scripts are named local APDU lists
(Empty / Explore / Install from .cap / Delete templates), sent to the
server explicitly at start. The listener takes mode/host/port/script
only; PSK inputs and the .cap row are gone.
- Multi-PSK TLS listener: identity -> key lookup from the card presets
(POST /api/scp81/psk-map updates a running listener), unknown
identities log tls-psk-unknown and fail; handshake logs carry psk_match.
- Script engine: execution tracking (next/done/pending/results), a
resumed dialog sends only the unexecuted tail (unreported APDU is
resent), a fresh dialog restarts, listing continuation pages are
tracked separately (pending.pages/complete). Restart script button.
- POST /api/scp81/gen-install replaces the SCP81 ram-install queueing
(generation only; the .cap is never stored).
Profiler / snapshots:
- Snapshot comparison is always exact (mask checkboxes removed; the
first-4-bytes mask remains a profile-creation option).
- "matching records" line shows count + #record numbers.
- New Clone action: copy named "Copy of <profile>", opened in the editor.
- Matched-record count/numbers fix ("1 из 8 (#8)").
SCP80:
- Configurable / auto-fitted LOAD block size: each LOAD APDU encodes into
one SMS (pySim rejects secured packets above 140 octets, so a 240-byte
block could never be sent). Response reports the effective size and
clamps; encode failures are reported per step with the pySim message.
SW cache otaman-v161; docs/api.md, scp81-findings and help EN/RU updated.
Tests: 226 Python + 356 frontend.
cardsSave() calls cardsRebuildSelect(), and the innerHTML rebuild reset the
dropdown to its first option ("- Select card -"). Since the counter sync
saves after every send, the selection was lost each time and had to be
re-picked manually. The rebuild now restores the previous selection
(guarded by the preset still existing). Service worker v155.
The R-APDU TLV length was read as a raw byte, so a listing page above 127
bytes (AF 80 23 81 FC <252 bytes> 00 00) was cut to its first 127 bytes with
a bogus status word (the data's last two bytes: CAFE/0001/9F70 instead of
the real 63 10 "more data"). The bogus SW also stopped the SW CAFE/6310
pagination, which is why later registry entries - e.g. the installed package
AA1902BC225801 - never showed up. Uses httpota.ber_len_read now.
Tests: 250-byte long-form page and short-form regression (204 python);
findings updated; service worker v154
- continuation repeats the SAME GET STATUS command with P2.b1 set (the
pagination state lives in the card); changing the 4F criterion is a match
filter, not a position - P2=03 with the last AID is rejected with 6A80 and
P2=02 with it returns that single match (the earlier duplicate)
- handle the standard "more data available" warning SW 63 10 (Table 11-38)
in addition to the live card's proprietary CA FE
- explore script: P1=40 is applications+SDs, P1=20 the ELF registry, P1=10
ELF+modules (Table 11-33) - the ELF-only registry was never queried, which
hid the installed package; labels and the results decoder show C4 (ELF AID)
and CC (SD AID) too
- UICC_SPECS.md: GET STATUS P1/P2 tables made explicit with the pagination
rule, plus BER length coding notes for the scripting templates and the
TS 102 223 channel data TLV (the two >127-byte traps)
201 python + 346 frontend; service worker v153
P2=02 means "get first or all occurrence(s)" (Table 11-34), so every
continuation re-returned the first listing (the criterion's single match),
pagination stopped after one extra page and the installed package
AA1902BC225801 never showed up in the ELF registry. P2=03 = "get next
occurrence(s)" is the correct value for the SW CAFE continuation.
Tests updated with the new continuation bytes; findings documented;
service worker v152
- POST /api/scp81/queue takes an explicit APDU list (or single APDU) and
queues it as the SCP81 script; entries that already are Command Scripting
templates (AA.../AE80..., the expanded format) are sent verbatim instead of
being wrapped again
- Remote APDU -> RAM chain: "To expanded" builds each command in the
TS 102 226 expanded form (AA definite / AE80 indefinite selector) and
"Queue in SCP81" queues the built commands for the next card POST, so the
full-featured RAM/INSTALL [for install] form (AIDs, privileges, TK/STK
parameters) can drive the HTTP OTA install
- RU strings; api.md; service worker v151
The SCP81 install row now carries the same fields as the SCP80/RAM form:
install parameters (default C900), STK parameters (CA TLV) and make
selectable. An UICC applet install can require STK parameters; without them
the card answers INSTALL [for install] with SW 6A80 (incorrect parameters in
data field). RU strings added; service worker v150.
The block slicer used the block number as a character offset
(loadfile_tlv[i * 2:(i + 240) * 2]), so every LOAD block after the first was
a 1-byte-shifted copy of the previous one - the cap header repeated every
239 bytes on the wire. A live install accepted three blocks, failed block 4
with SW 6400, then 6985, and INSTALL [for install] answered 6A88. The same
slicing was inherited by the SCP81 helper from the SCP80 path, so
multi-block caps could not install there either; both are fixed.
Tests: blocks are consecutive and reassemble the C4 TLV byte-for-byte
(200 python); findings updated; service worker v149.
The C-APDU TLV length was written as a raw byte: a 245-byte LOAD command
produced 'AE 80 22 F5 ...', which BER reads as a long-form marker, so the
card mis-parsed every script command over 127 bytes. Small commands worked,
which made a RAM install look alive: the card answered the LOAD steps with a
degenerate 'AF 80' body (no R-APDU), and the final INSTALL [for install]
failed with 6A88 because the package never loaded.
Both the indefinite ('22' TLV) and definite ('AA' outer) lengths are now
BER-encoded (same rule as the BIP channel data TLV fix). Tests: 245-byte
LOAD body, short form, definite variant (200 python, 346 frontend);
findings doc updated; service worker v148.
- each result group shows the command label (GET DATA FF21, GET STATUS
P1=80/40/10, GET DATA 0085, INSTALL/LOAD)
- GET STATUS pages deduplicate by AID: the continuation page re-includes its
search criterion, which made the last entry of every listing appear twice
- the GET DATA 0085 answer is decoded (host/agent/uri, PSK identity +
KVN/KID from the unframed [14][id][02 KVN/KID] security TLV, retry counter
and timer, connection block with APN and destination address) instead of a
truncated hex dump; undecodable results show the full hex now
- tests: admin-params decode with the live sample, command labels
(346 frontend, 196 python); service worker v147
The counter only advanced when a PoR came back with status por_ok, so
PoR-less sends kept reusing the same counter (the card rejects a repeated
counter for replay protection). It now advances on every successful send
(and shows "CNTR -> ..." in the result line), and the new value is written
into the selected card preset via spCntrSyncPreset(). Manually edited
counters are persisted too (the field's onchange). New spNextCntr() helper
with carry tests; service worker v146.
- shared _cap_apdu_sequence helper (INSTALL [for load] -> 240-byte LOAD
blocks -> INSTALL [for install]); the SCP80 /api/ram-install path now uses
it too (one source of truth; byte-level tests pin the APDUs)
- POST /api/scp81/ram-install: parses the .cap server-side and queues the
APDU sequence as the SCP81 command script (one C-APDU per POST, runs on the
card's next push); refused while a script is mid-run unless force
- /api/scp81/script reports the script kind (explore/none/custom/ram-install)
- tab: RAM install row (CAP file + SD AID + Queue button); RU strings
- docs: api.md, findings, AGENTS; service worker v145
- results carry the originating APDU, so auto-continued SW CAFE pages group
under their logical command
- new tab panel: memory pages decode to applets / free NV / free volatile,
GET STATUS pages decode to AID + lifecycle + privileges (via the existing
decodePrivileges) + module AIDs, truncated page tails are skipped
- tests: decoders and grouping (frontend 342, python 192)
- service worker v144
Long GET STATUS listings answer SW CAFE with 127-byte pages. The script
responder now extracts the last complete AID from the page and inserts a
next-occurrence GET STATUS (80F2 <P1> 02 <Lc> 4F <len> <AID> 00) as the
next command, until the listing ends. Pages are logged (script-page), a
repeated page logs script-page-stalled and stops, inserted continuations are
dropped at session start.
Live-verified: ELF registry and applications collected completely in two
pages each (7/7 commands, all script-status ok).
- tests: page parsing (truncated tails, live FC-prefixed junk), continuation
bytes, auto-insert, stall guard, per-session purge (192 python + 337 frontend)
- UI: page number and script-status log rendering; service worker v143
- scp81.py: PSK TLS listener (stdlib ssl PSK callbacks) speaking the GP
HTTP administration dialog; configurable framing (chunked/Content-Length,
TLS record split, Apache-style/compact headers, Connection header,
keep-alive, Next-URI template with %d, TLS version/cipher, answer delay,
keylog for capture decryption)
- server.py: script responder + Response Scripting parsing (AF/AB, 80/23
TLVs), memory decoder, SCP81 start options, terminal-side timer
management, background-mode BIP events, permissive OPEN CHANNEL
- BIP fix: the RECEIVE DATA channel-data TLV length is BER long form
(36 81 <len>) above 127 bytes; a raw length byte is mis-parsed on the
card, so the large TLS records never reached its stack (a live card
fetched the script response and silently never processed it - endless
resume). The card now executes scripts and returns R-APDUs: memory
(13 applets, 50646 B NV free, 2402 B volatile), ISD, stored HTTP OTA
parameters, ELF and application registries
- frontend: SCP81 tab (listener, script selection, HTTP OTA log), phone
event forms, i18n; service worker v141
- docs: api.md, scp81-findings.md (attempt matrix + root cause analysis);
tools/scp81_decrypt.py decrypts listener captures via the keylog
- tests: 187 python + 337 frontend
profilerScanCard's walkDir passed each child dir's own segment in
parent_path (dir.parentPath.concat(childSeg) as the child's parent path),
so /api/tree for every DF child walked the target as its own parent and
failed: DF.GSM, DF.TELECOM and DF.GSM-ACCESS subtrees were silently
missing from New snapshot, Profile from card and Profile from snapshot
(ADF children survived only because application names/AIDs resolve
globally).
- walkDir now stores each dir's full path and sends
parent_path = fullPath.slice(0, -1) (omitted for MF); the child's full
path is fullPath.concat(aid/fid/name)
- verified against the running server: the walker now enumerates 189 EFs
and captures 92, matching the file-manager probe exactly (was 111/48)
- test: fake MF -> DF.GSM tree asserts parent_path [undefined, ['MF']]
and that both subtrees' rules are produced; SW cache v132 -> v133.
- new 'Profile from snapshot' button in the Profiles toolbar: reuses the
snapshot picker (profilerSnapshotPickListHtml refactor) and then opens
the same scan form as Profile from card (profilerScanOpenForm), with
the profile name prefilled from the snapshot
- new profiler-build target 'profile-snapshot': the form's ignore list,
mask options and FCP/FCI mode apply to profilerScanSnapshot(), which
walks the snapshot's captured files instead of the card; uncaptured
contents produce rules without a content check, record files map to
exact record content, transparent files honor the masks
- profilerScanStart() pushes the generated profile and opens the editor
like the live scan; _scanSnapshot is cleared in the finally block
- RU entry 'Профиль из снимка'; tests for the snapshot rule builder
(exact/mask/ignore/uncaptured/record), the snapshot scan progress, the
picker wiring and the form title, plus a structural button check;
help EN/RU and READMEs updated; SW cache v130 -> v131.
- new profilerResultsHeaderText() builds the header from a structured
{kind, from, to}: profile checks read 'Profile verification results
for: <profile> -> <card ICCID | snapshot name>', snapshot comparison
reads 'Snapshot comparison results: <master> -> <checked>' (the arrow
matches the one already used in the compare header)
- profilerRunProfile() stores the header and profilerRenderResultsView()
writes it, so the header follows a language switch and the ICCID stays
current; RU keys added ('Результаты проверки профиля:',
'Результаты сравнения снимков:')
- tests: header builder variants + render wiring; flow tests now expect
header objects; help EN/RU and READMEs updated; SW cache v129 -> v130.
- new profilerLabelText() resolves expected/actual labels at render time;
profilerRenderReport and fcpDiffHtml (including the decode-failure
notes) use it, so a language switch keeps the labels localized
- Check card: reads EF.ICCID via /api/read (MF/2FE2) and labels the
report 'expected (<profile>)' / 'actual (<card ICCID>)'; the results
title becomes '<profile> — <ICCID>' (falls back to the plain title and
'actual' when EF.ICCID is unreadable)
- Check card snapshot: labels 'expected (<profile>)' / 'actual
(<snapshot name>)
- snapshot-vs-snapshot compare keeps verbatim master/check names;
the mismatch label column widens to w-48 for prefixed labels
- tests: profilerLabelText, prefixed report/diff labels, profilerCardIccid
decode/fallback, both check flows pass the labels; help EN/RU, READMEs
updated; SW cache v128 -> v129.
The data-l10n key for 'RAM operations perform atomic GlobalPlatform
commands over SCP80. Card keys are taken from the saved preset.' was
missing from LANG_RU, so the hint stayed English. SW cache v127 -> v128.
- ramOpChanged() now clears the previous execution status (result line,
steps, explorer, progress) only when the Operation value actually
changes, so switching Explore -> Install Package no longer shows the
old 'Partial - ELF Modules: no data' until Execute; re-entering the
RAM subtab still preserves the last result
- ramClearResults() also hides the progress caption
- the Card preset dropdown no longer resets to the placeholder after
every executed operation (ramSaveCntr -> ramRender rebuilt it) or on
subtab re-entry: ramRender keeps the current/remembered index,
ramApplyCard and ramExecute remember the selection for the session,
and cardsRemove keeps _ramCardIdx aligned via ramCardIdxAfterRemove
- tests: op-change clearing, selection preservation across rebuilds,
pick/execute remembering, index bookkeeping; SW cache v126 -> v127.
The RAM explorer HTML is generated after load, so its data-l10n sections
and Delete/Delete All buttons stayed English until a language switch, and
the memory/AID/lifecycle labels were hardcoded with no key at all.
- ramRenderExploreHtml() now builds every label and button with t()
(reusing the existing Delete/Delete All/heading/AID keys and wiring the
three previously-unused Application/Instance, Load File and Module AID
keys); new RU keys cover Applications:, Free NV:, Free Volatile:, AID:,
Lifecycle:, Privileges:, SD AID:, Implicit sel:, Version: and (none)
- the explorer dataset is cached in _ramExplorerData and re-rendered by
refreshDynamicI18n() on language switch (cleared on op change/results
reset)
- RAM flow strings localized too: Partial/OK summaries, error labels
(Memory/ISD/Apps/ELFs/ELF Modules/no data), GET DATA progress, delete
confirm/labels, install alerts/progress/steps and Error:
- tests: ram.test.js asserts every explorer label/button goes through t()
and the (none) placeholder; SW cache v125 -> v126.
The proactive command list only re-rendered on tab/pill switches, so new
fetched commands (background STATUS polling, menu traffic) stayed
invisible while the Phone view was open.
- /api/status now exposes proactive_seq (_PROACTIVE_ENTRY_ID, monotonic
and not reset with the log), so the existing 2s status poll detects
changes with no extra request; pysimCardStateUpdate() re-renders the
log when the sequence changed and the Phone/Phone view is visible
- the 5s backend timer no longer fetches the log (it was discarded) and
now uses the already-fetched stk-status: when active/pending changes
while the Phone tab is visible, stkCheckMenu() refreshes the menu
button without a tab switch
- pysimProactiveLogRender() keeps its scroll position and only shows
Loading... on a first/empty paint
- tests: proactive seq + STK signature helpers and poll-driven render
behaviour in card_state.test.js; SW cache v123 -> v124.
The File: EF.DIR [LF] line doubled as the selection store: pysimFsClickFile
wrote a formatted display string there and pysimFsRead/Save parsed it back
(stripping '✗ ' and ' [TYPE]'). The pane now keeps the name in
pysimFsSelected, the redundant title is gone (FID/type/size/FCI remain in
the info block), and the dead pysimFsSelect() plus the unused 'File:' RU
entry are removed. Read/Save still re-select on the server first: pySim's
current selection is a shared cursor that tree expansion, scans and
commands move, so it cannot identify the file shown in the pane.
SW cache v121 -> v122; structural test updated.
The decoded FCI items now follow the FID/type/size line directly; the
snapshot detail view keeps its caption. Test updated to assert the
caption is absent; SW cache v120 -> v121.
- selecting an EF now renders an info block between the File: title and
the contents: FID, file type, size, record length/count (nulls skipped)
followed by the decoded FCI via the existing profilerFciPreviewItems()
decoder (no raw hex), matching the snapshot detail view
- pysimFsInfoHtml() is pure and reused by pysimFsClickFile(); the [LF]
shorthand stays in the title; custom files get the same metadata from
the temporary select probe
- tests: pysimFsInfoHtml in profiler.test.js (full response, missing
fci_hex, skipped nulls) and a structural check for #pysim-fs-info;
help EN/RU and READMEs updated; SW cache v119 -> v120.
- EN: Custom files are added in file manager tree and included in card scan.
- RU: Пользовательские файлы добавляются в менеджер файлов и учитываются
при чтении карт.
- SW cache v118 -> v119.
pySim's lchan.select() resolves names against global selectables (self +
parent chain + MF children + applications) and falls back to probe_file(),
which blindly SELECTs an unknown FID and permanently injects a dynamically
named DF.XXXX/EF.XXXX into the running filesystem model. Probing a whole tree
or scanning a snapshot with custom files therefore polluted the model, made
tree branches show children of the wrong object, and could persist phantom
files into snapshots.
- server: new _select_with_parent()/_select_path() walk the requested parent
path (new parent_path field, parent_sel kept as legacy fallback) strictly
through the model and call lchan.select_file() only; model-unknown 4-hex
segments are probed only with allow_probe and the temporary child pySim
adds is detached again via the cleanup callable that the four handlers
(/api/tree|select|read|write) now run in a finally block
- frontend: getParentPath() builds the segment chain (MF, ADF names, FIDs)
and all tree/select/read/write bodies plus the snapshot/profile walker send
parent_path; allow_probe is set only for custom files; the blind retries
in the file manager were dropped
- tests: tests/test_select_scope.py (duplicate-FID resolution, no APDU for
unknown non-custom files, probe+detach, model unchanged); fs_load/fs_probe
assertions for parent_path and allow_probe; docs/api.md and AGENTS.md
document the contract; SW cache v117 -> v118.
- pysimFsLoadChildren failure now only marks exists=false and re-renders;
loaded children stay in the node and are hidden by the renderer (red
cross, no toggle, no (empty)), so a failed DF is re-attempted on the
next probe/refresh instead of being short-circuited
- dropped the parent_sel-less /api/tree retry: error payloads now carry
exists:false, so it doubled requests for every absent DF and re-selected
the same FID without its parent (pySim probe_file fallback)
- probe walks strictly on exists===true and never collects files under a
non-existing DF; no longer clears children on a failed EF select
- tests updated: single request per failed load, children untouched,
200 exists:false marks absent without retry, render hides stale
children, probe never fetches/selects them; SW cache v116 -> v117.
- pysimFsLoadChildren now treats {success:false,error} (and any error
payload) as a failed listing: absent DFs/ADFs render as a red cross
without an expand arrow instead of silently opening empty; the
/api/tree 500 body also carries exists:false for shape consistency
with /api/select
- an expanded directory with zero children shows a gray (empty)
placeholder
- new Probe all files button (data-needs=card) in the tree header:
walks the whole model tree from MF, verifies every DF/ADF via
/api/tree and every file (incl. custom entries) via /api/select,
skips subtrees of absent dirs, shows N/total progress, toggles to
Stop, and reports present/absent counts with elapsed time; results
colour the current tree only
- tests: fs_load (error/retry/empty), fs_render (red cross, (empty)),
fs_probe (flags, absent-dir skip, custom files, stop, summary);
html structural check; help EN/RU, READMEs, AGENTS updated;
SW cache v115 -> v116.
- each row now shows Edit and Delete buttons (same styling as the
profiler list buttons) instead of the red X glyph
- Edit reloads the entry into the top form: the Add button becomes
Save (data-l10n updated too) and a Cancel button appears; submit
updates the entry in place, recomputing fid/parentFid and excluding
the edited row from the duplicate-path check
- Delete removes without confirmation; deleting the row being edited
cancels the edit and an earlier deletion shifts the edit index
- pysimCustomAdd renamed to pysimCustomSubmit; tests for the whole
flow; SW cache v114 -> v115.
- top-level tab order is now Remote APDU, SCP80, Profiler, Card reader,
Phone simulator; the profiler view moved with its button so the DOM
order matches the tab strip
- docs (help EN/RU, READMEs) and the tab-order test updated; SW cache
v113 -> v114.
- the SVG artwork occupies only ~46% x 63% of its 183x183 canvas, so
the 18px render showed ~8x11px of ink; 30px roughly doubles the
visible glyph while staying under the 32px h1 line-height, so the
header row height is unchanged
- guard test asserts the inline size stays within the 24-32px budget;
SW cache v112 -> v113.
- the wrapper is select-none with an inline cursor:default (the compiled
CSS has no cursor-default utility), so hovering the dot/icons no
longer shows the I-beam or allows text selection
- SW cache v111 -> v112.
- the dot carries its own title (Connecting... / No server connection),
matching the wrapper tooltip; it is cleared when the sim icons replace
the dot
- tests assert the dot title in both server states and its removal once
the server is up; SW cache v110 -> v111.
- #state-indicator is now a wrapper with a dot plus an 18px dark:invert
image; no new compiled Tailwind classes (h-4/w-5 are not in the
prebuilt CSS), so sizing uses an inline style and the header height
is unchanged
- gray dot while probing, red dot when the server is unreachable;
server up -> nosim.svg, animated sim_anim.svg while equipping, and
sim.svg when the card is equipped (`_pysimEquipping` tracked from
status.equipping, refreshed on every 2s poll)
- i18n reuses existing strings; tests for all five states, dot color
transitions and the markup; SW cache v109 -> v110.
- pysimFsSortChildren() groups DFs above EFs, then sorts each group by
FID (default) or case-insensitive symbolic name; missing names fall
back to the FID as the sort key; deterministic tie-break; the input
array is not mutated
- render uses the sorted copy, so lazily loaded directories and custom
file injections are sorted too
- FID / Name pills above the tree (pysimFsSetSort) persist the choice in
localStorage (otaman_fs_sort) and re-render without refetching
- tests for the comparator (DF priority, both keys, fallbacks, custom
entries, no mutation) and the pill wiring; help/README updated;
SW cache v108 -> v109.
The stuck-card pattern: after a Back TR the card re-issues the parent menu
as a new SELECT ITEM (91XX -> FETCH, paused, awaiting TR), but the UI's
back/timeout branch ignored that response and showed the cached top menu;
the next item click then sent ENVELOPE(Menu Selection) while the card was
waiting for the TR. The card answers such an ENVELOPE with 9000 (not the
usual 91XX), and menu-select cleared the pending command without a TR,
leaving an unfinished proactive session until reset/equip.
- server: _finish_pending_menu() answers a paused command with a cancel TR
(0x10) before /api/menu-select sends its ENVELOPE and drains a 91XX
follow-up, so a new selection can never shadow an unanswered FETCH
- frontend: stkMenuRespond('back'|'timeout') renders the follow-up command
from the server response (SELECT ITEM / DISPLAY TEXT) and shows the
cached top menu only when the TR answer carries no command (9000)
- tests: finish-pending cancel TR + chain drain (Python); stkMenuRespond
back/timeout/cancel/ok rendering (frontend); help/AGENTS updated;
SW cache v107 -> v108.
The message branch treated the static auto_equip config flag as if
initialization were in progress, so with auto-equip on (default) a
cardless server showed 'Card inserted — initializing...' instead of the
no-card message. The initializing state now requires equipping or an
actually present card with auto-equip enabled; regression test added.
SW cache v106 -> v107.
SCP80, Card reader and Profiler list pill rows now use the same centered
layout as Remote APDU and Phone simulator (flex-wrap justify-center).
SW cache v105 -> v106.
Profiles / Card snapshots / Custom files switch from top-bar style tabs
(border-b, rounded-t) to the rounded-full pill style used by the other
second-level switchers. SW cache v104 -> v105.
Availability model: server-down / server-up-no-card / card-equipped.
- header shows a symbolic indicator dot (red/yellow/green, tooltip)
- a 5s probe tracks /api/version+/api/status while not fully ready; the
2s status poll takes over once connected
- controls declare data-needs='server' or 'card'; disabled + tooltip when
the state does not satisfy them: equip (server), status/reset, file
manager read/save/edit, raw APDU, profile-from-card, new snapshot,
profiler list Check card, event Send, RAM install/explore delete,
STK menu, Send STATUS, Verify vs pySim (server), pySim execute (server),
PLI save/poll toggle (server)
- pysimConnect no longer conflates server and card: it records both and
Connect stays usable without a card
- ramSendOta/ramInstallCap now use pysimFetch instead of raw fetch
(relative /api URLs broke custom server URLs)
Custom files moved from the Card reader sub-tabs to the Profiler list
tabs (Profiles / Card snapshots / Custom files); help/README/AGENTS
updated. Tests: availability gating, indicator structure, moved tab.
SW cache v103 -> v104.