@meri-imperiumi/signalk-passage-briefing 0.8.0 → 0.8.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/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.8.1] - 2026-10-08
6
+
7
+ ### Added
8
+ - Branding on the webapp page (per the Signal K visuals spec): the plugin's app icon (`icon-256.png`) serves as favicon and the base canvas color ships as the `theme-color` meta, so browser chrome matches the console background in both day and night modes.
9
+
10
+ ### Fixed
11
+ - The per-minute source-status tick no longer touches the logbook — fixes "Server unreachable: Fetch is aborted" with an empty route picker on production since 0.8.0. The checklist's logbook probe went through the resources API, whose `logentries` provider answers even a `limit: 1` listing by reading and schema-validating every day file — on a multi-year log that meant overlapping full-store scans every 60 s starving the server so `/api/status` and `/api/routes` blew the webapp's 8 s abort timeout. The logbook is a once-used learning resource (backfill), not an ongoing ingest source, so it leaves the data source statuses entirely; with no provider installed, backfill now fails loudly with the reason instead of silently reporting zero events.
12
+
5
13
  ## [0.8.0] - 2026-10-06
6
14
 
7
15
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meri-imperiumi/signalk-passage-briefing",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Offshore passage daily briefing webapp for Signal K",
5
5
  "main": "plugin/index.js",
6
6
  "scripts": {
package/plugin/index.js CHANGED
@@ -25,7 +25,6 @@ const { mkdir, readFile, unlink, writeFile } = require("node:fs/promises");
25
25
  const { PassageStateMachine } = require("./state-machine.js");
26
26
  const { PassageDatabase } = require("./sqlite-db.js");
27
27
  const {
28
- logbookAvailable,
29
28
  readLogbookEntries,
30
29
  readLogbookSailEvents,
31
30
  } = require("./logbook-source.js");
@@ -1997,14 +1996,6 @@ module.exports = (app) => {
1997
1996
  now,
1998
1997
  });
1999
1998
  }
2000
- sourceStatus.record({
2001
- id: "sk-logbook",
2002
- label: "Logbook (signalk-logbook)",
2003
- kind: "signalk",
2004
- url: "resources/logentries",
2005
- ...((await logbookAvailable(app)) ? {} : { absent: true }),
2006
- now,
2007
- });
2008
1999
  }
2009
2000
 
2010
2001
  /**
@@ -465,17 +465,20 @@ function normalizeLogEntry(entry) {
465
465
  * explicit window, so "all history" is an explicit epoch-to-eternity
466
466
  * range.
467
467
  *
468
- * A missing resources API (old server) or a failed listing (the
469
- * logbook plugin not installed — no provider for the type) reads as
470
- * an empty store, never a failure: this consumer degrades, the same
471
- * way a missing store directory used to.
468
+ * Loud on an unusable source: a missing resources API (old server)
469
+ * or a failed listing (the logbook plugin not installed — no provider
470
+ * for the type) throws with the reason, so the human-initiated runs
471
+ * that read the logbook (backfill, event diagnostics) can say why
472
+ * they found nothing instead of silently reporting zero events.
472
473
  *
473
474
  * @param {object} app - Signal K plugin `app`
474
475
  * @returns {Promise<Array<object>>} Normalized entries, oldest first
476
+ * @throws {Error} When the resources API or the `logentries` provider
477
+ * is unavailable
475
478
  */
476
479
  async function readLogbookEntries(app) {
477
480
  if (typeof app?.resourcesApi?.listResources !== "function") {
478
- return []; // No resources API on this server
481
+ throw new Error("Resources API not available on this server");
479
482
  }
480
483
  let resourceMap;
481
484
  try {
@@ -483,8 +486,10 @@ async function readLogbookEntries(app) {
483
486
  from: ALL_HISTORY_FROM,
484
487
  to: ALL_HISTORY_TO,
485
488
  });
486
- } catch (_error) {
487
- return []; // No provider for the type (logbook not installed)
489
+ } catch (error) {
490
+ throw new Error(
491
+ `No logentries resource provider on this server — install signalk-logbook to backfill from the log (${error.message})`,
492
+ );
488
493
  }
489
494
  if (!resourceMap || typeof resourceMap !== "object") {
490
495
  return [];
@@ -542,26 +547,6 @@ async function readLogbookEntriesRest({ baseUrl, token } = {}) {
542
547
  );
543
548
  }
544
549
 
545
- /**
546
- * Whether the `logentries` resource is being served on this server
547
- * (the signalk-logbook provider is registered). Probed with a
548
- * one-entry listing — cheap even on years of logs.
549
- *
550
- * @param {object} app - Signal K plugin `app`
551
- * @returns {Promise<boolean>}
552
- */
553
- async function logbookAvailable(app) {
554
- if (typeof app?.resourcesApi?.listResources !== "function") {
555
- return false;
556
- }
557
- try {
558
- await app.resourcesApi.listResources(RESOURCE_TYPE, { limit: 1 });
559
- return true;
560
- } catch (_error) {
561
- return false;
562
- }
563
- }
564
-
565
550
  /**
566
551
  * Reads the logbook through the resources API and extracts
567
552
  * sail-change events, optionally bounded to a window (state tracking
@@ -609,7 +594,6 @@ module.exports = {
609
594
  normalizeLogEntry,
610
595
  readLogbookEntries,
611
596
  readLogbookEntriesRest,
612
- logbookAvailable,
613
597
  readLogbookSailEvents,
614
598
  foldTwaDegrees,
615
599
  };
package/public/index.html CHANGED
@@ -3,7 +3,9 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="theme-color" content="#080a0c">
6
7
  <title>Passage Briefing</title>
8
+ <link rel="icon" href="icon-256.png">
7
9
  <link rel="stylesheet" href="css/visuals.css">
8
10
  <script type="module" src="app.js"></script>
9
11
  </head>
@@ -350,8 +350,8 @@ describe("readLogbookEntries", () => {
350
350
  assert.ok(queries[0].from && queries[0].to);
351
351
  });
352
352
 
353
- test("no resources API or no provider reads as an empty store", async () => {
354
- assert.deepEqual(await readLogbookEntries({}), []);
353
+ test("no resources API or no provider fails loudly with the reason", async () => {
354
+ await assert.rejects(readLogbookEntries({}), /Resources API not available/);
355
355
  const failing = {
356
356
  resourcesApi: {
357
357
  async listResources() {
@@ -359,7 +359,10 @@ describe("readLogbookEntries", () => {
359
359
  },
360
360
  },
361
361
  };
362
- assert.deepEqual(await readLogbookEntries(failing), []);
362
+ await assert.rejects(
363
+ readLogbookEntries(failing),
364
+ /logentries resource provider/,
365
+ );
363
366
  });
364
367
  });
365
368
 
@@ -453,7 +456,7 @@ describe("readLogbookSailEvents", () => {
453
456
  assert.equal(windowed[0].eventType, "REEF_INCREASE");
454
457
  });
455
458
 
456
- test("missing provider is not fatal", async () => {
459
+ test("missing provider fails loudly with the reason", async () => {
457
460
  const failing = {
458
461
  resourcesApi: {
459
462
  async listResources() {
@@ -461,7 +464,13 @@ describe("readLogbookSailEvents", () => {
461
464
  },
462
465
  },
463
466
  };
464
- assert.deepEqual(await readLogbookSailEvents({ app: failing }), []);
465
- assert.deepEqual(await readLogbookSailEvents({ app: {} }), []);
467
+ await assert.rejects(
468
+ readLogbookSailEvents({ app: failing }),
469
+ /logentries resource provider/,
470
+ );
471
+ await assert.rejects(
472
+ readLogbookSailEvents({ app: {} }),
473
+ /Resources API not available/,
474
+ );
466
475
  });
467
476
  });
@@ -2110,7 +2110,6 @@ test("source status: SK availability recorded and served at /sources", async ()
2110
2110
  assert.equal(entries["sk-polar"].lastStatus, "ok");
2111
2111
  assert.equal(entries["sk-ships-time"].lastStatus, "absent");
2112
2112
  assert.equal(entries["sk-routes-resources"].lastStatus, "fail");
2113
- assert.equal(entries["sk-logbook"].lastStatus, "absent");
2114
2113
 
2115
2114
  // Configured sources have entries even before their first cycle
2116
2115
  assert.equal(entries["weather-track"].lastStatus, null);