@ham2k/extension-sdk 0.5.1 → 0.5.3

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.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- // @ham2k/extension-sdk 0.5.1
1
+ // @ham2k/extension-sdk 0.5.3
2
2
  /** Experimental v1 scene contract. All coordinates are in the scene's viewBox. */
3
3
  export interface SvgScene {
4
4
  version: 1;
@@ -231,7 +231,25 @@ export interface HookContext {
231
231
  getOperation?(uuid: string): Promise<Record<string, JSONValue> | null>;
232
232
  getQsos?(operationUuid: string): Promise<Record<string, JSONValue>[] | null>;
233
233
  getSpotsForCall?(call: string, maxAgeMinutes?: number): Promise<Spot[]>;
234
- getHistoryForCall?(call: string): Promise<Record<string, JSONValue>[]>;
234
+ getHistoryForCall?(call: string, options?: HistoryForCallOptions): Promise<Record<string, JSONValue>[]>;
235
+ }
236
+ export interface HistoryForCallOptions {
237
+ refType?: string | string[];
238
+ operation?: string | string[];
239
+ excludeOperation?: string | string[];
240
+ fromDate?: string;
241
+ toDate?: string;
242
+ fromDateMillis?: number;
243
+ toDateMillis?: number;
244
+ fromTime?: string;
245
+ toTime?: string;
246
+ fromTimeMillis?: number;
247
+ toTimeMillis?: number;
248
+ band?: string | string[];
249
+ excludeBand?: string | string[];
250
+ mode?: string | string[];
251
+ excludeMode?: string | string[];
252
+ limit?: number;
235
253
  }
236
254
  export type LookupResult = (CallInfoLookup & {
237
255
  locAccuracy?: number;
package/dist/templates.js CHANGED
@@ -38,6 +38,19 @@ function registerFilters(liquid2) {
38
38
  const chosen = String(digits ?? "");
39
39
  return String(value ?? "").replace(/[0159]/g, (digit) => chosen.includes(digit) ? CUT_NUMBERS[digit] : digit);
40
40
  });
41
+ liquid2.registerFilter(
42
+ "refLabels",
43
+ (value) => (Array.isArray(value) ? value : []).flatMap((ref) => {
44
+ const label = [ref?.shortLabel, ref?.label, ref?.ref].map((text) => typeof text === "string" || typeof text === "number" ? String(text).trim() : "").find((text) => text !== "");
45
+ return label === void 0 ? [] : [label];
46
+ })
47
+ );
48
+ liquid2.registerFilter("sentence", (value, separator = ", ", final = " & ") => {
49
+ if (!Array.isArray(value)) return String(value ?? "");
50
+ const items = value.map((item) => String(item ?? "").trim()).filter((item) => item !== "");
51
+ if (items.length < 2) return items.join("");
52
+ return `${items.slice(0, -1).join(String(separator))}${String(final)}${items[items.length - 1]}`;
53
+ });
41
54
  }
42
55
  const CUT_NUMBERS = { "0": "T", "1": "A", "5": "E", "9": "N" };
43
56
  class TemplateError extends Error {
@@ -393,6 +393,41 @@ as a constant inside the extension that uses it, because these bundles are
393
393
  served to anyone who asks and a `.h2kext` is a zip. An installed extension
394
394
  sees no build secret, whatever key it claims and wherever it came from.
395
395
 
396
+ ### The upgrade to catalog extensions
397
+
398
+ An install that has been running the app's own extensions is offered the move
399
+ at start, and again the moment the catalog experiment is switched on: that
400
+ switch is the operator asking for catalog extensions, and being made to
401
+ relaunch before anything is offered reads as nothing having happened. The
402
+ switch withdraws nothing by itself; only a taken upgrade does, and the offer
403
+ may be deferred either way. A catalog that cannot be reached says so in the
404
+ footer when the operator asked, and says nothing at a launch nobody was
405
+ watching.
406
+
407
+ Accepting installs the pre-loads, then fetches the catalog's twin of every
408
+ other built-in the operator had switched on — `wca` becomes `ham2k-wca` —
409
+ carrying its settings and credentials the same way. Nothing is withdrawn until
410
+ that finishes; a download that fails puts the app's own extensions back in use
411
+ and the offer returns next launch. Twins that did arrive stay on disk, unused
412
+ until an upgrade completes — the built-in each one supersedes is what runs
413
+ meanwhile.
414
+
415
+ The upgrade restarts the extension runtime, which takes the footer items of
416
+ the generation going down with it: a data file's import raises an error rather
417
+ than returning into a torn-down runtime, so its progress line is cleared and
418
+ the file is left unstamped for the next sweep instead of recorded as fresh
419
+ with nothing imported.
420
+
421
+ A few built-ins have no twin on purpose — `qp`, which the catalog carries as
422
+ one extension per party instead — and the app names them itself
423
+ (`kBuiltInsWithoutTwin`). Those are withdrawn without asking the catalog, and
424
+ the dialog that reports the upgrade finished names each one the operator had
425
+ switched on, since it is where they learn it is gone. Any other twin missing
426
+ from the listing fails the upgrade and is asked for again next launch: a
427
+ listing lacks a key the same way whether the catalog will never serve it or has
428
+ not published it yet, and only the app knows which. A built-in retired from the
429
+ catalog on purpose joins that list in the same change.
430
+
396
431
  ### Installing from the catalog
397
432
 
398
433
  **Settings → Extensions → Catalog** lists what [catalog.ham2k.net](https://catalog.ham2k.net)
package/docs/hooks.md CHANGED
@@ -51,8 +51,9 @@ hook method receives `(args, ctx)` where `ctx: HookContext` is:
51
51
  reads above it is cheap enough for the lookup path; it resolves `[]`
52
52
  whenever the cache is cold or the host keeps no spots. Used by
53
53
  `spot-history` to pull a hunted park or summit into the QSO being logged.
54
- * `getHistoryForCall?(call)` — every non-deleted QSO ever logged with `call`,
55
- across every operation, most recent first, as raw QSON — the one log read
54
+ * `getHistoryForCall?(call, options?)` — the most recent non-deleted contacts
55
+ logged with `call` (five unless `limit` says otherwise, never more than
56
+ 50), across every operation, newest first, as raw QSON — the one log read
56
57
  that is cross-operation rather than scoped to a single one, since it
57
58
  answers "who is this station" from the whole log rather than "what's in
58
59
  this operation". Resolves `[]` for no match or no log access; unlike
@@ -60,6 +61,22 @@ hook method receives `(args, ctx)` where `ctx: HookContext` is:
60
61
  Used by `call-history` to fold the most recent past QSO's fields into a
61
62
  lookup result.
62
63
 
64
+ `options` (`HistoryForCallOptions` in `types.ts`) narrow the history
65
+ before it is ordered and limited, so `{ refType: 'cwt' }` finds last
66
+ month's CWT exchange behind a week of POTA contacts. The filters are
67
+ `refType` (a ref in the QSO's own `refs`), `operation`/`excludeOperation`,
68
+ `band`/`excludeBand`, `mode`/`excludeMode` (`SSB` also matches its
69
+ sidebands, and `CW`/`PHONE`/`DATA` match their whole family), whole UTC
70
+ days through `fromDate`/`toDate` or `fromDateMillis`/`toDateMillis`, and
71
+ exact instants through `fromTime`/`toTime` or
72
+ `fromTimeMillis`/`toTimeMillis`. All the bounds are inclusive. A malformed
73
+ option, an unknown one, or an include option given no values
74
+ (`operation: []`) rejects the call instead of answering an unfiltered
75
+ history. Hosts older than these options ignore them and answer the latest
76
+ five contacts unfiltered, so a caller re-checks on the results whatever it
77
+ relies on — and an empty answer there means "none among the latest five",
78
+ not "none at all".
79
+
63
80
  Status legend: ✅ implemented · 🔜 planned (design in DESIGN.md §4).
64
81
 
65
82
  ---
@@ -1206,6 +1223,14 @@ permission (pending challenge code for an email, immediately-active for an
1206
1223
  already-permitted account, or a blank permission for QR-code linking) and
1207
1224
  returns server rejections as `{ error, error_status }` data.
1208
1225
 
1226
+ `linkClient` must NOT mail anything. The core shows the challenge code on
1227
+ screen and offers the email as a separate choice, so a transport that mails
1228
+ here sends a confirmation nobody asked for — and one more on every renewal,
1229
+ since an expiring code re-requests through this same call.
1230
+ `linkClientWithEmail` is the call the operator reaches by choosing the
1231
+ email over the code, and the only one in this seam that mails. A server
1232
+ that mails by default (LoFi does) has to be told not to.
1233
+
1209
1234
  The account-management methods are all optional and all follow that same
1210
1235
  "a rejection is data, not a throw" convention, because every one of them
1211
1236
  reports to an operator in the middle of an action: `setAccountEmail` /
@@ -1256,7 +1281,7 @@ numeric value patches; local-only controls and animation frames make no bridge
1256
1281
  calls. Controls with `continuous: true` send `change` events during dragging and a
1257
1282
  final `commit` on release; pending movements are coalesced to the newest value.
1258
1283
  Numeric text supports `scale`, `truncate`, `modulo`, and `minIntegerDigits`
1259
- for local numeric formatting. The Radio Panel Example uses these for its Modern
1284
+ for local numeric formatting. KI2D’s Radio Panel uses these for its Modern
1260
1285
  frequency groups; its settings offer Modern/LCD styles and radio selection. Local sliders may use `hover` and `discrete` hourly buckets. Text `samples`
1261
1286
  may hold numbers or preformatted strings; control `valueLabels` supplies accessible
1262
1287
  value descriptions. Transform/opacity bindings may also supply numeric `samples`,
@@ -1302,12 +1327,12 @@ Button controls can supply `menu: [{label, event}]` to open a native dropdown.
1302
1327
  Selecting an item emits its `event` as the action with the original control ID;
1303
1328
  dismissing the menu emits nothing.
1304
1329
 
1305
- The `svg-weather`, `svg-solar`, and `svg-radio` reference panels live in
1330
+ The `ki2d-weather-panel`, `ki2d-solar-panel` and `ki2d-radio-panel` reference
1331
+ panels live in
1306
1332
  [ham2k/extensions](https://github.com/ham2k/extensions/tree/main/extensions/dashboard).
1307
1333
  They use public host APIs and ship as `.h2kext` packages, not app assets.
1308
- Install the packages, then add their panels through Edit Layout. Their original
1309
- extension and panel keys are retained so existing placements and settings survive.
1310
- See that repository’s dashboard README for building against the unreleased SDK.
1334
+ Install the packages, then add their panels through Edit Layout. See that
1335
+ repository's dashboard README for building them.
1311
1336
 
1312
1337
  The radio host calls are manifest capabilities, refused unless declared — the
1313
1338
  same shape as `requiresLocation`, and shown to the operator at install:
@@ -1315,8 +1340,16 @@ same shape as `requiresLocation`, and shown to the operator at install:
1315
1340
  `"requiresRadioWrite": true` allows `host.tuneRadio()` and
1316
1341
  `host.setRadioConnection()`, and implies read, since a command answers with the
1317
1342
  radio's state. An undeclared call rejects rather than answering null.
1343
+ A capability belongs to the bundle that declared it, and to the sub-extensions
1344
+ that bundle defines under its own key. A separately installed bundle never
1345
+ inherits one through its name: `rigpanel-themes` gets nothing from `rigpanel`.
1346
+
1347
+ An `onEvent` that cannot read its panel's config is refused rather than run
1348
+ with an empty one, since a config is where a panel keeps which radio it
1349
+ commands. The host allows an event up to about ten seconds in all (config, then
1350
+ the hook); the control shows its pending value until the outcome is known.
1318
1351
 
1319
- **Radio Panel Example** is the CAT reference extension. `host.listRadios()` lists configured
1352
+ **KI2D’s Radio Panel** is the CAT reference extension. `host.listRadios()` lists configured
1320
1353
  local transceivers. `host.readRadio(id?)` returns
1321
1354
  a **local** radio's reported frequency (Hz), mode, connection state,
1322
1355
  power, TX state and fresh telemetry, or null on hosts without this API.
package/docs/templates.md CHANGED
@@ -34,7 +34,9 @@ about 2 µs each.
34
34
  the namespaces are deliberately polo's. The syntax around them does not:
35
35
  `{{#if}}` is `{% if %}{% endif %}`, `{{#each}}` is `{% for %}{% endfor %}`,
36
36
  `{{> Partial}}` has no equivalent, and helpers are filters
37
- (`{{ dash x }}` → `{{ x | dash }}`).
37
+ (`{{ dash x }}` → `{{ x | dash }}`). polo's
38
+ `{{#join op.refs separator=", " final=" & "}}{{or shortLabel label key}}{{/join}}`
39
+ is `{{ op.refs | refLabels | sentence }}`.
38
40
 
39
41
  ## What a template can name
40
42
 
@@ -122,7 +124,7 @@ with nothing to say it was invented.
122
124
 
123
125
  All of Liquid's own (`date`, `downcase`, `upcase`, `strip`, `default`,
124
126
  `join`, `size`, `first`, `last`, `round`, `truncate`, `replace`, `map`,
125
- `where`, …), plus four:
127
+ `where`, …), plus six:
126
128
 
127
129
  | filter | does | example |
128
130
  |---|---|---|
@@ -130,12 +132,20 @@ All of Liquid's own (`date`, `downcase`, `upcase`, `strip`, `default`,
130
132
  | `alnum` | strip to alphanumerics | `2026-07-27` → `20260727` |
131
133
  | `pad: n` | zero-pad on the left to `n` characters (default 3); **blank stays blank** | `7` → `007` |
132
134
  | `cut: "digits"` | CW cut numbers for the listed digits, of `0`→`T` `1`→`A` `5`→`E` `9`→`N`; default `"09"` | `599` → `5NN`, `{{ 9 \| pad: 3 \| cut }}` → `TTN` |
135
+ | `refLabels` | a list of refs → each one's `shortLabel`, else `label`, else `ref`; a ref with none of them is skipped — an operation's contest ref HAS a label (`CQ WPX CW`) and is listed | `op.refs` → `POTA US-1234`, `KFF-9999` |
136
+ | `sentence: "sep", "final"` | joins a list with a different LAST separator (defaults `", "` and `" & "`); blanks are dropped | `A, B & C`, `A & B` |
133
137
 
134
138
  `pad` leaves a blank alone because a CW message can be keyed before the
135
139
  serial field has a number, and `000` sent in that gap is a serial the log
136
140
  will never agree with. Order matters with `cut`: pad first, or the padding
137
141
  zeros go out uncut.
138
142
 
143
+ `refLabels` exists because `map` reads one field: `map: "shortLabel"` drops a
144
+ reference that resolved to nothing, since it carries no labels — an
145
+ operation at an unlisted park would print as though it had none. Liquid's own
146
+ `array_to_sentence_string` is the near miss for `sentence`: it always writes
147
+ the serial comma (`A, B, & C`).
148
+
139
149
  `alnum` is app-polo's `compact` helper under a different name: Liquid already
140
150
  has a `compact` (it drops nils from an **array**, and `op.refs`/`qso.refs` are
141
151
  arrays), and `registerFilter` overwrites without warning, so taking that name
@@ -217,8 +227,9 @@ will jump rather than count. Show HH:MM.
217
227
  | CW messages | Radio settings `cwMessage1..8`, keyed on F1-F8 through the radio (docs/design/cat.md § CW keying) — rendered by the `template` hook (hooks.md) | by the operator, in the Station dialog's Messages… dialog |
218
228
 
219
229
  NOTES and COMMENT default to QSO notes and are withheld when private data
220
- is off. QSLMSG defaults to empty for program exports, and to the operation’s
221
- references for the whole-log ADIF. Custom QSL messages may use contact values.
230
+ is off. QSLMSG defaults to empty for program exports, and for the whole-log
231
+ ADIF to the operation’s references as `refLabels | sentence` names them —
232
+ “POTA US-0001 & WWFF KFF-0002”. Custom QSL messages may use contact values.
222
233
  Templates receive no withheld private or lookup values. File titles obey the
223
234
  private-data choice; filename templates are labels for the operator’s files.
224
235
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ham2k/extension-sdk",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Write extensions for the Ham2K Logger: typed hook contracts and the host API",
5
5
  "keywords": [
6
6
  "ham2k",
@@ -19,6 +19,6 @@ node build.mjs
19
19
  node ../../tools/h2kext-pack.mjs build -o /tmp/k2hrc-svg-scenes.h2kext
20
20
  ```
21
21
 
22
- For the standalone Flutter preview and migration plan, see
23
- `docs/design/svg-scenes.md` in the logger repository. This is an API experiment,
22
+ For the contract and migration plan, see `docs/design/svg-scenes.md` in the
23
+ logger repository. This is an API experiment,
24
24
  not a replacement for the production weather/solar panels yet.