@ham2k/extension-sdk 0.10.0 → 0.10.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.10.0
1
+ // @ham2k/extension-sdk 0.10.1
2
2
  /** Experimental v1 panel scene contract. All coordinates are in the scene's viewBox. */
3
3
  export interface PanelScene {
4
4
  version: 1;
@@ -661,6 +661,7 @@ export interface AdifFieldsHook {
661
661
  fieldsForOneQSO(args: {
662
662
  qso: Record<string, JSONValue>;
663
663
  operation: Record<string, JSONValue>;
664
+ mainHandler?: boolean;
664
665
  }, ctx: HookContext): Promise<{
665
666
  name: string;
666
667
  value: string;
@@ -526,15 +526,72 @@ Beyond what the packer already refused:
526
526
 
527
527
  Each has its own message. A bundle that cannot be installed says why.
528
528
 
529
- ## Native only, for now
530
-
531
- Installed bundles are files on disk. On web the app runs from its own assets
532
- and, for authors, the dev server — [development.md](https://github.com/ham2k/halo/blob/main/docs/extensions/development.md). So on
533
- web there is nothing to install into: no install from a file, no install or
534
- update from the catalog, and none of the pre-loaded extensions above.
535
-
536
- "For now" is a deferral, not a fact about the platform. Almost everything
537
- here is already platform-neutral — the zip walk, the manifest rules, the
538
- consent screen, the hash check — and what is left is a persistence seam of
539
- about six operations. `HALO-583` is the card that picks it up, and says what
540
- has to be decided first.
529
+ ## Where installed bundles live
530
+
531
+ Everything above is platform-neutral — the zip walk, the manifest rules, the
532
+ consent screen, the hash check. What differs is only where the bytes are kept
533
+ (`BundleStorage`):
534
+
535
+ - **natively**, one directory per key under the app's data directory, one
536
+ file per bundle entry;
537
+ - **on web**, one IndexedDB record per key (database `halo-extensions`),
538
+ holding every entry's bytes under its own name. IndexedDB rather than the
539
+ Origin Private File System, which would mirror the native layout: every
540
+ browser HaLo runs in supports it the same way, while OPFS writes from the
541
+ page are newer and patchier on Safari.
542
+
543
+ So the web build installs from a file, installs and updates from the catalog,
544
+ pre-loads the shipped bundles and takes the upgrade exactly as the native ones
545
+ do.
546
+
547
+ ### When the store is lost
548
+
549
+ The store can go while the preferences describing it survive: a browser
550
+ clearing IndexedDB alone, a data directory deleted by hand, a store that
551
+ failed mid-session. (When a browser clears a whole site, preferences go with
552
+ it, and the app starts as a new install.) So what the operator installed is
553
+ kept apart from the store that holds it, and put back from there.
554
+
555
+ `extensionsInstalled` records every extension the operator installed
556
+ (`{key: name}`). Every install writes it; only an uninstall — or Forget, below
557
+ — removes it. An install that predates it is seeded from its store once, and
558
+ after that it never follows the store: a store found short is exactly what it
559
+ must not shrink to. Whatever is recorded there and not in the store is
560
+ **missing**, however it went — judged by the keys the store holds, not by the
561
+ extensions the app offers, since a built-in or a dev server can stand in front
562
+ of an installed bundle. A store that cannot be read at all has lost nothing:
563
+ that launch offers nothing, rather than everything back into a failing store.
564
+ Where each came from is its provenance record's answer, and a key with none
565
+ came from a file.
566
+
567
+ The operator's on/off choice for a missing extension is kept too: the boot
568
+ cleanup that prunes settings for keys the app no longer knows treats every
569
+ recorded key as known.
570
+
571
+ Putting them back, after the app starts:
572
+
573
+ - **Shipped bundles** come back at boot from the app's own assets, unasked and
574
+ offline. The pre-load record says they were already offered, which is true
575
+ and beside the point; they are installed anyway, never as a first install,
576
+ so their settings and on/off choice stand. An install that has not taken the
577
+ upgrade gets back only what it had.
578
+ - **Everything else** is offered in a dialog once the boot has settled:
579
+ - extensions from the catalog in use are listed to install, at the release
580
+ it serves now. **Later** keeps every one for the next launch;
581
+ - extensions from a file are listed to install again from their files, and
582
+ ones from a different catalog as not available here. Neither is fetched —
583
+ the bytes would come from a publisher the operator never agreed to;
584
+ - every row has **Forget**, which drops it with everything it stored — the
585
+ only way out for one the catalog no longer serves, which would otherwise
586
+ be offered, and fail, every launch.
587
+ - **Install** installs each on its own: one that is not listed, will not
588
+ download or fails the check costs only itself. The runtime restarts with
589
+ everything that landed; a bundle it will not evaluate is set aside, as the
590
+ upgrade does, and the runtime restarts without it. The result names what
591
+ could not be installed (offered again next launch) and what installed but
592
+ would not run (kept, marked in the panel, where an update is the way back);
593
+ says so when the runtime would not start at all, rather than calling it a
594
+ success; and says the app is still busy — offering them again next launch
595
+ — when another catalog pass holds the store.
596
+ - The offer waits for the boot's catch-up to finish, or for an upgrade that
597
+ every catch-up stepped aside for to let go.
package/docs/hooks.md CHANGED
@@ -741,12 +741,17 @@ knowing them. Called by the `adif` extension **inside the runtime** via
741
741
 
742
742
  ```ts
743
743
  interface AdifFieldsHook {
744
- fieldsForOneQSO(args: { qso, operation }, ctx): Promise<{name, value}[]>
744
+ fieldsForOneQSO(args: { qso, operation, mainHandler? }, ctx): Promise<{name, value}[]>
745
745
  // One field set PER ADIF RECORD this contact should produce.
746
- fieldCombinationsForOneQSO?(args: { qso, operation }, ctx): Promise<{name, value}[][]>
746
+ fieldCombinationsForOneQSO?(args: { qso, operation, mainHandler? }, ctx): Promise<{name, value}[][]>
747
747
  }
748
748
  ```
749
749
 
750
+ `mainHandler` is true when the file is this extension's own export — app-polo's
751
+ argument of the same name. It is how a program writes what only its own
752
+ submission asks for: SOTA's files carry both stations' gridsquares, which the
753
+ full export leaves to the contact's own privacy rules.
754
+
750
755
  POTA contributes `SIG/SIG_INFO/POTA_REF` (hunted refs on the QSO) and
751
756
  `MY_SIG/MY_SIG_INFO/MY_POTA_REF` (activation refs on the operation).
752
757
 
@@ -819,11 +824,18 @@ would claim no reference at all.
819
824
  **A field name is written once per record, and the first to carry a value
820
825
  wins.** ADIF gives no meaning to a repeated field, so where two hooks answer
821
826
  the same name — the full export asks every program on the contact, and a park
822
- and a summit both answer `MY_SIG` — the later one is dropped, and the contact's own fields
823
- outrank all of them. Hooks are asked main handler first, then each
824
- `includeFieldsFrom` key in the order given, so that order decides — whichever
825
- of the two methods each hook answered. app-polo resolves a collision the same
826
- way ("keep the first one defined"). The **full export**, which asks every
827
+ and a summit both answer `MY_SIG` — the later one is dropped. Hooks are asked
828
+ main handler first, then each `includeFieldsFrom` key in the order given, so
829
+ that order decides — whichever of the two methods each hook answered. The
830
+ contact's own fields come after every hook's: a program knows what its file
831
+ needs in a field better than the contact's generic answer does (the section a
832
+ Field Day station sent, a satellite's downlink band). The exception is what
833
+ identifies the contact — `CALL`, `QSO_DATE`, `TIME_ON`, `QSO_DATE_OFF`,
834
+ `TIME_OFF`, `BAND`, `FREQ`, `MODE`, `SUBMODE`, `STATION_CALLSIGN`,
835
+ `OPERATOR`: a hook answering one of those is dropped, since it would rewrite
836
+ every record it is asked about, the whole-log backup included. This is a
837
+ deliberate divergence from app-polo, whose contact fields come first and whose
838
+ main handler's are appended unchecked, so a name both answer is written twice. The **full export**, which asks every
827
839
  program on the contact, has no such order: hooks answering `fieldCombinationsForOneQSO` are asked as one
828
840
  group and hooks answering `fieldsForOneQSO` as another, so which of two programs
829
841
  keeps a shared `MY_SIG` there is not something either of them chose.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ham2k/extension-sdk",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Write extensions for the Ham2K Logger: typed hook contracts and the host API",
5
5
  "keywords": [
6
6
  "ham2k",
@@ -71,12 +71,12 @@
71
71
  "build": "node build.mjs"
72
72
  },
73
73
  "//peerDependencies": [
74
- "The libraries the extension host carries. An extension does NOT need these",
74
+ "The libraries the extension host provides. An extension does NOT need these",
75
75
  "installed: the build preset rewrites each one the manifest declares into a",
76
76
  "lookup on the host's single instance, before esbuild ever resolves it, and",
77
- "the published .d.ts carries their types rolled in. They are here, optional,",
77
+ "the published .d.ts has their types rolled in. They are here, optional,",
78
78
  "for the one case that does reach the filesystem \u2014 `inline: [...]`, which",
79
- "ships your own copy because you need a version the host does not carry."
79
+ "ships your own copy because you need a version the host does not provide."
80
80
  ],
81
81
  "peerDependencies": {
82
82
  "@ham2k/lib-callsigns": "*",