@ham2k/extension-sdk 0.5.0 → 0.5.1

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.0
1
+ // @ham2k/extension-sdk 0.5.1
2
2
  /** Experimental v1 scene contract. All coordinates are in the scene's viewBox. */
3
3
  export interface SvgScene {
4
4
  version: 1;
@@ -60,6 +60,7 @@ function qsoValues(qso) {
60
60
  const their = qso.their ?? {};
61
61
  const our = qso.our ?? {};
62
62
  const startMillis = Number(qso.startAtMillis ?? 0);
63
+ const serialSent = refField(qso.refs, "ourSerial");
63
64
  return {
64
65
  call: their.call ?? "",
65
66
  their,
@@ -71,12 +72,31 @@ function qsoValues(qso) {
71
72
  // sides — `qso.our.sent` is what WE sent, i.e. the report they received.
72
73
  rstSent: our.sent ?? "",
73
74
  rstRcvd: their.sent ?? "",
75
+ // Unpadded, as stored: `001` or `1` is the operator's call, not the
76
+ // contest's, so it is left to the `pad` filter.
77
+ serialSent,
78
+ serialRcvd: refField(qso.refs, "theirSerial"),
79
+ // The short forms are the SENT side: a CW message is what we send, and
80
+ // that is where these get typed.
81
+ rst: our.sent ?? "",
82
+ serial: serialSent,
74
83
  notes: qso.notes ?? "",
75
84
  refs: qso.refs ?? [],
76
85
  ...startMillis > 0 ? dateValues(startMillis) : { date: "", dateCompact: "", time: "", at: "" },
77
86
  startAtMillis: startMillis
78
87
  };
79
88
  }
89
+ function refField(refs, field) {
90
+ if (!Array.isArray(refs)) return "";
91
+ for (const ref of refs) {
92
+ if (ref === null || typeof ref !== "object" || Array.isArray(ref)) continue;
93
+ const value = ref[field];
94
+ if (typeof value !== "string" && typeof value !== "number") continue;
95
+ const text = String(value).trim();
96
+ if (text !== "") return text;
97
+ }
98
+ return "";
99
+ }
80
100
  function logValues(values) {
81
101
  return {
82
102
  station: values.station ?? "",
package/dist/templates.js CHANGED
@@ -30,7 +30,16 @@ function registerFilters(liquid2) {
30
30
  (value) => String(value ?? "").replace(/[^A-Za-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")
31
31
  );
32
32
  liquid2.registerFilter("alnum", (value) => String(value ?? "").replace(/[^A-Za-z0-9]/g, ""));
33
+ liquid2.registerFilter("pad", (value, width = 3) => {
34
+ const text = String(value ?? "").trim();
35
+ return text === "" ? "" : text.padStart(Number(width) || 0, "0");
36
+ });
37
+ liquid2.registerFilter("cut", (value, digits = "09") => {
38
+ const chosen = String(digits ?? "");
39
+ return String(value ?? "").replace(/[0159]/g, (digit) => chosen.includes(digit) ? CUT_NUMBERS[digit] : digit);
40
+ });
33
41
  }
42
+ const CUT_NUMBERS = { "0": "T", "1": "A", "5": "E", "9": "N" };
34
43
  class TemplateError extends Error {
35
44
  constructor(message, options) {
36
45
  super(message);
package/docs/templates.md CHANGED
@@ -74,12 +74,22 @@ that contact's length.
74
74
 
75
75
  ### `qso` — one contact
76
76
  `call`, `their`, `our` (both whole, so `qso.their.guess.name` reaches the
77
- lookup's answer), `band`, `mode`, `freq`, `rstSent`, `rstRcvd`, `notes`,
78
- `date`, `dateCompact`, `time`, `at`, `startAtMillis`, `refs`.
77
+ lookup's answer), `band`, `mode`, `freq`, `rstSent`, `rstRcvd`, `serialSent`,
78
+ `serialRcvd`, `notes`, `date`, `dateCompact`, `time`, `at`, `startAtMillis`,
79
+ `refs` — and two short forms for the sent side, `rst` (= `rstSent`) and
80
+ `serial` (= `serialSent`), since a CW message is what we send and that is
81
+ where these get typed: `{{ qso.call }} {{ qso.rst | cut }} {{ qso.serial | pad: 3 | cut }}`.
79
82
 
80
83
  `rstSent` is what WE sent — QSON stores each side's report under its own
81
84
  `sent`, and these are named for the operator's view of the contact.
82
85
 
86
+ `serialSent` / `serialRcvd` are a contest's serial numbers, blank outside
87
+ one. A contest keeps them on its own ref, so they are found by field name —
88
+ the first ref carrying `ourSerial` / `theirSerial` — which is what lets one
89
+ message serve every serial contest (docs/design/contests.md §5.8). In a CW
90
+ message `serialSent` is the number the serial field is SHOWING, the one the
91
+ QSO will be logged with. It is unpadded (`7`, not `007`): use `pad`.
92
+
83
93
  ### `config` — a panel's own form values
84
94
  Whatever that panel's `form` declared, under the keys it used.
85
95
 
@@ -112,12 +122,19 @@ with nothing to say it was invented.
112
122
 
113
123
  All of Liquid's own (`date`, `downcase`, `upcase`, `strip`, `default`,
114
124
  `join`, `size`, `first`, `last`, `round`, `truncate`, `replace`, `map`,
115
- `where`, …), plus two:
125
+ `where`, …), plus four:
116
126
 
117
127
  | filter | does | example |
118
128
  |---|---|---|
119
129
  | `dash` | non-alphanumerics → `-`, collapsed and trimmed, **case preserved** | `N0CALL/P` → `N0CALL-P` |
120
130
  | `alnum` | strip to alphanumerics | `2026-07-27` → `20260727` |
131
+ | `pad: n` | zero-pad on the left to `n` characters (default 3); **blank stays blank** | `7` → `007` |
132
+ | `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` |
133
+
134
+ `pad` leaves a blank alone because a CW message can be keyed before the
135
+ serial field has a number, and `000` sent in that gap is a serial the log
136
+ will never agree with. Order matters with `cut`: pad first, or the padding
137
+ zeros go out uncut.
121
138
 
122
139
  `alnum` is app-polo's `compact` helper under a different name: Liquid already
123
140
  has a `compact` (it drops nils from an **array**, and `op.refs`/`qso.refs` are
@@ -140,6 +157,10 @@ Each of these was measured against the shipped runtime, not assumed.
140
157
  number as SECONDS, so epoch millis render in the year 58567. Use the `…At`
141
158
  strings.
142
159
 
160
+ **`cut`'s digit list must be quoted.** Liquid reads a bare `0159` as the
161
+ number 159 before the filter sees it, so `{{ n | cut: 09 }}` cuts only the
162
+ nines — the zero is gone with no error. Write `cut: "09"`.
163
+
143
164
  **Unknown names are silent.** An unknown variable renders empty and an
144
165
  unknown filter passes its value through. That is deliberate — an operator's
145
166
  typo costs one line, not the document — but it means a misspelled
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ham2k/extension-sdk",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Write extensions for the Ham2K Logger: typed hook contracts and the host API",
5
5
  "keywords": [
6
6
  "ham2k",