v3.29.1: the per-row keysets in the outfile preview

The import applied one global default keyset to every TAR entry of every
imported card.  The preview's TAR table (TAR + MSL, the three role rows +
"+ TAR") now has a keyset select per row, mirroring the preset editor: an
empty "— none —" plus the keysets found in the file with their KIc/KID,
the first preselected, and the editor's amber warning when a non-zero MSL
has no keyset.  Each TAR's packets then use exactly the chosen keyset.

- PWA: the global "Default keyset for the TARs" select (and the request's
  tar_keyset) is gone; cardsOutfileTarsFromDom/FromForm carry each row's
  keyset; cardsOutfileKeysets() became cardsOutfileKeysetOptions() (the
  union with the KIc/KID bytes for the labels).
- Server: a `tars` entry's own `keyset` (1-15; an unusable value is a 400)
  wins, then the optional global `tar_keyset` (the API shorthand), then the
  card's first keyset - parse_outfile no longer clobbers per-entry keysets
  with the card-level fallback.  presets.normalise_keyset_ref() is
  module-level (one rule for the store and the parser).
- Docs: help EN/RU, api.md (the tars entry keyset + the global fallback),
  the repo AGENTS.  Tests: the per-entry/global/card fallbacks, the
  bad-keyset 400, the options helper, the row select and round-trip, the
  import body.  925 frontend + 944 Python; sw cache simple-v448.
This commit is contained in:
2026-10-15 00:40:16 +03:00
parent cf7ee77a9e
commit 8a66ea5a0a
12 changed files with 168 additions and 95 deletions
+11
View File
@@ -955,6 +955,17 @@ class PresetStoreHttpTests(unittest.TestCase):
status, resp = self._post('/api/presets/import-outfile', {
'text': text, 'tar_keyset': 99})
self.assertEqual(status, 400, resp)
# the per-row keysets travel in `tars` (v3.29.1)
status, resp = self._post('/api/presets/import-outfile', {
'text': text, 'tars': [{'role': 'isd', 'tar': '000000', 'msl': '16', 'keyset': 2},
{'role': 'uiccRfm', 'tar': 'B00000', 'msl': '16', 'keyset': 1},
{'role': 'usimRfm', 'tar': 'B00001', 'msl': '16', 'keyset': 1}]})
self.assertEqual(status, 200, resp)
self.assertEqual([t['keyset'] for t in resp['presets'][0]['tars']], [2, 1, 1])
status, resp = self._post('/api/presets/import-outfile', {
'text': text, 'tars': [{'tar': '000000', 'msl': '16', 'keyset': 99}]})
self.assertEqual(status, 400, resp)
self.assertIn('keyset', resp['error'])
# a non-list TAR table is the caller's error, never a silent fallback
status, resp = self._post('/api/presets/import-outfile', {
'text': text, 'tars': 'nope'})