@meri-imperiumi/signalk-passage-briefing 0.13.1 → 0.13.2

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,11 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.13.2] - 2026-10-19
6
+
7
+ ### Fixed
8
+ - Briefing refresh crashed with 502 `Cannot read properties of undefined (reading '1')` when the course provider reported the vessel navigating the briefed route while still heading to its first waypoint (`navigation.course.activeRoute.pointIndex` 0, forward — e.g. just after engaging a route): the progress trim read `raw[pointIndex - 1]` for a last passed waypoint that does not exist yet. Forward pointIndex 0 now trims to the vessel position with nothing trimmed off (`trimmedFromNm` 0), and without a known vessel position the full untrimmed plan serves.
9
+
5
10
  ## [0.13.1] - 2026-10-19
6
11
 
7
12
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meri-imperiumi/signalk-passage-briefing",
3
- "version": "0.13.1",
3
+ "version": "0.13.2",
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
@@ -564,12 +564,25 @@ module.exports = (app) => {
564
564
  ) {
565
565
  return null; // Not past the first point, or an index we cannot use
566
566
  }
567
+ const target = raw[pointIndex];
568
+ const vessel = vesselPosition();
569
+ // Heading to the first waypoint (forward pointIndex 0): no leg of
570
+ // the plan is behind the boat, so there is no last passed waypoint
571
+ // to anchor the trim distance on. The plan starts at the vessel
572
+ // when known (nothing trimmed off, 0 nm in), else serves in full.
573
+ if (!reverse && pointIndex === 0) {
574
+ if (!vessel) {
575
+ return null;
576
+ }
577
+ return {
578
+ coordinates: [[vessel.lon, vessel.lat], ...raw],
579
+ trimmedFromNm: 0,
580
+ };
581
+ }
567
582
  // Travel order decides what remains: forward navigates toward
568
583
  // higher indices, reverse toward lower ones (the course provider's
569
584
  // `reverse` flag). The start point is the vessel's actual position
570
585
  // when available, else the last passed waypoint.
571
- const target = raw[pointIndex];
572
- const vessel = vesselPosition();
573
586
  const start = vessel
574
587
  ? [vessel.lon, vessel.lat]
575
588
  : reverse
@@ -579,7 +592,8 @@ module.exports = (app) => {
579
592
  ? raw.slice(0, pointIndex + 1).reverse()
580
593
  : raw.slice(pointIndex);
581
594
  // Distance along the stored plan from its start to the trim point:
582
- // the boat sits between the target and the last passed point
595
+ // the boat sits between the target and the last passed point (the
596
+ // forward pointIndex 0 case returned above, so this is defined)
583
597
  const reference = reverse ? target : raw[pointIndex - 1];
584
598
  const anchorSlice = reverse
585
599
  ? raw.slice(0, pointIndex + 1)
@@ -2802,6 +2802,61 @@ test("route progress: briefing trims to the sailed legs (work doc #28)", async (
2802
2802
  revPayload.trimmedFromNm > 55 && revPayload.trimmedFromNm < 65,
2803
2803
  `reversed trimmedFromNm ${revPayload.trimmedFromNm}`,
2804
2804
  );
2805
+
2806
+ // Just engaged, heading to the first waypoint (pointIndex 0): no
2807
+ // waypoint is behind the boat, so the plan starts at the vessel
2808
+ // with nothing trimmed off. Regression: the trim used to read
2809
+ // raw[-1] and crashed the refresh with "Cannot read properties of
2810
+ // undefined (reading '1')" (502 on /api/briefing/refresh)
2811
+ app.getSelfPath = (path) =>
2812
+ path === "navigation.position"
2813
+ ? { latitude: -0.05, longitude: 0 }
2814
+ : path === "navigation.course.activeRoute"
2815
+ ? {
2816
+ href: "/resources/routes/r1",
2817
+ pointIndex: 0,
2818
+ }
2819
+ : null;
2820
+ const engagedRes = await call("/api/briefing/refresh", {
2821
+ query: { route: "r1" },
2822
+ });
2823
+ assert.equal(
2824
+ engagedRes.code,
2825
+ null,
2826
+ `engaged refresh: ${JSON.stringify(engagedRes.payload)}`,
2827
+ );
2828
+ const engaged = await call("/api/briefing", { query: { route: "r1" } });
2829
+ const engagedPayload = engaged.payload.payload;
2830
+ assert.ok(
2831
+ Math.abs(engagedPayload.waypoints[0].lat + 0.05) < 0.01,
2832
+ `engaged start ${engagedPayload.waypoints[0].lat}`,
2833
+ );
2834
+ assert.equal(engagedPayload.trimmedFromNm, 0);
2835
+ // The full plan follows the boat: last waypoint is the route end
2836
+ assert.ok(
2837
+ Math.abs(engagedPayload.waypoints.at(-1).lat - 1.5) < 0.01,
2838
+ `engaged end ${engagedPayload.waypoints.at(-1).lat}`,
2839
+ );
2840
+
2841
+ // Same pointIndex 0 but no vessel position: no anchor for the
2842
+ // start — the full plan serves untrimmed
2843
+ app.getSelfPath = (path) =>
2844
+ path === "navigation.position"
2845
+ ? null
2846
+ : path === "navigation.course.activeRoute"
2847
+ ? {
2848
+ href: "/resources/routes/r1",
2849
+ pointIndex: 0,
2850
+ }
2851
+ : null;
2852
+ await call("/api/briefing/refresh", { query: { route: "r1" } });
2853
+ const untrimmed = await call("/api/briefing", { query: { route: "r1" } });
2854
+ const untrimmedPayload = untrimmed.payload.payload;
2855
+ assert.ok(
2856
+ Math.abs(untrimmedPayload.waypoints[0].lat) < 0.01,
2857
+ `untrimmed start ${untrimmedPayload.waypoints[0].lat}`,
2858
+ );
2859
+ assert.equal(untrimmedPayload.trimmedFromNm, undefined);
2805
2860
  } finally {
2806
2861
  globalThis.fetch = originalFetch;
2807
2862
  }