@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 +20 -2
- package/dist/templates.js +13 -0
- package/docs/distribution.md +35 -0
- package/docs/hooks.md +41 -8
- package/docs/templates.md +15 -4
- package/package.json +1 -1
- package/samples/k2hrc-svg-scenes/README.md +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @ham2k/extension-sdk 0.5.
|
|
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 {
|
package/docs/distribution.md
CHANGED
|
@@ -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)` —
|
|
55
|
-
|
|
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.
|
|
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 `
|
|
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.
|
|
1309
|
-
|
|
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
|
|
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
|
|
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
|
|
221
|
-
|
|
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
|
@@ -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
|
|
23
|
-
|
|
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.
|