@ham2k/extension-sdk 0.12.11 → 0.12.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -53,8 +53,10 @@ function activityExportHook(rules) {
53
53
  filename: filenameFor(operation, qsos ?? [], ref.ref, compactFilenames),
54
54
  icon: rules.icon,
55
55
  selectedByDefault: true,
56
- // Segment slicing: only the contacts made while this activity ran.
57
- refType: rules.activationType
56
+ // Segment slicing: only the contacts made while THIS reference was
57
+ // being activated.
58
+ refType: rules.activationType,
59
+ ref: ref.ref
58
60
  }));
59
61
  },
60
62
  async generateExport(args, ctx) {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- // @ham2k/extension-sdk 0.12.11
1
+ // @ham2k/extension-sdk 0.12.13
2
2
  /** Experimental v1 panel scene contract. All coordinates are in the scene's viewBox. */
3
3
  export interface PanelScene {
4
4
  version: 1;
@@ -597,6 +597,7 @@ export interface HookContext {
597
597
  distanceUnits?: string;
598
598
  decimalSeparator?: string;
599
599
  groupingSeparator?: string;
600
+ dateOrder?: "mdy" | "dmy" | "ymd";
600
601
  gridPrecision?: number;
601
602
  edition?: string;
602
603
  enabledExperiments?: string[];
@@ -703,6 +704,7 @@ export interface PostResult {
703
704
  ok: boolean;
704
705
  message?: string;
705
706
  userMessage?: ResultMessage;
707
+ skipped?: boolean;
706
708
  }
707
709
  export interface ResultMessage {
708
710
  presentation: "status" | "dialog";
@@ -869,6 +871,7 @@ export interface TextInputDescriptor {
869
871
  }[];
870
872
  numeric?: boolean;
871
873
  uppercase?: boolean;
874
+ numbersRowKey?: string;
872
875
  maxLength?: number;
873
876
  suggestedValue?: string;
874
877
  }
@@ -890,6 +893,7 @@ export interface OptionsInputDescriptor {
890
893
  minCharsForSuggestions?: number;
891
894
  placeholder?: string;
892
895
  uppercase?: boolean;
896
+ numbersRowKey?: string;
893
897
  maxLength?: number;
894
898
  suggestedValue?: string;
895
899
  pattern?: string;
@@ -910,6 +914,7 @@ export interface RefListInputDescriptor {
910
914
  replacement: string;
911
915
  flags?: string;
912
916
  }[];
917
+ numbersRowKey?: string;
913
918
  }
914
919
  export interface ActivitySuggestion extends Ref {
915
920
  distance?: number;
@@ -1004,6 +1009,7 @@ export interface ExportRequest {
1004
1009
  operation: Record<string, JSONValue>;
1005
1010
  qsos: Record<string, JSONValue>[];
1006
1011
  segments?: OperationSegmentPayload[];
1012
+ events?: Record<string, JSONValue>[];
1007
1013
  exportType?: string;
1008
1014
  exportKey?: string;
1009
1015
  compactFilenames?: boolean;
@@ -1037,6 +1043,7 @@ export interface ExportOption {
1037
1043
  priority?: number;
1038
1044
  selectedByDefault?: boolean;
1039
1045
  refType?: string;
1046
+ ref?: string;
1040
1047
  }
1041
1048
  export interface ExportHook {
1042
1049
  getExportTypes?(args: Record<string, never>, ctx: HookContext): Promise<ExportTypeDefinition[]>;
@@ -1278,9 +1285,21 @@ export interface CommandAction {
1278
1285
  kind: "start" | "break" | "stop";
1279
1286
  setLocation?: boolean;
1280
1287
  };
1288
+ findInLog?: {
1289
+ query?: string;
1290
+ };
1281
1291
  setCallField?: {
1282
1292
  text: string;
1283
1293
  };
1294
+ setTime?: {
1295
+ now?: boolean;
1296
+ deltaMillis?: number;
1297
+ hour?: number;
1298
+ minute?: number;
1299
+ second?: number;
1300
+ month?: number;
1301
+ day?: number;
1302
+ };
1284
1303
  reloadDataFiles?: {
1285
1304
  target: string;
1286
1305
  };
@@ -1685,6 +1704,7 @@ export interface FormField {
1685
1704
  itemFields?: FormField[];
1686
1705
  itemTitle?: string;
1687
1706
  orderable?: boolean;
1707
+ itemSwitch?: string;
1688
1708
  inline?: boolean;
1689
1709
  itemLabel?: string;
1690
1710
  defaultValue?: any[];
@@ -1786,6 +1806,7 @@ export interface SettingsPanelDescriptor {
1786
1806
  order?: number;
1787
1807
  sidebar?: boolean;
1788
1808
  dataFilesSection?: boolean;
1809
+ section?: string;
1789
1810
  }
1790
1811
  export interface SettingsField extends Omit<FormField, "validate" | "transform" | "itemFields"> {
1791
1812
  pattern?: string;
@@ -1801,6 +1822,7 @@ export interface SimpleSettingsPanel {
1801
1822
  order?: number;
1802
1823
  sidebar?: boolean;
1803
1824
  dataFilesSection?: boolean;
1825
+ section?: string;
1804
1826
  fields: (SettingsField | FormActionElement)[];
1805
1827
  }
1806
1828
  export interface DynamicSettingsPanel {
@@ -18,7 +18,8 @@ function settingsPanelEntriesFor(record, descriptors) {
18
18
  order: hook.order ?? null,
19
19
  kind: "simple",
20
20
  sidebar: hook.sidebar ?? true,
21
- dataFilesSection: hook.dataFilesSection ?? false
21
+ dataFilesSection: hook.dataFilesSection ?? false,
22
+ section: hook.section ?? null
22
23
  }
23
24
  ];
24
25
  }
@@ -33,7 +34,8 @@ function settingsPanelEntriesFor(record, descriptors) {
33
34
  order: d.order ?? null,
34
35
  kind: "dynamic",
35
36
  sidebar: d.sidebar ?? true,
36
- dataFilesSection: d.dataFilesSection ?? false
37
+ dataFilesSection: d.dataFilesSection ?? false,
38
+ section: d.section ?? null
37
39
  }));
38
40
  }
39
41
  return [
@@ -46,7 +48,8 @@ function settingsPanelEntriesFor(record, descriptors) {
46
48
  order: null,
47
49
  kind: "dynamic",
48
50
  sidebar: true,
49
- dataFilesSection: false
51
+ dataFilesSection: false,
52
+ section: null
50
53
  }
51
54
  ];
52
55
  }
@@ -39,7 +39,9 @@ function opValues(operation, extra = {}) {
39
39
  station.split(",").map((call) => call.trim().toUpperCase()).filter((call) => call !== "")
40
40
  )
41
41
  ],
42
- operator: operation.operator ?? "",
42
+ // Resolved by the host for this device's position; a template cannot do it
43
+ // itself, because the position never crosses the bridge.
44
+ operator: operation.operatorCall ?? "",
43
45
  // The generated, ref-derived title ("at US-1234") and the operator's own
44
46
  // words, kept apart: an export that withholds private data withholds the
45
47
  // second and keeps the first (see the adif hook's preamble).
@@ -388,7 +388,7 @@ The app builds in only its **core** extensions — the ones that are the app
388
388
  itself: `adif`, `scoring`, `lookups`, `templates`, the command sets and
389
389
  `ham2k-lofi` sync. Every other extension is a catalog bundle, and the app ships
390
390
  some of them already packaged under `app/assets/preloaded-extensions/`,
391
- installing them into the ordinary store on first run. Afterwards they are
391
+ installing the ones the operator has switched on into the ordinary store. Afterwards they are
392
392
  ordinary installed extensions — the same rows, the same update check, the same
393
393
  Update and uninstall — so an operator starts with a working app and the catalog
394
394
  can move it forward from there.
@@ -416,7 +416,30 @@ ship pre-loads that fail the check ([releases.md §6.4](https://github.com/ham2k
416
416
  They are installed once, not every launch. A record of the highest version
417
417
  ever pre-loaded per key is what makes that true: an app update carrying a
418
418
  newer bundle installs it, a catalog release already ahead of it is left
419
- alone, and a key the operator uninstalled stays gone.
419
+ alone, and a key the operator uninstalled stays gone — its uninstall leaves
420
+ its switch off, which is what the pre-load asks before installing a newer copy.
421
+
422
+ **Only what is switched on is installed** — by the operator's choice for the
423
+ key, else its built-in's, else on. One switched off would be a dead row in the
424
+ Extensions panel, and for a new install that is most of them. Skipped, it is
425
+ not recorded as offered, so switching on one never offered brings it in; one
426
+ already offered comes back from the catalog. A shipped key's switch is kept
427
+ after it leaves the store, and an uninstall or a Forget leaves it off, so an
428
+ app update carrying a newer copy does not bring back one the operator
429
+ removed. Nothing is
430
+ installed until onboarding has been answered, since the activities it asks
431
+ about are pre-loads: installed on their defaults first, whichever the operator
432
+ then turns down would stay installed and switched off. Onboarding settles them
433
+ as it finishes (`settleOnboardingExtensions`), as `ONBOARD_AS` does.
434
+
435
+ An install can hold every bundle the app ships, whatever its switch. It takes
436
+ the pre-loaded bundles and built-ins' twins it holds switched off out of the
437
+ store once (`extensionsSwitchedOffRemoved`), keeping their settings,
438
+ credentials and switch for a reinstall, and records that done only once it has
439
+ read the whole store. Other catalog extensions are the operator's own and stay,
440
+ as do file installs. A replayed onboarding (DEVONBOARD) removes nothing. A
441
+ catalog install the operator makes is always switched on, whatever switch it
442
+ inherits.
420
443
 
421
444
  An installed bundle may not claim a key the app builds in, and a pre-load is
422
445
  no exception: it goes through the same door as any other install.
@@ -445,8 +468,8 @@ a callsign before its extension state was first recorded
445
468
  a build without them, unasked: there is nothing left to run instead. The boot's
446
469
  catch-up (`catchUpOnCatalogExtensions`) carries each built-in's on/off choice
447
470
  onto its twin, over whatever an earlier attempt left there; installs the
448
- pre-loads, which carry the built-in's settings, panel values and credentials on
449
- a twin's first install, and its default where the operator never chose; then
471
+ pre-loads switched on, which carry the built-in's settings, panel values and
472
+ credentials on a twin's first install; then
450
473
  fetches the twin of any other built-in the operator had switched on. It records
451
474
  the move done once the pre-load has run, so a boot killed half way carries the
452
475
  choices again next time. It needs no network while every twin is pre-loaded.
@@ -469,8 +492,8 @@ refuses the bundle itself: a keychain that would not open or a full disk is
469
492
  tried again next launch, and meanwhile the adoption fetches the catalog's copy
470
493
  if the operator had it on. A twin the pre-load did install is never fetched
471
494
  back after the operator uninstalls it. A choice stored under the pre-load's own
472
- name — `ham2k-lookup`'s, which kept its name — is kept rather than dropped as
473
- stale.
495
+ name — `ham2k-lookup`'s, which kept its name — decides whether it is installed,
496
+ and is kept rather than dropped as stale.
474
497
 
475
498
  **What does not come along.** A few built-ins have no twin on purpose — `qp`,
476
499
  which the catalog carries as one extension per party instead, and `stateparks`,
package/docs/forms.md CHANGED
@@ -66,15 +66,20 @@ export interface FormField {
66
66
  // Record<string, any>[]; each item is shaped like itemFields. By default
67
67
  // the field renders as one summary row (from itemTitle, templated
68
68
  // against the item's values, e.g. "${name}" — NOT "{{name}}") that opens
69
- // an editor dialog with per-item rows and add/edit/delete; `inline: true`
70
- // instead renders those rows (with reorder handle/delete per row, and an
71
- // Add row) directly in the form, skipping that outer dialog — for a list
72
- // that's the main content of its own section. Either way, tapping a row
73
- // (or Add) opens the same itemFields editor dialog (the form renderer
74
- // recursively, scoped to itemFields).
69
+ // an editor dialog with per-item rows and Add; `inline: true` instead
70
+ // renders those rows (with a reorder handle per row, and an Add row)
71
+ // directly in the form, skipping that outer dialog — for a list that's the
72
+ // main content of its own section. Either way, tapping a row (or Add)
73
+ // opens the same itemFields editor dialog (the form renderer recursively,
74
+ // scoped to itemFields), whose footer holds Delete for an existing item,
75
+ // which asks the operator to confirm.
75
76
  itemFields?: FormField[];
76
77
  itemTitle?: string;
77
78
  orderable?: boolean;
79
+ // The key of an on/off value in each item, set from a switch at the end of
80
+ // the item's row; leave it out of itemFields. An item without the key reads
81
+ // as on. An app that predates this shows no switch.
82
+ itemSwitch?: string;
78
83
  inline?: boolean;
79
84
  // Singular noun for one item (e.g. "custom note file"), used in the
80
85
  // editor dialog's Add/Edit titles and the Add button instead of `label`
package/docs/hooks.md CHANGED
@@ -309,7 +309,7 @@ interface SpotsHook {
309
309
  // Spot: {their: {call}, freq?, band?, mode?, refs?: Ref[],
310
310
  // spot: {timeInMillis, source, label?, sourceInfo?}}
311
311
  // SpotEligibility: {enabled, icon?}
312
- // PostResult: {ok, message?}
312
+ // PostResult: {ok, message?, userMessage?, skipped?}
313
313
  // PostSelfSpotRequest: {operation, freq, mode?, comment?}
314
314
  // PostOtherSpotRequest: {qso, comment?, spotterCall?}
315
315
  ```
@@ -330,6 +330,15 @@ directly to the POTA API, no auth required). `sota`'s spot API requires an
330
330
  authenticated account app-halo doesn't have yet, so `sota` stays
331
331
  fetch-only for now.
332
332
 
333
+ A poster whose own rules decline a particular spot — a Discord channel whose
334
+ message template renders blank for it — answers `{ok: true, skipped: true}`.
335
+ Its icon stays neutral gray instead of lighting as sent, and no failure is
336
+ reported. The Spotting chip's color counts only the sources that did not skip.
337
+ When every one did, the chip stays neutral and the operator is told so, in
338
+ each source's `message` when it gives one; that spot is never what
339
+ auto-respotting repeats, and a round of it every source skips ends it.
340
+ `skipped` without `ok` is still a failure.
341
+
333
342
  ## ✅ `cluster` — the DX cluster connection
334
343
 
335
344
  ```ts
@@ -381,11 +390,17 @@ interface ExportHook {
381
390
  generateExport(args: ExportRequest, ctx): Promise<ExportResult>
382
391
  }
383
392
  // ExportOptionsRequest: {operation, qsos, compactFilenames?}
384
- // ExportOption: {exportType, exportKey?, format, label, filename?, icon?, color?, refType?, priority?, selectedByDefault?}
385
- // ExportRequest: {operation, qsos, exportType?, exportKey?, compactFilenames?} — full QSON, one coarse call
393
+ // ExportOption: {exportType, exportKey?, format, label, filename?, icon?, color?, refType?, ref?, priority?, selectedByDefault?}
394
+ // ExportRequest: {operation, qsos, segments?, events?, exportType?, exportKey?, compactFilenames?} — full QSON, one coarse call
386
395
  // ExportResult: {filename, mimeType, content}
387
396
  ```
388
397
 
398
+ `qsos` holds contacts alone. The log's event rows — `break`, `start`, notes —
399
+ come separately in `events`, for a file that writes the timeline (the full
400
+ ADIF does, with private data); `segments` is what they resolve to.
401
+ `operation` carries `startAtMillisMin`/`startAtMillisMax`: the span of the
402
+ contacts in `qsos`, not of the whole operation.
403
+
389
404
  Two-step flow: the Exports Panel calls `suggestExportOptions` on every
390
405
  `export` hook to build its selectable list (a hook that omits it never
391
406
  appears in the panel), then calls `generateExport` — passing back the
@@ -471,6 +486,12 @@ whole-log ADIF — asks for a glyph of its own instead of borrowing one. An
471
486
  `icon` name that doesn't resolve falls back to the activity's, same as an
472
487
  unreadable `color` does.
473
488
 
489
+ **One reference's file.** An export claiming one reference of its `refType` —
490
+ one park's log, when the operation activated several — names it in `ref`, and
491
+ the core narrows the slice to the segments carrying that reference. Without
492
+ it, a park activated after a break gets every contact made at the park before
493
+ it. `activityExportHook` sets it for you.
494
+
474
495
  **Filenames.** Build them with the SDK's `exportFilename` rather than
475
496
  spelling one out, passing the request's `compactFilenames` straight through —
476
497
  that is what makes the user's "Use compact file names" setting apply to your
@@ -491,7 +512,7 @@ filename: exportFilename({
491
512
  Set `filename` on the OPTION, not just on the result: the panel shows the
492
513
  option's name, and the core writes the file under it. A name computed again
493
514
  in `generateExport` sees a different input — that call is handed only the
494
- QSOs the option's `refType` covers — so the two would otherwise disagree in
515
+ QSOs the option's `refType` (and `ref`) covers — so the two would otherwise disagree in
495
516
  front of the user.
496
517
 
497
518
  ## ✅ `ref:<type>` — reference handlers
@@ -681,14 +702,15 @@ interface ActivityHook {
681
702
  // The input catalog. A new kind is earned by a different WIDGET, never by
682
703
  // different validation — so a pattern-constrained field is `text` with a
683
704
  // `pattern`, not a kind of its own (docs/design/contests.md §5.3).
684
- // {kind: 'refList', refType, placeholder?, pattern?, transforms?}
705
+ // {kind: 'refList', refType, placeholder?, pattern?, transforms?,
706
+ // numbersRowKey?}
685
707
  // {kind: 'text', refType, field, placeholder?, pattern?, transforms?,
686
- // numeric?, uppercase? /* default true */, maxLength?,
687
- // suggestedValue?}
708
+ // numeric?, uppercase? /* default true */, numbersRowKey?,
709
+ // maxLength?, suggestedValue?}
688
710
  // {kind: 'options', refType, field, options: [{code, name?}], matchOn?,
689
711
  // allowFreeform?, preferredCodes?, minCharsForSuggestions?,
690
712
  // placeholder?, pattern?, transforms?, numeric?, uppercase?,
691
- // maxLength?, suggestedValue?}
713
+ // numbersRowKey?, maxLength?, suggestedValue?}
692
714
  // {kind: 'serial', refType, field, sequence: {key, scope?, start?, padTo?},
693
715
  // editable?}
694
716
  // {kind: 'form', refType, form} // operationControls only
@@ -700,6 +722,12 @@ interface ActivityHook {
700
722
  // exports as typed. The pattern is HaLo's idea of the program rather than the
701
723
  // program's own, so it may never cost the operator a reference.
702
724
  //
725
+ // `numbersRowKey` is the one character the mobile numbers row offers after
726
+ // its digits while the field has focus, where the operator would otherwise
727
+ // switch keyboard layers for it. Without it a `refList` field offers `'-'`,
728
+ // which joins most programs' prefixes to their digits, and the other kinds
729
+ // the operator's decimal separator.
730
+ //
703
731
  // `text`, `options` and `serial` values round-trip onto a ref of `refType` under
704
732
  // `field`, through the same core-owned path refList writes refs — several may
705
733
  // share one refType and are merged into a single ref. `suggestedValue` fills the
@@ -919,6 +947,14 @@ returning several is describing individual records. Declaring both methods is
919
947
  fine and expected — a hook that does is asked only for its combinations, so
920
948
  its plain fields aren't written twice. Implemented by: `pota`.
921
949
 
950
+ Only a program's **own** export asks for combinations. The full ADIF export is
951
+ one record per contact — the copy the operator keeps, which a reimport or a
952
+ log processor must not read as several contacts — so it asks every hook for
953
+ `fieldsForOneQSO` alone, as app-polo's does. A splitting program answers that
954
+ method too, for the one record — POTA names its first hunted park in `SIG_INFO`
955
+ and every one in `POTA_REF`. A hook answering only combinations is left out of
956
+ the full export, and the kernel logs an error for it as it activates.
957
+
922
958
  **Only the export's OWN hook is asked.** A file submitted to one program is
923
959
  that program's log, so an export that delegates the ADIF build names itself as
924
960
  `mainHandler` in the `ExportRequest` — app-polo's "main handler"
@@ -992,9 +1028,9 @@ writes the contact's own operator on every record, falling back to the station
992
1028
  call's base call or the station call itself. This is a
993
1029
  deliberate divergence from app-polo, whose contact fields come first and whose
994
1030
  main handler's are appended unchecked, so a name both answer is written twice. The **full export**, which asks every
995
- program on the contact, has no such order: hooks answering `fieldCombinationsForOneQSO` are asked as one
996
- group and hooks answering `fieldsForOneQSO` as another, so which of two programs
997
- keeps a shared `MY_SIG` there is not something either of them chose.
1031
+ program on the contact, has no main handler to order by: hooks are asked in priority order,
1032
+ so which of two programs keeps a shared `MY_SIG` there is not something either
1033
+ of them chose.
998
1034
 
999
1035
  Because the winner is picked per field name, **answer a pair whole or not at
1000
1036
  all**: a hook offering `MY_SIG` without `MY_SIG_INFO` takes the name and leaves
@@ -1113,7 +1149,7 @@ interface CommandHook {
1113
1149
  // CommandCatalogEntry: {command, describe, params?, category?, needsOperation?, expectsParams?, suggest?}
1114
1150
  // CommandAction (closed set): {setVfo?, setPower?, updateQso?, addQso?,
1115
1151
  // setKeyerSpeed?, showKeyer?, setEnterSends?, sendSpots?, autoRespot?, syncAll?, updateSettings?, updateSetting?, openSettings?,
1116
- // updateOperation?, markSegment?, setCallField?, reloadDataFiles?, devNotice?, devStatus?,
1152
+ // updateOperation?, markSegment?, findInLog?, setCallField?, setTime?, reloadDataFiles?, devNotice?, devStatus?,
1117
1153
  // devProgress?, devHammer?, devSplash?, devOnboard?, devTester?, devPopup?,
1118
1154
  // devTimeTravel?, toggleExperiment?}
1119
1155
  ```
@@ -1698,6 +1734,11 @@ interface PanelHook {
1698
1734
  // | {kind: 'scene', scene, title?, triggers?} (see Panel scene reference)
1699
1735
  ```
1700
1736
 
1737
+ `operation` is the operation's data with its `uuid`, its
1738
+ `startAtMillisMin`/`startAtMillisMax`, and `operatorCall`: who is at the key on
1739
+ this device, resolved for its position in a multi-station operation, and empty
1740
+ when nobody is named.
1741
+
1701
1742
  The host addresses a panel as `ext:<hookKey>:<key>`, and that id is stored
1702
1743
  inside saved layouts — renaming a `key` drops the panel out of every
1703
1744
  arrangement holding it.
package/docs/settings.md CHANGED
@@ -79,13 +79,19 @@ export interface SimpleSettingsPanel {
79
79
  // below — this only controls the top-level nav.
80
80
  sidebar?: boolean;
81
81
  // Defaults false. Set true to also show this panel as a row in the Data
82
- // Files screen's own "Extensions" section — the ONLY host-recognized
83
- // alternate surface today, so leave this false for anything not actually
84
- // about managing data files. Independent of `sidebar`; a panel usually
85
- // wants both false-then-true, but the two aren't coupled at the type
86
- // level — inferring this from `sidebar: false` alone would sweep any
87
- // sidebar-opt-out panel in here.
82
+ // Files screen's own "Extensions" section — leave this false for anything
83
+ // not actually about managing data files. Independent of `sidebar`; a
84
+ // panel usually wants both false-then-true, but the two aren't coupled at
85
+ // the type level — inferring this from `sidebar: false` alone would sweep
86
+ // any sidebar-opt-out panel in here.
88
87
  dataFilesSection?: boolean;
88
+ // The key of one of the app's own settings sections ('spotting', the
89
+ // Spotting section) to show this panel inside, as one row that opens it
90
+ // in a dialog — instead of a section of its own in Settings and a sidebar
91
+ // entry, whatever `sidebar` says. An app without that section (an older
92
+ // one, or a key it doesn't know) shows the panel as if this weren't set,
93
+ // so it needs no newer API level.
94
+ section?: string;
89
95
  fields: (SettingsField | FormActionElement)[];
90
96
  }
91
97
  ```
@@ -136,6 +142,7 @@ export interface SettingsPanelDescriptor {
136
142
  order?: number;
137
143
  sidebar?: boolean; // see SimpleSettingsPanel.sidebar above; defaults true
138
144
  dataFilesSection?: boolean; // see SimpleSettingsPanel.dataFilesSection above; defaults false
145
+ section?: string; // see SimpleSettingsPanel.section above
139
146
  }
140
147
 
141
148
  export interface DynamicSettingsPanel {
@@ -204,7 +211,14 @@ values). Tapping it opens an editor dialog with a row per item plus an
204
211
  dialog. Editing or adding an item opens the same form renderer
205
212
  recursively, scoped to `itemFields`, in a nested dialog — there is no
206
213
  separate "list item editor" widget, just `FormRenderer` given a smaller
207
- `FormDefinition`.
214
+ `FormDefinition`. An existing item is deleted from that dialog's footer,
215
+ once the operator confirms.
216
+ Saving it merges what it edited into the item, so keys it has no field
217
+ for are kept.
218
+
219
+ Set `itemSwitch` to the key of an on/off value in each item to give every
220
+ row a switch for it, and leave that key out of `itemFields`. An item without
221
+ it reads as on.
208
222
 
209
223
  Set `orderable: true` to allow drag-to-reorder in the editor; omit it for
210
224
  lists where order doesn't matter.
package/docs/templates.md CHANGED
@@ -65,7 +65,8 @@ An ISO-8601 UTC string, for the `date` filter: `{{ now | date: '%H:%M' }}`.
65
65
  ### `op` — the operation
66
66
  `station` (the field as typed, which may hold several comma-separated
67
67
  callsigns; `call` is the same value, the name `qso.call` has), `stations`
68
- (that list, uppercased and deduped), `operator`,
68
+ (that list, uppercased and deduped), `operator` (who is at the key on this
69
+ device, empty when nobody is named),
69
70
  `title` (the generated, ref-derived one), `userTitle` (the operator's own
70
71
  words), `grid`, `refs`, `uuid`, `qsoCount`, `date`, `dateCompact`, `time`,
71
72
  `at`, `startDate`, `startTime`, `startAt`, `endDate`, `endTime`, `endAt`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ham2k/extension-sdk",
3
- "version": "0.12.11",
3
+ "version": "0.12.13",
4
4
  "description": "Write extensions for the Ham2K Logger: typed hook contracts and the host API",
5
5
  "keywords": [
6
6
  "ham2k",