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.
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 two buttons under a cntr_low verdict shared the red warning style, and
the notice only ever rendered in the RAM pill: an SCP80 send wrote it into
the hidden RAM container, so the plain-send view showed the verdict text
but offered no way to act on it.
- The counter probe button is amber and the preset jump green (the warning
text stays red).
- The probe renders the inline throbber the RAM operations use
(animate-spin, no modal) while it runs; its result replaces it in the
same box.
- Each pill carries its own notice box (#ram-cntr-low / #sp-cntr-low):
ramShowCntrLow/ramSyncCounter target the originating view's box and
clear the other, the SCP80 send passes 'sp' explicitly (it used to rely
on the keyset-view heuristic), and a new send attempt clears the stale
verdict (ramClearResults clears both boxes).
- The prebuilt Tailwind output was regenerated (npm run build):
border-amber-300/-700 and border-emerald-300/-700 were absent from
style.css (the stale-style trap).
Tests: the button classes, the spinner and the box targeting pinned in
ram.test.js. 846 frontend + 830 Python; sw cache simple-v416.
The report view re-renders the member blocks on every poll while a suite runs
(innerHTML replacement), so an expanded Run log collapsed within 500 ms. The
<details> now carries the stable member index (data-member) and
testRenderReport carries the open state across the re-render; the log content
keeps updating in place.
- frontend/tests/testsuites.test.js: the wiring pin (data-member emitted, the
open state read and restored).
- sw cache simple-v400 (index.html changed); style.css rebuilt.
Tests: 837 frontend + 795 Python.
The TAR table used the default auto layout with w-full, so the browser
distributed the surplus width across all columns and TAR/MSL/description
looked evenly spread. table-fixed with explicit column widths (TAR w-28,
MSL w-20, action w-12; the description column takes the remainder) and
w-full inputs keep the first two columns tight and left-packed. The ADM
placeholder is just "ADM" now. sw cache simple-v365.
Tests: 665 Python + 787 frontend green; the whole inline PWA script
syntax-checked.
The PIN/ADM and TAR fieldsets now share one responsive row (side by side
from lg, stacked below): PIN/ADM is a fixed 224px column (lg:w-56) that
stretches to the TAR box's height, TAR takes the rest (flex-1 min-w-0).
The codes are a vertical list (label above each input) and the hint is
shortened to "All codes are optional" - the specifics stay in the input
placeholders and the help. A structural guard pins the row and the short
hint; sw cache simple-v363.
Tests: 665 Python + 785 frontend green; the whole inline PWA script
syntax-checked.
The Cards tab now shows the preset list by default; "Add preset" / "Edit"
open a separate editor view (Save stores and returns to the list, Cancel
discards; ramGoToPreset opens it too). The editor keeps the name/ICCID
header (with From card) and groups its fields into bordered blocks:
- PIN/ADM: the new optional card codes PIN1/PUK1/PIN2/PUK2 (plain text,
4-8 decimal digits) plus ADM, moved out of the top row; nothing consumes
the PIN/PUK codes yet.
- TAR: one uniform table (TAR, MSL, description); the first three rows are
the fixed role entries with the immutable English descriptions "ISD
compact format" / "UICC shared FS RFM compact format" / "ADF FS RFM
compact format"; further rows are deletable with an optional description.
- SCP80 (keyset editor) and SCP81 (PSK pair) as before.
Store: PRESET_FIELDS gains pin1/puk1/pin2/puk2 (optional; whitespace
stripped; 4-8 decimal digits validated on save and import), SCHEMA_VERSION
4, docs/help/AGENTS updated. TAR rows use unified ids (cards-tr-N-*), the
editor switches via cardsShowList/cardsShowEditor.
Tests: 665 Python + 783 frontend green; the whole inline PWA script
syntax-checked. sw cache simple-v361.
Presets drop the card-wide SPI1/SPI2 and the fixed tar/uiccTar/usimTar
fields: a preset now carries `tars` - the three mandatory role entries
(isd/uiccRfm/usimRfm, spec defaults pre-settled) plus free TARs with an
optional description, each with its MSL (Minimum SPI1, TS 102 226
8.2.1.3.2.4). v3.9.1 presets convert on load/import (the spi1 seeds all
three MSLs); duplicate TARs and empty TAR/MSL values are refused; the
store schema version is 3.
Operations derive a packet's SPI1 from its TAR's MSL (_spi1_for_tar): the
RAM ops, the test runner (per-step overrides still win), the counter probe
and each TAR-probe row; the plain send keeps an explicit SPI1 with a
`warning` when it is below the MSL, and a TAR outside the preset without an
explicit SPI1 is refused with a hint. The TAR probe resolves per checked
TAR before sending and reports the effective spi1 per row. The secured
packet form's SPI2 default is 00 (the RAM/probe operations pin their own).
PWA: the Cards form edits the TAR table (role rows with MSL inputs plus
Add/Remove free rows with a description), the list shows a TARs (MSL)
column, the SCP80 TAR field suggests the preset's entries (datalist) and a
preset apply seeds the SPI1 from the operation TAR's MSL with an amber
warning below it; the preset-wide SPI1/SPI2 fields and the header marker's
SPI requirement are gone (completeness = a keyset + the three role entries
with their MSLs). Help EN/RU, docs/api.md and AGENTS updated; sw cache
simple-v358.
Live: TAR probe 000000 at its MSL 16 (registered, por_ok, counter
advanced); a send without spi1 derived 16 (por_ok, counter advanced); a
send with spi1 0A returned the warning and the card refused it (6200, no
counter consumed); an unknown TAR without spi1 was refused with the hint.
A CAP install is a long chain of LOAD commands whose progress and results
were only visible at the bottom of a large form - and an interrupted
install can leave a half-loaded package.
- Server: _RAM_PROGRESS (+ _ram_progress_begin/step/end) records the live
step of the running RAM operation; /api/ram-install and
/api/ram-install-app wrap their chains in it (total = steps + 1 for the
format check) and /api/status carries it as ram_progress - lock-free, so
the 2 s poll works while the request holds _CARD_LOCK.
- PWA: #ram-op-modal (the STK panel pattern) blocks everything while a RAM
operation runs: title, spinner, status line, progress bar, per-step
result lines, Close disabled until it finishes; a beforeunload guard
protects the window during the run. install-cap, install-app /
make-selectable and Explore all use the shared helpers
(ramOpModalBegin/Status/Finish); the install results stay in the form
area as before once the modal is closed. The status poll feeds the modal
via ramOpProgress (headline from the pure ramOpProgressText).
- Tests: ramOpProgressText (JS) + the progress lifecycle (Python); the
card-state and capmem suites stub the DOM-bound hooks.
- Stylesheet rebuilt; version 3.6.34; sw simple-v307.
The RAM install form's toolkit parameters are the make-or-break field for
toolkit applets, and CA vs EA is easy to get wrong (the earlier live 6A80s).
- "SIM/UICC Toolkit parameters" is now an accented bordered fieldset (blue
border/legend, always visible in the cap / install / make-selectable
modes) with the checkbox in the legend and the fields inside.
- ramCapToolkitMode(imports) maps the CAP's linked libraries to the install
parameter family: sim.* (AID tree A0000000090003FFFFFFFF89...) -> SIM
Toolkit (Tag CA), uicc.* / uicc.usim.* (A0000000090005FFFFFFFF89...,
A0000000871005FFFFFFFF89...) -> UICC Toolkit (Tag EA) - family-based, so
access-only applets get the right field too. Applied when the block is
enabled and when the CAP is swapped while it is on, with a "detected from
the CAP" hint under the select; no CAP keeps the manual choice.
- Rebuilt style.css for the new accent classes; tests pin the fieldset and
the family mapping.
663 frontend / 504 Python green; version 3.6.32; sw simple-v305.
The compiled Tailwind stylesheet (frontend/style.css) had been stale since
2026-09-22: classes introduced later were missing from it, so the Install /
Make selectable chips had no background or text colour in light mode (dark
mode only looked green because an older dark:text-emerald-400 rule matched)
and the new w-28 action column did not apply.
- Rebuilt with `npm run build` (Tailwind + appended src/contrast.css);
verified the previously missing classes (bg-emerald-100, text-emerald-700,
hover:bg-emerald-200, bg-red-100, bg-blue-100, w-28, ...).
- Explorer rows are visible chips now: rounded bg-gray-50 dark:bg-slate-800/60
with a hover:bg-gray-100 highlight and the colored left accent kept (module
rows a subtler variant), so a right-column button is unambiguously tied to
its row; ISD/app/ELF/module rows all use the same treatment.
- AGENTS working conventions: an index.html change that introduces new
utility classes also requires `npm run build` in frontend/.
660 frontend / 504 Python green; version 3.6.30; sw simple-v303.
The short EUICCInfo2 fields (versions, card resources, category, PPRs, …)
were one per row in the wide right-hand box, wasting half of it.
- esimChipValueWide(): a row needs the full width when its value is a
list or a long token (>24 chars or ', '), so short scalars pair up.
- esimChipBox() gained a twoColumns mode: rows in a responsive
grid-cols-1 sm:grid-cols-2 grid with long values spanning both columns;
the other boxes keep their single-column rows.
- esimRenderChip() renders the EUICCInfo2 box with two columns.
- style.css rebuilt (sm:grid-cols-2, sm:col-span-2).
- tests: the wide/short rule, the two-column markup and the render call;
help EN/RU and AGENTS note the layout; sw.js simple-v233.
The chip view was one long flat list spread over the full height.
- esimRenderChip split into esimChipSectionRows (object or RAT rule list
-> rows) and esimChipBox (titled box, skipped when empty); the boxes
are laid out on a grid: EID, EUICCInfo1, Addresses and the rules
authorisation table in the left column, the larger EUICCInfo2 in the
wider right-hand box (single column on narrow screens). Rows use a
narrower label column (w-36).
- Values wrap with break-words + min-w-0 instead of break-all: text
breaks at spaces first and long hex/AID tokens only when they cannot
fit (same for the profile metadata rows).
- style.css rebuilt (grid-cols-1, lg:grid-cols-3, lg:col-span-1/2/3,
break-words).
- tests: the new helpers, the grid/box layout and the wrapping check;
help EN/RU note the grouping; sw.js simple-v232.
Server (pysim_simple_server/netsim.py + POST /api/net-sim):
- pure builders for the observed phone write vocabulary (UICC_NAA.md §13):
EPSNSC (A0 TLV with KSI/KASME/counts/algo, padded to the card's FCP record
size; invalidate-wipe and invalidate-keep-key), LOCI/PSLOCI/EPSLOCI real
and dummy forms (PLMN kept, LAC FFFE, status 01/02), Kc/KcGPRS (9-byte
USIM and 33-byte GSM forms, 07 invalidates), EF.SMSstatus counter bump
(read-modify-write), CBMI/CBMIR, the Location status event (9B 01 <st>,
with the optional 13 07 location info for normal service) and the
AUTHENTICATE APDU (TS 31.102 7.1.2.1) with DB/DC response parsing
- scenario runner executes the recipes under _CARD_LOCK, picks the first
existing candidate path (ADF.USIM vs DF.GSM/DF.TELECOM), reads FCP/current
records where the format is card-specific, skips the event unless the card
subscribed to Location status, honours the scenario toggles and returns a
per-step log; only D6/DC, ENVELOPE and AUTHENTICATE are ever sent
- GET /api/mcc-mnc serves the optional worldwide operator list
(--mcc-mnc-list, default <workspace>/samples/mcc-mnc-list.json): ?q=
search and ?random=1&exclude= for roaming; the list stays out of the repo
PWA:
- Phone pill gains a 'Network simulation' fieldset: one button per scenario
(cold boot, EPS/2G attach, service lost, limited service, roaming denied,
churn, SMS received, CB reconfig, AUTHENTICATE), a collapsed Parameters
block (operator search + random roaming, LAC/Cell ID/TAC/RAC, optional
identities, toggles, churn count/delay) and a step log with SWs
- i18n EN/RU, help/README/api.md/AGENTS.md updated; version 2.5.0,
SW cache simple-v193
Tests: tests/test_netsim.py (19), frontend/tests/netsim.test.js (3) and
html guards - 423 frontend / 280 Python, all passing
- Phone simulator: STK menu, STATUS and Polling, TERMINAL PROFILE and
SET UP EVENT LIST become fieldsets with legends; 'Fetched proactive
commands:' gains a bordered container
- SCP81 listener: the HTTP OTA listener label moves into the legend (the
live state span stays next to it), Script results (R-APDUs) and HTTP OTA
log become fieldsets (the log's Clear button moves to the body top-right)
- Options (applied at Start) stays a details/summary but its summary is
styled as an embedded label (page-background patch over the top border,
native triangle kept) so it expands/collapses from the legend
- html.test.js: card-fieldset test no longer assumes exactly two fieldsets;
new guard test for the legends and the Options summary
- Tailwind rebuild of style.css; SW cache simple-v192 (version stays 2.4.0)
After the gray text was raised to #cbd5e1 in dark mode, elements using
dark:text-slate-300 (inactive tabs, labels, list text) matched the muted
level. Map dark:text-slate-300 to #e2e8f0 (slate-200, the body's dark
text color) so normal text stays one step brighter than muted gray.
SW cache v96 -> v97.
Elements using bare text-gray-300/400/500/600 (proactive log ids and
timestamps, expand markers, event codes, ...) kept the light-theme gray
values in dark mode, which made them hard to read on slate-800/900.
- dark .text-gray-*:not([class*='dark:text-']) rules map them to the same
level as the other dark muted text (slate-300 #cbd5e1, slate-200/100 for
gray-700/800)
- dark:text-gray-400/600 and dark:text-slate-500 normalized to #cbd5e1
- dark:border-slate-600/700/800 (+ /50) brightened one step
- light theme unchanged
- the whole contrast block moved to src/contrast.css, appended by the
npm build scripts after Tailwind so it stays reproducible; SW cache
v95 -> v96
Redefine .text-xs to render at 0.875rem/1.25rem (text-sm), covering the PWA
and both help pages via the shared stylesheet. Explicit leading-* overrides
still apply.
SW cache v41.
- Light theme: darken gray-400/500/600 muted text one step
- Dark theme: lighten slate-400/500 muted text one step
- Dark theme: lighten red-500/600/700 text (was too dark on dark bg)
SW cache v40.
Server:
- Track all TERMINAL RESPONSEs (chain, TP, pySim auto-handler, menu-respond
link to paused entry) with shared _build_tr/_record_tr
- Log entry ids + cmd_num; server-side decode of fetched commands and TRs
(compact PLI port); Result CTLV extracted as tr_result/tr_result_name
- _LoggingApduTracer captures TR SW for pySim auto-handler responses
- _DefaultProactiveHandler answers PLI with editor data (no re-request)
- VERSION 1.8.0
Frontend:
- Expandable proactive log rows (click to expand, state survives re-render)
- Response section: Result name + hex, decoded fields, width-restrained
readonly hex inputs; no SW display
- Rename Expanded Script tab/pill/button/docs; proactive UICC view font
sizing increase; card reader pills text-sm
- Drop truncation: full qualifier descriptions (wrap)
- Raw hex input moves under the description, fills row width (flex-1)
- Subtle bottom-border separator between qualifiers