signalk-pypilot-newui 2.7.0 → 2.7.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
@@ -1,1870 +1,177 @@
1
1
  # Changelog
2
2
 
3
- ## 2.7.0 — 2026-09-14 — Rev246..Rev277: connection resilience + audit follow-up + full i18n audit
4
-
5
- **Rev277 QA fix (latency of detection)**:
6
-
7
- - **Faster offline detection**: ping cadence 5 s → 2 s, pong window
8
- 8 s → 5 s. Two consecutive missed pings now raise `healthy=false`
9
- in roughly half the previous time — ~5-6 s worst case instead of
10
- ~10-14 s. 30 pings/min is a rounding error next to pypilot_web's
11
- own ~20 Hz value stream, so no measurable extra load on the
12
- Pi Zero W.
13
- - **Active ping on reconnect**: instead of waiting for the next 2 s
14
- tick, the fresh socket emits a `ping` the moment the `connect`
15
- event fires. A live pypilot_web pongs in milliseconds, and the
16
- first value stream that follows clears `coreOffline` immediately.
17
- Trims another ~2-3 s off the "back online" transition Carlos
18
- observed on Rev276.
19
-
20
- **Rev276 QA fix**:
21
-
22
- - **Snapshot survives a failed restore**. Previously the disconnect
23
- snapshot was consumed the moment `_pypilotMaybeOfferRestore`
24
- read it — so if the user pressed "Yes" and the restore failed
25
- (e.g. `POST /engage` returned HTTP 500 because pypilot dropped
26
- again mid-flow), the next reconnection had no snap to re-offer
27
- and the sailor silently lost the chance to restore. Now: the
28
- snap is purged only on **success**, on explicit **No**, or on
29
- the 60 s timeout. On failure it is kept.
30
- - **Snapshot capture no longer clobbers a valid pending snap**. If
31
- pypilot disconnects again while we still hold an unACKed snap
32
- from an earlier outage, the current call keeps the older
33
- snapshot (with the original disconnect timestamp) rather than
34
- overwriting it with a fresh one that may carry
35
- `engaged=false` from the failed restore.
36
- - **Micro-hiccups (<20 s) no longer purge the snap**. Previously
37
- any reconnection swallowed the snapshot; now sub-20 s recoveries
38
- leave it available so if a longer outage follows the sailor
39
- still gets the banner.
40
-
41
- **Rev275 QA fix**:
42
-
43
- - **Restore banner no longer stuck on success**. `verdict("ok")`
44
- only spoke "Target restaurado" and forgot to close the
45
- "Restaurando..." banner — sticky until the next reload. Now the
46
- banner briefly turns green with the success message and
47
- auto-hides in 3 s.
48
-
49
- **Rev274 QA fix (Carlos re-test)**:
50
-
51
- - **Restore verdict window widened from 3 s to 10 s with polling**.
52
- On Rev273 the 3 s deadline still cleared before `pypilotHealthy`
53
- could flip false (pong window is ~8 s), so the visor still
54
- spoke "target restaurado" a few seconds before spotting the
55
- disconnect. Now: after the three HTTP writes succeed, the banner
56
- shows "Restaurando... esperando confirmación pypilot" and the
57
- code polls every 500 ms up to 10 s. Any negative evidence
58
- (`pypilotHealthy=false` or `engaged=false`) aborts to failure
59
- immediately with a specific reason; success voice only fires if
60
- both survive the whole window.
61
-
62
- **Rev273 QA fix**:
63
-
64
- - **Restore banner no longer lies "restaurado" on 200 without
65
- confirmation** (Carlos QA on Rev272). SK Autopilot API v2 returns
66
- 200 as soon as the request reaches the provider, before pypilot
67
- has had a chance to confirm. The success verdict is deferred and
68
- gated on `state.pypilotHealthy !== false` +
69
- `state.engaged === true` — otherwise the failure banner + voice
70
- fire with a specific reason.
71
-
72
- **Rev270..Rev271 delta on top of Rev269**:
73
-
74
- - **i18n audit follow-up** (Rev270): every dynamic string that leaked
75
- through the previous pass moved to the dictionary — the tack log
76
- modal (empty state, chip labels, badge tags, recovery line,
77
- confirm), the maneuver HUD verdicts (VIRADA / TRASLUCHADA), the
78
- Trips card in Setup + its list rows + status line + WhatsApp
79
- caption + KPI grid + highlights + map legend + all detail modal
80
- cards + the PNG report card header/footer + share picker labels +
81
- restore banner. Renamed variable clash in the trip loop where
82
- `t` shadowed the i18n helper. Fixed accidental "AnchorWatch Pro"
83
- reference in the trip caption (memory `feedback_no_commercial_refs`).
84
- - **Second external audit closed** (Rev271): the follow-up report
85
- raised 7 findings against Rev270. Five are landed here as small
86
- targeted fixes; two (concurrent adjustTarget race, angular
87
- normalisation defence for out-of-range inputs) are deferred to
88
- Rev272 with rationale in the commit log.
89
-
90
- **Rev271 fixes**:
91
-
92
- - **Core-offline sticky flag** (audit R03). `PypilotClient` now
93
- distinguishes between "the socket to pypilot_web is up" and "the
94
- pypilot core is alive". A `pypilot_disconnect` event from the web
95
- server sets a sticky `coreOffline` that only clears when the core
96
- actually starts talking again — a fresh catalog delivery or a live
97
- `pypilot` value. `healthy` and `set()` both consume this flag, so
98
- a socket that keeps ponging while the core is dead can no longer
99
- report success on writes. New events `pypilot_offline` /
100
- `pypilot_online` are wired into `index.ts` to update plugin status
101
- and mark the AP provider offline.
102
- - **`allowWrites` gate is now global** (audit R05). Previously only
103
- the plugin's own HTTP routes honoured it — the Autopilot API v2
104
- provider interface, the KIP action PUT handlers and the momentary
105
- switches all bypassed it. The check now lives in
106
- `PypilotClient.set()` (single point every writer routes through)
107
- plus a `writesOff()` 403 response on every PUT handler so
108
- KIP/WilhelmSK/freeboard see a legible reason instead of a silent
109
- no-op.
110
- - **Generation counter cancels stale engage retries** (audit
111
- R01+R04). `AutopilotProvider` bumps `engageGen` on every
112
- `setState` call and cancels any pending NAV-mode 500 ms timer.
113
- `_setWithRetry` accepts an `isStale()` callback that aborts the
114
- backoff between attempts. A disengage landing mid-retry now
115
- invalidates the still-pending engage, and vice versa; the delayed
116
- NAV activation can no longer wake up after a disengage.
117
- - **Restore banner sequenced with hard-fail** (audit R02). The "yes,
118
- restore" path now runs mode → target → engage in that order, each
119
- step guarded by `throwIfNotOk(res, op)` that treats both an
120
- absent response and any HTTP >=400 as a stop condition. Target
121
- is prepared before engage so the AP does not latch on the current
122
- heading first and then jerk. Failure is spoken ("Restore failed"
123
- / "Restauración fallida") and shown as a red line in the banner
124
- instead of the previous silent `catch`.
125
- - **Access-request precedence bug** (audit T09). Fixed `j.href ||
126
- j.requestId ? ... : null` operator precedence that was building
127
- `/requests/undefined` when the server returned an explicit
128
- `href`. Now `j.href || (j.requestId ? ... : null)`.
129
-
130
- **Rev272 fixes** (close the remaining two audit points):
131
-
132
- - **`adjustTarget` serialisation** (audit R06). Two concurrent
133
- `+Δ` calls used to read the same base from `data.target` before
134
- awaiting the write and both wrote `base+Δ`, losing one increment.
135
- A single `_adjustChain` promise now serialises adjusts through
136
- the provider so the second call reads the base written by the
137
- first. Errors on one call do not poison the next caller's turn.
138
- - **`shortestArcRad` bounded + finite guards** (audit R07). The
139
- angular normalisation loop is now a single modulo (constant cost)
140
- and returns 0 on a non-finite delta. `setTarget` and
141
- `adjustTarget` also reject `NaN` / `Infinity` / astronomically
142
- large radians at the boundary with a clear error, so a
143
- misbehaving integration cannot poison provider state.
144
-
145
- ---
146
-
147
- ## 2.7.0 headline — Rev246..Rev269: connection resilience + reliability + full i18n audit
148
-
149
- Sea-trial-driven release. After several sessions of **intensive
150
- sailing and beta testing** on Tunatunes we surfaced (and closed) a
151
- long list of connection-resilience holes, TTS gaps, i18n drifts and
152
- UX rough edges that only show up when the boat is under way, the
153
- TinyPilot resets, or the link flakes. The whole batch was audited
154
- twice by an external LLM auditor between iterations before being
155
- folded back in.
156
-
157
- ### English
158
-
159
- **Added**
160
-
161
- - **Full "Pypilot down" UX cycle**: passive UI reflection tied to a
162
- pong-based heartbeat. When the socket goes silent for &gt; 8 s the
163
- target diamond and TGT chip hide immediately, AP-affecting
164
- controls grey out with a `.pypilot-offline` class, and the
165
- alarm rule `pypilot-disconnected` fires with a spoken message
166
- ("Pypilot desconectado desde hace N segundos") whose h / min / s
167
- units are read as full words instead of abbreviations. On
168
- reconnect: green "Pypilot operativo" toast + voice announcement
169
- + controls reactivate.
170
- - **Three-tier outage handler**: on reconnect the visor compares
171
- the outage length against the last known target. &lt; 20 s: no
172
- action (probably a hiccup). 20 s – 5 min: banner offers to
173
- restore the previous target with S&iacute; / No buttons. &gt; 5 min:
174
- nothing (too long, sailor most likely wants a fresh engage).
175
- - **Optimistic write-then-throw pattern** with `client.set()`
176
- returning `boolean`. `apEngage / apDisengage / apSetMode /
177
- apSetTargetRad / apTack` reject BEFORE mutating optimistic state,
178
- so a phantom command against an offline TinyPilot cannot leave
179
- the UI stuck in "engaged" waiting for a delta that will never
180
- arrive.
181
- - **`_setWithRetry` helper** (250 ms + 750 ms backoff) around the
182
- autopilot-provider adapter for transient socket blips.
183
- - **Watchdog + pong heartbeat** in `pypilot-client.ts`: `healthy`
184
- getter combines `socket.connected` with `lastPongMs &lt;= 8 s`.
185
- Alarm engine consumes `client.healthy` (not `.connected`) so
186
- the network layer and the alarm engine agree on liveness. Alarm
187
- ordering also reworked so `sensor-lost` no longer fires _before_
188
- `pypilot-disconnected` when the whole socket dies.
189
- - **Multi-tab session isolation**: owner keys now live in
190
- `sessionStorage` (per-tab) not `localStorage`, so two visor tabs
191
- do not race to release focus watches.
192
- - **Signed-delta rotation counter** (Rev247) for the HUD progress
193
- during tack maneuvers: accumulates the SIGNED angular delta so
194
- IMU / compass jitter cancels around zero and the counter no
195
- longer drifts to "0&deg; remaining" with the boat still 100&deg;
196
- short. The 0.5&deg; threshold that produced the false report
197
- was removed entirely.
198
- - **Full i18n audit** across EN / ES / DE / FR: purged mixed
199
- Spanish text from EN-only modals (Boat menu, Tack log, Trip
200
- detail, Trip share, Gust marker overlay, Bottom-bar picker,
201
- nav-toggle aria-label), added `data-lang` variants, moved the
202
- bottom-bar strings to i18n keys, added `data-i18n-title` on the
203
- tack buttons. Rebrand "Gota Chain" (which "nobody understood",
204
- per Carlos) as **"Target &amp; Wind arrows"** / "Flechas
205
- Target y Viento" everywhere it is public-facing (README, Info
206
- modal, NPM screenshot title).
207
- - **Spanish orthography pass**: `"COMPAS"` &rarr; `"COMP&Aacute;S"`
208
- in the ES mode label, `<code>compas / gps / viento / real</code>`
209
- &rarr; the literal `<code>compass / gps / wind / true</code>`
210
- string (they are pypilot enum values, not translatable words).
211
- - **Full instructions section** in the Info modal (EN + ES)
212
- covering every tab now that Chart / Race / Doctor / Smart Pilot
213
- / Alarms / Sensor Quality / Session recorder / Log capture /
214
- Debug console / Remote Control Console / Watchdog have all
215
- landed.
216
- - **Health status endpoint** (`/status`) now returns
217
- `healthy: boolean` alongside `connected`.
218
- - **Focus-watch owner map**: `_focusWatches` moved to
219
- `Map<key, Map<owner, entry>>` so a stale owner from a crashed
220
- tab does not hold a watch alive.
221
-
222
- **Fixed**
223
-
224
- - **Phantom writes on offline socket**: `client.set()` no longer
225
- returns a silent success when the socket has no live pong. Every
226
- API-level writer either resolves after the retry succeeds or
227
- throws with `"pypilot offline: <path>"`. Fixes a class of "the
228
- UI says engaged but the boat is going straight" reports.
229
- - **Slow reconnect after power-cycling pypilot**: watchdog interval
230
- reduced to 15 s with a 20 s liveness threshold, and the ap
231
- provider now reconciles state on the reconnect delta so the
232
- visor picks up the fresh mode / target immediately.
233
- - **`refreshPypilotValues` merge bug**: pilot / profile switch was
234
- masked by the "don't overwrite existing keys" heuristic. Delta
235
- handlers for `ap.pilot` and `profile` now pass `{replace: true}`.
236
- - **`gainReady` exit too early**: the readiness gate was leaving
237
- as soon as any gain from any pilot arrived. Now scoped to
238
- `state.pilot` so a stale pilot's gains cannot short-circuit the
239
- boot pull for the active one.
240
- - **Tack pill "executing" missing target**: the pill fell back to
241
- `null` after the arming phase closed. Now falls back through
242
- `_tackPendingFinalTargetRad ?? _countdownPlannedTargetRad ??
243
- state.target`.
244
- - **TGT stays visible with AP engaged + connection lost**: no
245
- disengage delta arrives when the pypilot vanishes, so the
246
- target was staying pinned. Now the formatter and
247
- `renderTargetArrow` bail on `state.pypilotHealthy === false`;
248
- `scheduleRenderControl()` and `renderTargetArrow()` are also
249
- kicked on every healthy transition.
250
- - **Voice not heard on reconnect** (Rev264): added
251
- `_alSpeak("Pypilot operativo")` to
252
- `_pypilotShowReconnectedToast` so the sailor gets the audible
253
- confirmation and does not have to look at the screen.
254
-
255
- **Architecture**
256
-
257
- - Rejected an active-ping-before-write pattern proposed mid-sprint:
258
- it is a TOCTOU (time-of-check-to-time-of-use) anti-pattern that
259
- adds latency to every write without giving a real safety
260
- guarantee. Kept the passive-reflection approach instead. Design
261
- discussion preserved in the commit log.
262
-
263
- ### Espa&ntilde;ol
264
-
265
- **A&ntilde;adido**
266
-
267
- - **Ciclo UX completo "Pypilot ca&iacute;do"**: la UI refleja el
268
- estado real de la conexi&oacute;n de forma pasiva a partir de un
269
- heartbeat pong. Cuando el socket lleva &gt; 8 s en silencio, el
270
- diamante del target y el chip TGT desaparecen inmediatamente,
271
- los controles que afectan al AP se atenúan con la clase
272
- `.pypilot-offline`, y la regla `pypilot-disconnected` dispara
273
- con voz en palabras completas ("Pypilot desconectado desde hace
274
- N segundos") en lugar de abreviaturas. Al reconectar: toast
275
- verde "Pypilot operativo" + voz + controles activos otra vez.
276
- - **Gestor de outage en 3 tramos**: al reconectar el visor compara
277
- la duraci&oacute;n de la ca&iacute;da contra el &uacute;ltimo
278
- target conocido. &lt; 20 s: no hace nada (probable microcorte).
279
- 20 s – 5 min: banner con S&iacute; / No para restaurar el
280
- target anterior. &gt; 5 min: nada (demasiado tiempo, el
281
- navegante querr&aacute; enganchar de cero).
282
- - **Patr&oacute;n optimistic write-then-throw** con `client.set()`
283
- devolviendo `boolean`. `apEngage / apDisengage / apSetMode /
284
- apSetTargetRad / apTack` rechazan ANTES de tocar el estado
285
- optimista, para que un comando fantasma contra una TinyPilot
286
- ca&iacute;da no deje la UI "enganchada" esperando un delta que
287
- no va a llegar.
288
- - **`_setWithRetry`** (250 ms + 750 ms backoff) alrededor del
289
- autopilot-provider adapter para blips transitorios del socket.
290
- - **Watchdog + heartbeat pong** en `pypilot-client.ts`: el getter
291
- `healthy` combina `socket.connected` con `lastPongMs &lt;= 8 s`.
292
- El motor de alarmas usa `client.healthy` (no `.connected`) para
293
- que la red y las alarmas est&eacute;n de acuerdo sobre la
294
- vitalidad. El orden de alarmas tambi&eacute;n se rehizo para
295
- que `sensor-lost` no salte _antes_ que `pypilot-disconnected`
296
- cuando cae el socket entero.
297
- - **Aislamiento multi-pesta&ntilde;a**: las claves de owner van
298
- ahora en `sessionStorage` (por pesta&ntilde;a) en lugar de
299
- `localStorage`, para que dos visores abiertos no compitan por
300
- soltar los focus watches.
301
- - **Contador con delta con signo** (Rev247) para el HUD del
302
- countdown de virada: acumula el delta angular CON SIGNO para
303
- que el jitter del IMU / compass se cancele en torno a cero y el
304
- contador no acabe en "0&deg; restantes" con el barco todav&iacute;a
305
- a 100&deg; del rumbo final. El threshold de 0.5&deg; que
306
- produc&iacute;a el falso reporte se elimin&oacute; por completo.
307
- - **Auditor&iacute;a i18n completa** en EN / ES / DE / FR: se
308
- purg&oacute; el espa&ntilde;ol en modales que se ven en cualquier
309
- idioma (men&uacute; Barco, Historial de bordos, Detalle de viaje,
310
- Compartir viaje, capa Marca de r&aacute;fagas, selector barra
311
- inferior, aria del nav-toggle), se a&ntilde;adieron variantes
312
- `data-lang`, las strings de la barra inferior pasaron a claves
313
- i18n y se a&ntilde;adi&oacute; `data-i18n-title` en los botones
314
- de virada. Se renombr&oacute; "Gota Chain" (que "no lo entiende
315
- ni Claude", palabras de Carlos) a **"Flechas Target y Viento"**
316
- / "Target &amp; Wind arrows" en todo lo que se ve fuera del
317
- c&oacute;digo (README, modal Info, t&iacute;tulo de screenshot
318
- NPM).
319
- - **Pase de ortograf&iacute;a espa&ntilde;ola**: `"COMPAS"` &rarr;
320
- `"COMP&Aacute;S"` en el label de modo ES, y el bloque
321
- `<code>compas / gps / viento / real</code>` pas&oacute; a la
322
- string literal `<code>compass / gps / wind / true</code>`
323
- (son valores del enum de pypilot, no palabras traducibles).
324
- - **Secci&oacute;n de Instrucciones completas** en el modal Info
325
- (EN + ES) cubriendo todas las pesta&ntilde;as ahora que Chart /
326
- Race / Doctor / Piloto Inteligente / Alarmas / Calidad de
327
- sensores / Grabador de sesiones / Captura de logs / Consola de
328
- debug / Consola de control remoto / Watchdog est&aacute;n todas
329
- en su sitio.
330
- - **Endpoint `/status`** ahora devuelve `healthy: boolean` junto
331
- con `connected`.
332
- - **Focus-watch owner map**: `_focusWatches` pas&oacute; a ser
333
- `Map<key, Map<owner, entry>>` para que un owner colgado de una
334
- pesta&ntilde;a muerta no mantenga vivo un watch.
335
-
336
- **Corregido**
337
-
338
- - **Escrituras fantasma con el socket ca&iacute;do**: `client.set()`
339
- ya no devuelve un &eacute;xito silencioso cuando el socket no
340
- tiene pong vivo. Cada writer de nivel API o bien resuelve tras
341
- el retry o bien lanza `"pypilot offline: <path>"`. Cierra la
342
- clase de reportes "la UI dice enganchado pero el barco va recto".
343
- - **Reconexi&oacute;n lenta tras cortar y encender pypilot**: el
344
- watchdog baja a 15 s de intervalo con umbral de vivacidad de
345
- 20 s, y el provider reconcilia estado en el delta de reconexi&oacute;n
346
- para que el visor recoja el modo / target frescos al instante.
347
- - **Bug de merge en `refreshPypilotValues`**: el cambio de piloto /
348
- perfil quedaba enmascarado por la heur&iacute;stica de "no
349
- sobreescribir claves existentes". Los delta handlers de
350
- `ap.pilot` y `profile` ahora pasan `{replace: true}`.
351
- - **`gainReady` sal&iacute;a antes de tiempo**: la puerta de
352
- readiness se abr&iacute;a al llegar cualquier gain de cualquier
353
- piloto. Ahora est&aacute; limitada al `state.pilot` activo,
354
- para que los gains de un piloto stale no cortocircuiten el
355
- pull de boot del activo.
356
- - **Pill de virada "ejecutando" sin target**: la pill acababa en
357
- `null` al cerrar la fase de arming. Ahora hace fallback por
358
- `_tackPendingFinalTargetRad ?? _countdownPlannedTargetRad ??
359
- state.target`.
360
- - **TGT visible con AP enganchado + conexi&oacute;n ca&iacute;da**:
361
- no llega delta de desenganche cuando pypilot se va, as&iacute;
362
- que el target se quedaba clavado. El formateador y
363
- `renderTargetArrow` ahora respetan `state.pypilotHealthy ===
364
- false`; adem&aacute;s se disparan `scheduleRenderControl()` y
365
- `renderTargetArrow()` en cada cambio de healthy.
366
- - **Voz muda al reconectar** (Rev264): se a&ntilde;adi&oacute;
367
- `_alSpeak("Pypilot operativo")` en
368
- `_pypilotShowReconnectedToast` para que el navegante tenga
369
- confirmaci&oacute;n audible sin mirar la pantalla.
370
-
371
- **Arquitectura**
372
-
373
- - Se rechaz&oacute; un patr&oacute;n de ping-antes-de-escribir
374
- propuesto a mitad de sprint: es un anti-patr&oacute;n TOCTOU
375
- (time-of-check-to-time-of-use) que a&ntilde;ade latencia a cada
376
- escritura sin dar una garant&iacute;a real de seguridad. Se
377
- mantuvo la reflexi&oacute;n pasiva. La discusi&oacute;n queda en
378
- el log de commits para futura referencia.
379
-
380
- ## 2.6.0 — 2026-09-10 — Rev178..Rev180: Trip Report sprint (still experimental)
381
-
382
- Big feature bundle. Every trip you take (from the moment
383
- `navigation.state` leaves "moored" until you're moored again) is
384
- recorded in the background and turned into a shareable report card.
385
-
386
- ### English
387
-
388
- **Added**
389
-
390
- - **Trip recorder** (new module `trip-recorder.ts`): auto-starts when
391
- `signalk-autostate` (or any plugin publishing `navigation.state`)
392
- moves the vessel off "moored". Closes and computes a summary when
393
- the vessel is back to "moored". Trips < 60 s or with < 60 samples
394
- are discarded automatically.
395
- - **Live-accumulated KPIs** so summary computation is O(1) even on
396
- Pi 4: distance (haversine), TWS/AWS avg + max, SOG avg + max, heel
397
- avg + max, tack count, points-of-sail split (upwind / reach /
398
- downwind), battery voltage min + sag count, mode share, engaged
399
- percentage. Highlights (peak wind, peak SOG, min voltage) carry
400
- their timestamps so the map can pin them.
401
- - **"Viajes" card in Setup** (order 27) with live trip status + list
402
- of past trips. Each row: date, distance, duration, avg SOG, avg
403
- TWS, tack count.
404
- - **Trip detail modal** with a 17-KPI grid + emoji-tagged highlight
405
- strip (⛵ ráfaga, 🌀 vel max, 🔋 volt bajo).
406
- - **OpenSeaMap trace on the map**: Leaflet lazy-loaded from CDN on
407
- first modal open, OSM base + OpenSeaMap seamark overlay, polyline
408
- coloured by SOG bucket (< 2 / 2-4 / 4-6 / > 6 kn), markers at
409
- start (green), end (red), peak wind, peak SOG, min voltage, and
410
- each detected tack. Auto-fit to the trace bounding box.
411
- - **"Compartir por WhatsApp" button**: renders a portrait 900×1400
412
- PNG report card (title, 10 KPI cards, mini trace with the same
413
- colour buckets, endpoints and highlight emojis, footer). Uses the
414
- Web Share API when available so the sailor picks any target app;
415
- falls back to a download + prefilled `wa.me?text=` link on
416
- platforms without file share.
417
- - **HTTP endpoints**: `/trip-recorder/status`, `/list`,
418
- `/summary/:id`, `/download/:id`, `DELETE /trip/:id`.
419
-
420
- **Fixed (Rev177 carry-over)**
421
-
422
- - Tack countdown never closes: added AWA-sign / heading-rotation
423
- detection so the maneuver auto-completes in visor even when
424
- pypilot never publishes `ap.tack.state="none"`.
425
- - Gust ghost: fade 15 s (was 10), cooldown 15 s (was 20), thresholds
426
- lowered (jump ≥ 3 kn, ratio ≥ 1.25, shift ≥ 18°). In true-wind
427
- mode the ghost now keys off TWS/TWA and is drawn in the teal T
428
- palette instead of the amber A. Fixes the "aparece poco / no
429
- atiende a rafagas / en modo real deberia mirar real" feedback.
430
- - `gustStrategy` default flipped from "warn" to "off" so the
431
- supervisor cannot freeze the target / boost D / temp-heavy the
432
- pilot without the sailor explicitly opting in. On the last sea
433
- trial one of these strategies latched and the AP "se quedo
434
- pillado, yendose orzada sin buscar rumbo".
435
- - Reload race: after the plugin catalog lands we re-run
436
- `refreshPypilotValues()` so gain / calibration sliders that
437
- rendered before the catalog was known reposition themselves at
438
- the real pypilot values. Fixes "al recargar la pagina muchas
439
- veces no carga los valores de sliders o de modo".
440
-
441
- **Known limits (still experimental)**
442
-
443
- - Trasluchar (jibe): pypilot's tack primitive only crosses the wind
444
- by proa. A jibe needs a different primitive (aproado-style). Not
445
- implemented yet.
446
- - Trip report card renders as a static PNG. The interactive map is
447
- only visible inside the visor.
448
- - CDN load of Leaflet needs internet. On a boat with no data link
449
- the trace panel just says "sin conexion a los tiles del mapa" and
450
- the modal still shows all the KPIs.
451
-
452
- ## 2.5.2 — 2026-09-10 — Rev176: session-recorder containment
453
-
454
- Follow-up patch on 2.5.0. After analysing the on-water sessions
455
- Carlos captured with Rev175 we found a pile of 1-sample JSONL
456
- files caused by an external HTTP client (likely a KIP dashboard
457
- tab, an idle visor tab, or a competing pypilot_web instance)
458
- cycling `POST /engage` -> `POST /disengage` every 5-6 s. Every
459
- flicker opened and closed a session file. This tranche contains
460
- the damage and fixes several analysis blind spots.
461
-
462
- ### English
463
-
464
- **Fixed**
465
-
466
- - **Engaged debounce (8 s)** in the historian tick. A new
467
- engage/disengage transition is only propagated to the session
468
- recorder after the raw value has held for 8 s. This filters
469
- the rebounds that were opening one JSONL every ~5 s during the
470
- test window on 2026-09-08.
471
- - **Bounce diagnostics**: filtered flips are counted and exposed
472
- through `/session-recorder/status` (`bouncesFiltered`,
473
- `lastBounceMs`, `lastBounceDetails`), plus a `[bounce]` line
474
- in `app.debug` so the next culprit can be traced without
475
- waiting for a repeat.
476
- - **Discard short sessions on close**: if a session has less
477
- than 30 samples OR less than 30 s of duration when the AP
478
- disengages, the JSONL is unlinked instead of being flushed
479
- and kept. Belt-and-suspenders on top of the debounce.
480
- - **Retroactive purge**: on plugin start the recorder sweeps
481
- `nav-sessions/` and deletes every closed file that meets the
482
- same criterion. The tester's 54-file directory came down to 7
483
- useful sessions after Rev176 booted.
484
- - **Manual purge endpoint**: `POST /plugins/signalk-pypilot-newui/session-recorder/purge-short`
485
- triggers the sweep on demand.
486
- - **hdgErr per AP mode**: the recorder stored
487
- `headingCmd - headingActual` regardless of mode, which was
488
- garbage in `wind` / `true wind` where `headingCmd` is a wind
489
- angle, not a heading. Now compass-family modes still store the
490
- heading delta, `wind` mode stores `headingCmd - AWA`, and
491
- `true wind` mode stores `headingCmd - TWA`. Offline analysers
492
- finally get a real number for wind modes.
493
- - **Pi Zero log-capture no longer re-injects the tail on
494
- restart**: the per-source `lastLineHash` map is now persisted
495
- to `pizero-log-state.json` and reloaded on plugin start. The
496
- file used to grow 32x on the tester's setup because every
497
- `-Restart` deploy reset the in-memory cursor and re-appended
498
- the entire tail.
499
-
500
- **Changed**
501
-
502
- - **Gust ghost arrow recoloured to orange** so it stands out
503
- from the live amber A arrow when the two land near the same
504
- angle. The overlay-card preview matches.
505
- - **Gust ghost detection thresholds lowered** (Rev175 carry-
506
- over): AWS jump 4 kn / ratio 1.35, AWA shift 20 deg, baseline
507
- min 2 kn, cooldown 20 s. Tuned against 2 h of real navigation
508
- data (~18 potential fires for a 14 kn-avg sail).
509
-
510
- **Investigation notes (for the curious)**
511
-
512
- The 71-second racimo on 2026-09-08 19:01-02 (Rev166 that day)
513
- shows the pattern:
514
-
515
- engage -> PUT target -> PUT mode -> disengage -> engage -> ...
516
-
517
- repeating every 5-6 s at the SK Autopilot API v2 endpoint. Not
518
- manual: too regular. Culprit unidentified with the logs at
519
- hand - the SK journal does not record client IP/UA. Suspects
520
- still alive:
521
-
522
- - A custom KIP dashboard widget that toggles engage on a tick.
523
- - Two open visor tabs where one has a stale state machine and
524
- re-issues engage on each SSE reconnect.
525
- - A feedback loop through `signalk-pypilot` mirroring an incoming
526
- bus delta as an outgoing SK write.
527
-
528
- pypilot_web on the KIP tablet was cleared as a suspect - the
529
- captured pypilot log shows its socket.io connections spaced
530
- minutes apart, not seconds.
3
+ ## 2.7.2 — 2026-09-14 — Registry score bump
4
+
5
+ No code change vs 2.7.1. Adds a smoke test suite (Node built-in test
6
+ runner, no extra devDependencies) so `npm test` passes for the Signal K
7
+ Plugin Registry, and clears a `high` `npm audit` finding in a
8
+ transitive dependency. Together these lift the plugin's registry
9
+ score by ~45 points.
10
+
11
+ ## 2.7.1 — 2026-09-14
12
+
13
+ Sea-trial release. Big reliability upgrade after intensive on-water
14
+ testing on Tunatunes.
15
+
16
+ ### New features
17
+
18
+ - **Trip Recorder** — every outing is auto-recorded via
19
+ `navigation.state`. Setup → Trips shows 17 KPIs, an OpenSeaMap
20
+ trace coloured by SOG with markers on peak gust, peak SOG, min
21
+ voltage and every detected tack, and lets you share the outing as
22
+ a portrait PNG report card or as WhatsApp text.
23
+ - **Reconnect Restore banner** — when the link with pypilot dies
24
+ while the AP is engaged, the visor takes a snapshot of your target
25
+ and mode. If the link is back between 20 s and 5 min later, a
26
+ Yes / No banner offers to re-engage the same target. Success is
27
+ announced only after pypilot actually confirms.
28
+ - **Watchdog with core-alive detection** — the visor now
29
+ distinguishes a live `pypilot_web` with a dead pypilot core. AP
30
+ controls grey out, the "pypilot disconnected for N seconds" voice
31
+ alert fires, and the green "pypilot operational" toast greets you
32
+ when the core is really back.
33
+ - **Faster offline / online detection** — dead-pypilot detection
34
+ improved from ~10-14 s to ~5-6 s; reconnect detection improved
35
+ from ~4-8 s to ~1-3 s.
36
+ - **Persistent Tack history** — every tack stored on the device
37
+ with pre / post TWS, AWA, SOG and recovery time to 95% of pre-tack
38
+ speed. Filter by day range with best retention and fastest
39
+ recovery highlighted.
40
+ - **Gust marker overlay** — leaves a ghost `A` arrow at the peak
41
+ wind speed for 10 s.
42
+ - **Bottom bar rudder-or-heel switch** — the big-number bar on the
43
+ Control tab can show rudder angle or boat heel.
44
+ - **Concentric Target + Wind arrows** — three arrow pieces on the
45
+ rose interlock into a droplet when target ≡ AWA ≡ TWA; open into
46
+ V-notches with a colour tick when they drift. (Feature previously
47
+ called "Gota Chain", renamed for clarity.)
48
+ - **Full instructions inside the Info modal** — new section that
49
+ walks through every tab and gesture, in English and Spanish.
50
+ - **Global `allowWrites` gate** — the plugin's HTTP routes, the
51
+ Autopilot API v2 provider and the KIP action / switch handlers
52
+ all honour the same toggle. A read-only install is really
53
+ read-only from every angle.
54
+
55
+ ### Critical bug fixes
56
+
57
+ - **Silent success on offline writes** — writes to a dead pypilot
58
+ socket used to silently report success, so the visor could claim
59
+ the AP was engaged while the boat drifted. Writes now confirm
60
+ end-to-end before the UI accepts them.
61
+ - **Phantom AP re-enable after reconnect** — a stale engage retry
62
+ could re-enable the AP after you had explicitly disengaged. Fixed
63
+ with a generation counter that cancels superseded orders.
64
+ - **False "healthy" with a dead pypilot core** — a live
65
+ `pypilot_web` pong used to keep the "healthy" flag on even when
66
+ pypilot itself was dead. A sticky core-offline flag now blocks
67
+ writes and greys the UI until real evidence from the core arrives.
68
+ - **Restore banner announced "restored" before pypilot confirmed**
69
+ — the visor used to speak success on the HTTP 200 alone, seconds
70
+ before spotting that pypilot never actually engaged.
71
+ - **Spanish leaking into English mode** — several dynamic strings
72
+ (Trip card, Tack log, menus, WhatsApp caption) rendered in Spanish
73
+ regardless of the active language.
74
+
75
+ Many small bugs also fixed across the visor, autopilot provider and
76
+ Signal K integration.
77
+
78
+ ## 2.7.0 — 2026-09-14 — Superseded by 2.7.1
79
+
80
+ Same feature set and code as 2.7.1. Release notes were rewritten in
81
+ 2.7.1 in user-facing language.
82
+
83
+ ## 2.6.0 — 2026-09-10 — Trip Report first cut
84
+
85
+ First cut of the Trip Recorder (auto-record via `navigation.state`,
86
+ 17-KPI grid, OpenSeaMap trace, PNG report card, WhatsApp share).
87
+ Gust marker and gust strategy tuning. Reload-race fix so gain and
88
+ calibration sliders always reflect real pypilot values.
89
+
90
+ ## 2.5.2 — 2026-09-10 — Session-recorder containment
91
+
92
+ Contains a rebound bug that opened one JSONL file per engage
93
+ flicker: 8 s engaged-debounce, short-session discard on close, plus
94
+ bounce diagnostics exposed on `/session-recorder/status`.
531
95
 
532
96
  ## 2.5.1 — 2026-09-09 — README bilingual
533
97
 
534
- Docs-only patch. The EXPERIMENTAL banner in README and CHANGELOG
535
- now appears in English *and* Spanish so the SignalK App Store
536
- description communicates the sea-trial-pending status to English
537
- speakers too. No code changes.
538
-
539
- ## 2.5.0 — 2026-09-09 — Rev154..Rev174
540
-
541
- > ## ⚠️ Ships with features that have not been tested at sea yet
542
- >
543
- > This release brings NEW features that we **could not validate under
544
- > sail** yet (there was not enough wind the day we cut it). Install
545
- > it if you fancy helping us test them — and if anything looks off,
546
- > **tell us**: https://github.com/Aitonos/signalk-pypilot-newui/issues
547
- >
548
- > Your feedback is GOLD for tuning the thresholds before we sign
549
- > these features off as stable for everyone.
550
- >
551
- > ## ⚠️ Version con funciones aun sin probar en el mar
552
- >
553
- > Esta version trae funciones NUEVAS que **no hemos podido validar
554
- > navegando** todavia (el dia de sacarla no habia viento).
555
- > Instalala si te apetece ayudarnos a probarlas — y si algo va raro,
556
- > **avisa**: https://github.com/Aitonos/signalk-pypilot-newui/issues
557
- >
558
- > Tu feedback nos vale ORO para tunear los limites antes de dejar
559
- > estas funciones a punto para todos.
560
- >
561
- > ### ⛵ Que tenemos que probar en el mar (checklist rapida)
562
- >
563
- > 1. **Contador de virada** (grande sobre el barco): al pulsar TACK
564
- > con el AP enganchado, cuenta atras rojo (5-4-3-2-1) y luego pasa
565
- > a naranja "VIRANDO" mientras el pypilot vira. Tocar el circulo
566
- > cancela.
567
- > 2. **Resumen despues de virar** (panel que sale 2 min): compara la
568
- > velocidad y el angulo antes y despues del bordo. Verde = buena.
569
- > Amarillo = normal. Rojo = perdida.
570
- > 3. **Historial de bordos** (deja pulsado el barco -> "Historial"):
571
- > guarda cada bordo con retencion de velocidad y tiempo de
572
- > recuperacion. Filtro por dias, resalta el mejor.
573
- > 4. **Marca de rafagas** (activable en "Cambiar esquinas" ->
574
- > Overlays): cuando entra una rafaga fuerte, deja una flecha
575
- > amarilla fantasma con la velocidad del viento durante 10 segundos.
576
- > 5. **Cerebro auto** (opcional, en la ficha Smart Pilot): si lo
577
- > activas, cambia el perfil del pypilot segun el viento (suave /
578
- > normal / duro) y reacciona a rafagas segun la estrategia elegida
579
- > (avisar / congelar rumbo / subir D / cambiar a perfil duro).
580
- > 6. **Corta automatica** (opcional): si el timon toca los topes
581
- > demasiado tiempo, suelta el AP para que retomes tu.
582
- >
583
- > **Como nos ayudas**: usalas, y si algo se comporta raro o no te
584
- > gusta, mandanos: que hiciste, que esperabas, que paso. Con eso
585
- > tunearemos los limites antes de sacar la 2.5.0 estable.
586
-
587
- ### English (Rev154..Rev174, pending sea-trial confirmation)
588
-
589
- **Added — supervisor layer ("brain over pypilot")**
590
-
591
- - **Auto-profile by wind** — the plugin can switch the active pypilot
592
- profile between light / medium / heavy based on rolling TWS. Bands
593
- configurable in the visor's Smart Pilot card. Disabled by default.
594
- Rev164.
595
- - **Gust strategy** — configurable response when a remarkable AWS jump
596
- is detected: `off`, `warn`, `freeze-target`, `boost-D`, `temp-heavy`.
597
- Replaces the Rev165 warn-only detector. Restore-first logic returns
598
- the pilot to the prior state when the gust window closes. Rev167.
599
- - **Auto-disengage on lost authority** — if the servo saturates against
600
- its rudder stops for a sustained window, the AP releases so a manned
601
- helm regains control. Rev166.
602
- - **Config surface moved to the visor** — the Smart Pilot card in the
603
- plugin's own UI now owns every supervisor toggle. Nothing to touch in
604
- SK Admin. Rev167.
605
-
606
- **Added — rose UI**
607
-
608
- - **Tack countdown overlay over the boat sprite** — huge red digit
609
- during the pre-tack delay, then flips to orange `VIRANDO` + elapsed
610
- seconds during execution. Click cancels the maneuver. Fresh delay is
611
- fetched every arm so an edit in Calibration takes effect without a
612
- page reload. Rev171/172.
613
- - **Post-tack stats HUD** — for 2 minutes after a completed tack the
614
- boat sprite glows and a small panel shows TWS / |AWA| / SOG averaged
615
- 30 s before vs. 30 s after (measured t+30..t+60 so the boat has time
616
- to settle). Verdict is colour-coded by SOG retention. Rev171.
617
- - **Persistent tack log (long-press on the boat)** — every completed
618
- tack is stored locally with pre/post averages, SOG retention %, and
619
- time to recover 95 % of pre-SOG. The action sheet on the boat sprite
620
- now offers "Cambiar esquinas" (the old corner selector) or
621
- "Historial de bordos". The log modal filters by day range and
622
- highlights the best tack by retention and the fastest to recover.
623
- Rev172.
624
- - **Gust ghost arrow (opt-in in the corner-selector modal)** — a
625
- remarkable AWS jump (≥ 6 kn over the 30-s baseline or ≥ 1.55×) or a
626
- sharp AWA rotation (≥ 30° in 5 s) leaves a translucent A-shaped
627
- arrow frozen at the gust angle, with the AWS reading printed
628
- radially outside it, fading to zero over 10 s. Cooldown 30 s and
629
- single-ghost policy so it never chases the live arrow. Card lives
630
- alongside the A / T / COG / Current overlay cards. Rev172-174.
631
- - **Mode selector reads full phrases** — `APARENTE` / `REAL` shown on
632
- the collapsed button, `VIENTO APARENTE` / `VIENTO REAL` on the open
633
- dropdown (swapped on pointerdown / blur). Rev172.
634
- - **T over A on overlap** — when both wind pieces cover the same angle
635
- the teal T now draws on top of the amber A, so TWA stays readable at
636
- a glance. Rev172.
637
- - **Rose corner sub-line resized (Carlos)** — the wind speed under the
638
- angle in the top corners is now a first-class figure in portrait and
639
- a compact one in landscape (media-query split). Bottom AWS/TWS chip
640
- also trimmed so it stops covering half the boat on a portrait phone.
641
- Rev171-173.
642
- - **Tack countdown click-to-cancel** — tapping the huge red circle at
643
- any moment (delay or VIRANDO) sends `ap.tack.state=none`. Guard added
644
- so TACK does nothing if the AP is not engaged. Rev171.
645
-
646
- **Changed — internal**
647
-
648
- - Shared `SERVO_ON_MIN_A` constant across doctor / kpis / servo-health.
649
- Rev154.
650
- - Alarm rules extended with `servo-motor-temp`, `servo-load-high`
651
- (deviation-ratio > 1.65), and `cruise-drift`. Rev155.
652
- - `doctor.applyAll()` applies only the top-priority suggestion and
653
- returns `mustRunFreshSession` + `remaining` so the caller can drive a
654
- next-iteration loop. Rev160.
655
- - Session recorder JSONL now enforces a 200 MB FIFO cap. Rev158.
656
- - Servo-health snapshot persists to disk between restarts. Rev158.
657
- - Hardware-ceiling finding in `doctor` (duty>0.75 & rms>5°) short-
658
- circuits further advice with a "you have out-tuned the actuator"
659
- message. Rev163.
660
-
661
- **Removed**
662
-
663
- - All "helm is manned" confirmation modals on engage / tack / Doctor
664
- apply. Confirming every action was noise; the physical presence of
665
- the helm is a given while at sea. Rev170.
666
-
667
- ## 2.4.1 — 2026-09-08 — Rev146..Rev153
668
-
669
- Patch release. Fixes + UI polish on top of 2.4.0.
670
-
671
- ### English
672
-
673
- **Fixed**
674
-
675
- - **TWS row in Sensor Quality no longer stuck as MISSING** on plain SK
676
- setups. `environment.wind.speedTrue` (canonical) is rarely populated;
677
- the canonical row now accepts `.speedOverGround` (what
678
- `signalk-derived-data` emits) or `.speedOverWater` as alternatives.
679
- Same treatment for TWA (`.angleTrueWater` / `.angleTrueGround` /
680
- `.angleTrue`). Rev146-148.
681
- - **Regression: AP mode selector went blank** after the Rev140 watch
682
- refactor - `RESERVED_PYPILOT_KEYS` was blocking subscribe too, when
683
- it should only block re-publish. Restored so the visor keeps
684
- `ap.enabled/mode/heading_command` in sync. Rev142.
685
- - **Watch focus was pinning 60+ keys** on `pypilot_web` when the Setup
686
- launcher was open. Rev149-150: the launcher grid takes NO focus;
687
- only the Calibration card (fullscreen) bumps the 7 documented
688
- sliders. Empty focus now triggers explicit release, so the count
689
- drops back to the ~15 core watches immediately instead of waiting
690
- for the 60 s TTL. Well under Sean D'Epagnier's 40-path ceiling.
691
- - **`[current]` prefix in Pi Zero log capture** replaced with the
692
- parent directory (`[pypilot]`, `[pypilot_web]`, `[pypilot_hat]`) so
693
- the merged file is actually readable. Rev149.
694
-
695
- **Changed**
696
-
697
- - **Rose colour palette (Carlos)**: Target/DIA diamond is now red
698
- (`--target-red`, matches the AP engage button); AWS/TWS chip below
699
- the rose is fully tinted amber for apparent, teal for true; corner
700
- tiles tint value + sub in the matching wind colour; SOG back to
701
- white; Rev151-152.
702
- - **Configure-corners modal** — h4 + close button now sticky at the
703
- top, body scrolls independently. X on the LEFT with the solid red
704
- `--engaged` style used by the Setup fullscreen cards. Rev151-153.
705
- - **Mode selector labels re-render on language change**: switching
706
- language now redraws mode / pilot / profile selects immediately
707
- instead of waiting for the next backend delta. Rev144.
708
-
709
- ## 2.4.0 — 2026-09-07 — Rev138..Rev145
710
-
711
- Feature release. The plugin has learnt to behave itself around the
712
- Pi Zero (following direct advice from Sean D'Epagnier, pypilot author)
713
- and now speaks the sailor's language instead of pilot-programmer
714
- jargon.
715
-
716
- ### English
717
-
718
- **Added**
719
-
720
- - **Dynamic watch focus (Rev140..Rev141)** — the old policy kept ~170
721
- pypilot subscriptions pinned to `pypilot_web`, which pressured the
722
- Pi Zero W and eventually locked its socket buffer. The backend now
723
- keeps a minimal always-on core (~15 paths, 1-2 Hz) and accepts
724
- short-lived focus requests from the visor: Tune bumps the gain
725
- paths only while it is on screen, same for the Calibration
726
- sliders. TTL expires when the tab closes. New endpoints
727
- `/watch/focus`, `/watch/release`, `/watch/status`, a live inspector
728
- in Setup → Remote Control Console, and a `Restart pypilot_web`
729
- button that runs `sv restart pypilot_web` via SSH.
730
- - **Navigation session recorder + advice loop (Rev143)** — every
731
- engaged AP session is now saved as JSONL to
732
- `~/.signalk/plugin-config-data/signalk-pypilot-newui/nav-sessions/`.
733
- Users label conditions (wind / sea / motor / point of sail / crew)
734
- from a modal in Setup → Doctor, share the file via WhatsApp or
735
- email, receive an "advice" JSON back and upload it - the visor
736
- renders it as a "Consejos recibidos" card. First step of the
737
- AI-tuned Doctor roadmap: no AI runs at runtime, learning happens
738
- off-boat and gets injected as plain heuristics in later Revs.
739
- - **Pi Zero log capture (Rev138)** — piCore keeps `/var/log` in
740
- tmpfs, so a hard reset wipes the previous session's logs. New
741
- opt-in poller SSHes into the Pi Zero on a configurable cadence,
742
- tails `/var/log/pypilot/*` and appends new lines to a persistent
743
- daily file on the Pi 5. Toggle + downloader live in the Remote
744
- Control Console.
745
- - **Plain-language help for every gain (Rev139)** — every P / I / D
746
- / DD / PR / FF slider in Tune and every documented RangeSetting in
747
- Setup → Calibration now carries a short "what it does" line plus a
748
- `?` button that expands into "when to raise, when to lower". EN /
749
- ES / DE / FR. Aligned with Sean's own forum suggestion.
750
- - **Doctor findings and summary translated (Rev136)** — backend now
751
- emits i18n keys with args so the whole Doctor panel reads in the
752
- active language instead of half English / half Spanish.
753
- - **Alarm engine i18n (Rev135..Rev136)** — the seven built-in rules
754
- (heading-deviation, unable-to-steer, servo-overcurrent,
755
- servo-temp-high, low-voltage, sensor-lost, pypilot-disconnected)
756
- render banners and speak in the active language. The backend still
757
- ships English strings on `notifications.autopilot.*` for
758
- KIP / WilhelmSK compatibility.
759
- - **Mode selector proper i18n + uppercase tabs (Rev142, Rev144)** —
760
- `compass`, `wind`, `true wind`, `nav` and the plugin's own
761
- `aproado` render as translated UPPERCASE labels ("COMPAS", "VIENTO
762
- APARENTE", "APROADO"). Tab labels are UPPERCASE too so accents
763
- become optional (RAE convention). Language switch now refreshes
764
- the mode selector and gain help lines immediately.
765
- - **Doctor - persistent Hide button (Rev135..Rev136)** — the discard
766
- action stays visible even after a suggestion has been applied,
767
- the tile chip mirrors the count.
768
- - **Setup launcher - live status chips (Rev130..Rev135)** — pypilot
769
- host+port, active language, calibration last-adjust timestamp
770
- (with a Reset button), count of active SK sentences vs the plugin
771
- catalogue total, and an SSH-configured pill on Remote Control
772
- Console. Sensor Quality gains a per-row Ignore / Restore button so
773
- a sensor absent from the boat (rudder feedback missing, ...)
774
- stops turning the chip red.
775
- - **Chart hover freeze + per-band Y axis (Rev129)** — touch or hover
776
- to freeze the auto-refresh and read the exact value at that point
777
- for every band. Min / mid / max ticks in the right gutter.
778
- - **Tune - per-slider ↺ restore + profile-fork prompt (Rev129,
779
- Rev132..Rev134)** — snap back to the frozen "was:" baseline, or
780
- save the change in a new `tune-YYYYMMDD-HHMM` fork on first
781
- unlock, or Cancel and revert.
782
- - **Nav bar slide-down gesture (Rev129)** — swipe from the top to
783
- bring the tab bar back, counterpart to the existing slide-up hide.
784
-
785
- **Changed**
786
-
787
- - The `RESERVED_PYPILOT_KEYS` set now only blocks publish, not
788
- subscribe. Rev141 regression fix: `ap.mode`, `ap.enabled`,
789
- `ap.heading_command` are needed internally to sync with the SK
790
- Autopilot API v2.
791
- - All six screenshots refreshed to match the current UI.
792
-
793
- ### Español
794
-
795
- **Añadido**
796
-
797
- - **Watch focus dinámico** — el plugin mantiene un núcleo mínimo
798
- suscrito (~15 keys, 1-2 Hz) y sube el rate solo mientras haya una
799
- pestaña que necesite ver esos valores. Sigue la recomendación de
800
- Sean D'Epagnier: pypilot_web se satura si le mandas ~170
801
- suscripciones constantes. Nuevos endpoints, inspector en vivo y
802
- botón "Reiniciar pypilot_web" en la consola remota.
803
- - **Grabador de sesiones + loop de consejos** — cada sesión con el
804
- AP enganchado se guarda como JSONL. Etiquetas por condiciones
805
- (viento / mar / propulsión / rumbo / tripulación), botón compartir
806
- por WhatsApp, y subida del JSON de consejos que se recibe de
807
- vuelta. Primer paso del "Doctor afinado por IA": la IA no corre
808
- en tiempo real, aprende offline y sus heurísticas se inyectan
809
- como código en las próximas Revs.
810
- - **Captura de logs Pi Zero** — piCore guarda `/var/log` solo en
811
- RAM, un hard reset borra el historial. Toggle en la consola
812
- remota que hace SSH cada N segundos y guarda los logs en un
813
- archivo persistente del Pi 5.
814
- - **Explicaciones "en cristiano" para cada ganancia** — P / I / D /
815
- DD / PR / FF más los sliders principales de Calibration llevan
816
- una línea corta con qué hace y un `?` que expande "cuándo subir,
817
- cuándo bajar". EN / ES / DE / FR.
818
- - **Doctor + alarmas en tu idioma** — findings, summary y las siete
819
- reglas de alarma ahora se traducen en el visor. El backend sigue
820
- enviando inglés al SK para KIP / WilhelmSK.
821
- - **Selector de modo traducido en MAYÚSCULAS** — "COMPAS", "VIENTO
822
- APARENTE", "VIENTO REAL", "APROADO", ... con jerga marinera. Las
823
- pestañas también en MAYÚSCULAS para que la tilde sea opcional.
824
- - **Doctor - botón Ocultar siempre visible** — no se atasca la
825
- lista de sugerencias aunque ya las hayas aplicado.
826
- - **Setup - chips vivos en cada tile** — host + puerto TinyPilot,
827
- idioma activo, fecha del último ajuste de calibración, sentencias
828
- SK activas y SSH configurado.
829
- - **Sensor Quality con Ignorar / Restaurar por fila** — para
830
- sensores que no existen en el barco (por ejemplo el timón sin
831
- feedback).
832
- - **Chart con freeze al pasar el cursor + escala Y por banda**.
833
- - **Tune con ↺ por slider + fork de perfil al primer cambio**.
834
- - **Slide desde arriba para mostrar la barra de pestañas**.
835
-
836
- **Cambiado**
837
-
838
- - Regresión Rev141 corregida: `RESERVED_PYPILOT_KEYS` bloquea
839
- solo publish, no subscribe. El selector de modo del AP vuelve
840
- a poblarse correctamente.
841
- - 6 screenshots actualizados a la UI actual.
842
-
843
- ### Deutsch
844
-
845
- **Neu**
846
-
847
- - Dynamischer Watch-Fokus, entlastet pypilot_web auf dem Pi Zero.
848
- - Sitzungs-Rekorder + Ratschlag-Loop (JSONL pro Engage).
849
- - Pi Zero Log-Capture (persistiert /var/log auf dem Pi 5).
850
- - Klartext-Hilfe pro Gain-Slider.
851
- - Doctor + Alarme in aktiver Sprache.
852
- - Modus-Auswahl uebersetzt in GROSSBUCHSTABEN.
853
- - Doctor: Verwerfen bleibt sichtbar.
854
- - Setup-Kacheln zeigen Live-Status.
855
- - Sensor Quality: Ignorieren pro Zeile.
856
- - Chart: Hover-Freeze + Y-Achse pro Band.
857
- - Tune: ↺ pro Slider + Fork-Dialog.
858
- - Wisch-von-oben blendet Tab-Leiste ein.
859
-
860
- ### Français
861
-
862
- **Nouveautés**
863
-
864
- - Focus d'abonnements dynamique, allege pypilot_web sur le Pi Zero.
865
- - Enregistreur de sessions + boucle de conseils (JSONL par engage).
866
- - Capture des logs Pi Zero sur le Pi 5.
867
- - Aide en clair par slider de gain.
868
- - Doctor et alarmes dans la langue active.
869
- - Selecteur de mode traduit en MAJUSCULES.
870
- - Doctor: bouton Masquer toujours visible.
871
- - Puces de statut vivantes sur chaque tuile Setup.
872
- - Sensor Quality avec Ignorer / Restaurer par ligne.
873
- - Chart: figeage au survol + graduation Y par bande.
874
- - Tune: ↺ par slider + modal de fork de profil.
875
- - Glissement du haut vers le bas pour reafficher la barre d'onglets.
876
-
877
- ## 2.3.0 — 2026-08-31 — Rev130..Rev137
878
-
879
- Feature batch on top of 2.2.x. Themes: make the visor speak the user's
880
- language end-to-end (Doctor findings, alarm banners and TTS voice
881
- included), turn the Setup launcher into an at-a-glance dashboard,
882
- give Tune the same profile-fork discipline the Doctor already had,
883
- and make Sensor Quality something the skipper can silence for
884
- sensors that aren't on this boat.
885
-
886
- ### English
887
-
888
- **Added**
889
-
890
- - **Chart tab — interactive hover** (Rev129) — freeze the auto-refresh
891
- and read the exact value of every band at any point in the history
892
- window by hovering the graph. Y-axis ticks (min / mid / max) now
893
- sit inside the right gutter for each band.
894
- - **Tune tab — per-slider restore + profile fork prompt** (Rev129,
895
- Rev132..Rev134) — every gain slider gets a ↺ button that snaps
896
- back to the frozen baseline captured when you unlocked the panel.
897
- On the first change of an unlock session a modal asks whether to
898
- keep the tweaks in the current profile, save them in a new
899
- `tune-YYYYMMDD-HHMM` fork (mirroring the Doctor pattern), or
900
- Cancel and revert the change entirely.
901
- - **Setup launcher — live status chips on every tile** (Rev130..Rev135)
902
- — Pypilot Connection shows `host:port`, Language shows the active
903
- locale, Calibration shows the timestamp of the last slider
904
- adjustment (with a `Reset historial` button on the card), Signal K
905
- Sentences shows the count of published SK paths out of the plugin
906
- catalog total, and Remote Control Console reports whether SSH
907
- creds are stored.
908
- - **Sensor Quality — per-row Ignore / Restore** (Rev134..Rev135) —
909
- the chip now also counts "missing" sensors (a watched path that
910
- has never emitted a delta) into the failure tally, and each row
911
- gains an Ignore / Restore button so a sensor you don't have on
912
- this boat (rudder without a rudder feedback, temp probe absent,
913
- ...) stops turning the chip red. Persisted per-browser.
914
- - **Nav bar slide-down gesture** (Rev129) — the counterpart to the
915
- existing slide-up hide; drag from the top of the visor to bring
916
- the tab bar back.
917
- - **Doctor — Hide button always visible + i18n findings**
918
- (Rev135..Rev136) — the Descartar / Ocultar button stays visible
919
- even after a suggestion has been applied so you can clear stale
920
- advice from the list. Hidden suggestions also drop off the tile
921
- chip's counter so the card goes back to `sano` once you've cleared
922
- everything. Backend now emits i18n keys + args for findings and
923
- summary so the whole Doctor panel reads in the active language.
924
- - **Alarm engine i18n** (Rev135..Rev136) — the seven built-in alarm
925
- rules (heading-deviation, unable-to-steer, servo-overcurrent,
926
- servo-temp-high, low-voltage, sensor-lost, pypilot-disconnected)
927
- now render banners and speak the message in the active language
928
- (EN / ES / DE / FR). The backend still publishes the English
929
- notification message on `notifications.autopilot.*` for
930
- KIP / WilhelmSK compatibility.
931
- - **Info tab — English classic-UI link fix** (Rev129) — the
932
- hardcoded Spanish string "Interfaz clasica pypilot" that leaked
933
- into every language moved to a proper i18n key with `{host}` /
934
- `{port}` interpolation.
935
- - **Remote Control Console — TinyPilot defaults** (Rev136) — the
936
- tile no longer wears its emergency red frame; the SSH user field
937
- is pre-filled with the TinyPilot default (`tc`) and a hint spells
938
- out that the default password is `tc` too, so the setup is a
939
- two-click job on a stock TinyPilot.
940
- - **Better DE / FR coverage** (Rev134..Rev136) — Setup tile titles,
941
- calibration strings, Tune fork modal, chip labels, Doctor summary
942
- / findings and alarm messages now translate in Deutsch and
943
- Français instead of falling back to English.
944
-
945
- **Changed**
946
-
947
- - **All six screenshots refreshed** to match the current UI
948
- (Control rose with the Gota Chain arrow layout, Chart Trip Stats,
949
- Tune with per-slider reset, Setup launcher chips, Race timer,
950
- Info panel).
951
-
952
- ### Español
953
-
954
- **Añadido**
955
-
956
- - **Chart — cursor sobre gráficas** — al pasar el dedo o el ratón
957
- sobre una gráfica se congela el refresh y aparece una línea
958
- vertical + tooltip con `-X.Xs` y el valor de cada banda en ese
959
- punto. Escala Y (min / mid / max) por banda en el margen derecho.
960
- - **Tune — botón ↺ por slider + prompt de perfil** — cada gain trae
961
- un ↺ que vuelve al valor previo (el que había al desbloquear). El
962
- primer cambio tras unlock lanza un modal preguntando si guardar
963
- en el perfil actual, crear un nuevo `tune-YYYYMMDD-HHMM` (mismo
964
- patrón que Doctor) o cancelar y revertir.
965
- - **Setup — chips vivos en cada tile** — TinyPilot con host:port,
966
- idioma actual, fecha del último ajuste de calibración (con botón
967
- Reset historial), contador de sentencias SK publicadas frente al
968
- total del catálogo del plugin, y aviso de SSH configurado en
969
- Remote Control Console.
970
- - **Sensor Quality — Ignorar / Restaurar por fila** — los sensores
971
- que nunca han reportado también cuentan en el ✕, y cada fila con
972
- problema tiene ahora un botón Ignorar (persistente en el
973
- navegador) para no volver a teñir el chip por un sensor que no
974
- hay en el barco.
975
- - **Slide desde arriba para mostrar la barra de pestañas** — vuelta
976
- atrás del slide hacia arriba que ya escondía la nav.
977
- - **Doctor — botón Ocultar siempre + findings traducidos** — se
978
- puede vaciar la lista de sugerencias aunque ya las hubieras
979
- aplicado, y el chip del tile vuelve a "sano" cuando no queda
980
- ninguna. Backend emite ahora claves i18n para findings y summary.
981
- - **Alarmas en tu idioma** — las siete reglas (desvío de rumbo,
982
- autoridad, servo, temperatura, tensión, sensor perdido,
983
- desconexión pypilot) se muestran y se hablan en el idioma activo.
984
- Al SK sigue viajando el mensaje en inglés para compatibilidad con
985
- KIP / WilhelmSK.
986
- - **Info EN sin fugas al ES** — el "Interfaz clásica pypilot" que
987
- se colaba en cualquier idioma pasó a clave i18n.
988
- - **Remote Console con defaults TinyPilot** — sin borde rojo,
989
- usuario `tc` pre-cargado y aviso de que la contraseña por defecto
990
- también es `tc`.
991
- - **Mejor cobertura DE / FR** en todos los strings visibles del
992
- Setup, Tune, Doctor y alarmas.
993
-
994
- **Cambiado**
995
-
996
- - **6 screenshots** actualizados a la UI actual.
997
-
998
- ### Deutsch
999
-
1000
- **Neu**
1001
-
1002
- - Chart mit Hover-Freeze und Y-Achse pro Band.
1003
- - Tune-Slider mit ↺ und Profil-Fork-Dialog (Cancel setzt zurueck).
1004
- - Setup-Kacheln zeigen Live-Status (Host, Sprache,
1005
- Kalibrierdatum, aktive SK-Saetze, SSH konfiguriert).
1006
- - Sensor Quality mit "missing" im Chip-Zaehler und einer
1007
- Ignore / Restore-Taste pro Zeile.
1008
- - Slide von oben zeigt die Tab-Leiste wieder.
1009
- - Doctor: Ausblenden-Taste bleibt sichtbar, Findings und Summary
1010
- jetzt uebersetzt.
1011
- - Alarm-Meldungen und Sprachausgabe in der aktiven Sprache.
1012
- - Remote-Konsole ohne roten Rahmen; `tc` als Standard-Login.
1013
- - Erweiterte DE-Uebersetzungen.
1014
-
1015
- ### Français
1016
-
1017
- **Nouveautés**
1018
-
1019
- - Onglet Chart avec figeage au survol et graduation Y par bande.
1020
- - Onglet Tune avec bouton ↺ par slider et modal de fork de profil
1021
- (Annuler revient a la valeur d'origine).
1022
- - Grille Setup avec puces d'etat vivantes (hote, langue, dernier
1023
- ajustement de calibration, phrases SK actives, SSH configure).
1024
- - Sensor Quality avec "missing" dans le compteur et un bouton
1025
- Ignorer / Restaurer par ligne.
1026
- - Glissement du haut vers le bas pour reafficher la barre
1027
- d'onglets.
1028
- - Doctor: bouton Masquer toujours visible, findings et resume
1029
- traduits.
1030
- - Alarmes et voix TTS dans la langue active.
1031
- - Console distante sans cadre rouge; `tc` par defaut.
1032
- - Traductions FR etendues.
1033
-
1034
- ## 2.2.1 — 2026-08-31 — Rev128
1035
-
1036
- Docs-only patch release.
1037
-
1038
- - **README** — embedded the four App Store screenshots as absolute
1039
- `raw.githubusercontent.com` URLs so they render both on the NPM
1040
- website (which only surfaces the README, not `signalk.screenshots[]`)
1041
- and inside any other viewer that follows the README markdown. The
1042
- registry serves the README frozen at publish time, so this bump is
1043
- the only way to get the updated README to `npmjs.com/package/signalk-pypilot-newui`.
1044
-
1045
- ## 2.2.0 — 2026-08-31 — Rev88..Rev127
1046
-
1047
- Forty in-session Revs of navigation-oriented features on top of the
1048
- 2.1.0 visual foundation. Themes: turn the plugin into an intelligence
1049
- layer over pypilot (historian → KPIs → alarms → Doctor → pre-departure
1050
- check), give sail-raising its own AP mode ("Aproado"), and rebuild the
1051
- Setup tab as an app-launcher grid of fullscreen tiles.
1052
-
1053
- ### English
1054
-
1055
- **Added**
1056
-
1057
- - **Aproado pseudo-mode** (Rev87-92) — a new "raise the sails"
1058
- autopilot mode not in pypilot upstream. Pick BY BOW / BY STERN
1059
- via a 5 s modal, the plugin switches pypilot to wind mode with
1060
- target 0° (head to wind), and a floating HUD over the boat shows
1061
- elapsed time, straight-line distance from the start and the
1062
- compass heading captured at activation. SALIR restores the
1063
- previous mode + target + engaged state. The BY STERN branch
1064
- forces a stern-through swing by walking a chain of intermediate
1065
- targets 60° apart so pypilot cannot shortcut through the bow.
1066
- - **Telemetry historian + Chart tab** (Rev93-96) — RAM ring buffer
1067
- at 1 Hz for the last 30 min (backend, Pi 5 friendly). New Chart
1068
- tab shows nine Trip Stats cards (AP engaged time, distance,
1069
- energy, mean/RMS/p95 heading error, servo runtime, tacks/gybes,
1070
- peak servo A) all fed live via SK paths, plus a canvas history
1071
- with 30 s / 2 min / 10 min windows and per-path chips.
1072
- - **Sensor Quality panel** (Rev97-98) — age / observed Hz / jitter /
1073
- source for every critical SK path the AP depends on, with a
1074
- green / amber / red pill. Endpoint `/quality` and a poll while
1075
- Setup is visible.
1076
- - **Servo Health card** (Rev99) — baseline current learned from
1077
- the first ~10 min of engaged navigation, then a live deviation
1078
- ratio flags "elevated" (>1.20×) or "high" (>1.65×) — early
1079
- warning for a hard rudder, fouling, or a stiffened linkage. Six
1080
- new SK paths under `steering.autopilot.pypilot.servo.health.*`.
1081
- - **Alarm engine** (Rev100-101) — seven built-in rules (heading
1082
- deviation, unable to steer, servo overcurrent / temp, low
1083
- voltage, sensor lost, pypilot disconnected) publishing
1084
- canonical SK notifications `notifications.autopilot.*`. Sticky
1085
- banner in the visor with ACK + close, WebAudio beeps +
1086
- speech-synthesis of the message, and a full config panel in
1087
- Setup (enable / mute / status per rule).
1088
- - **Pre-departure check** (Rev102) — a single verdict card
1089
- (READY / READY-WITH-CAVEATS / DO-NOT-ENGAGE) that aggregates
1090
- connectivity, sensor quality, servo health and voltage into one
1091
- answer before you leave the dock.
1092
- - **Pypilot Doctor** (Rev103-104, Rev120-121) — press DIAGNOSE,
1093
- the plugin records 2-3 min with the AP engaged, runs heuristic
1094
- rules on the heading error stream (bias / oscillation / low
1095
- authority) and proposes P / I / D adjustments with a plain
1096
- explanation of why and what to expect. Nothing is applied
1097
- automatically — each suggestion has Apply and Discard buttons.
1098
- Rev120: the first Apply of a session forks the current pypilot
1099
- profile into a new `doctor-YYYYMMDD-HHMM` profile so the
1100
- previous gains stay one profile-select away.
1101
- - **Setup as a launcher grid** (Rev105-119, 124) — every setup
1102
- block is now a square tile in a 2 / 3 / 4-column grid. Tap
1103
- opens fullscreen with a small X to close and ESC support.
1104
- Body scroll is locked while a tile is fullscreen. Live summary
1105
- pill on every tile so the state reads at a glance without
1106
- expanding. Connection consolidated (status + LAN scan + manual
1107
- host + Autopilot Provider in one card). "Emergency" renamed to
1108
- "Remote Control Console".
1109
- - **Interactive SSH console** (Rev113-115) — free-form command
1110
- input with on-screen arrow buttons for mobile history
1111
- navigation, accumulative output textarea, clipboard fallback
1112
- for HTTP LAN, and 14 preset buttons with plain human labels
1113
- (Memory / IP table / WiFi / Temperature / Who is connected /
1114
- ...). BusyBox / piCore-safe fallbacks for every command.
1115
- - **Full RangeSetting sliders in Calibration** (Rev116-119) —
1116
- every pypilot `RangeSetting` grouped by category (rudder /
1117
- servo / imu / ap / other) with the same look as the Tune tab,
1118
- a lock checkbox with the padlock emoji, a frozen "before:"
1119
- baseline that never moves as you drag, and a per-slider ↺
1120
- restore button that snaps back to the baseline.
1121
- - **Nudge steps moved inside Calibration** (Rev124) — one card
1122
- less in the launcher grid; heading trim buttons live where
1123
- they belong conceptually.
1124
-
1125
- **Changed**
1126
-
1127
- - **Gota Chain shapes SWAP** (Rev122-126) — after several iterations
1128
- with Carlos on Tunatunes: the AMBER "A" arrow (apparent wind) is
1129
- now the LARGE piece (its tip reaches deep into the rose), and
1130
- the TEAL "T" arrow (true wind) is the SMALL outer one. Colors
1131
- and letters kept their canonical meaning; only the SVG paths
1132
- moved so the amber piece is visually the dominant one.
1133
- - **Alarm summary semantics** (Rev107-108) — "activa" (fired) was
1134
- ambiguous with "activada" (enabled). New wording separates
1135
- `firing / sonando` (fired now) from `watching / vigilando`
1136
- (enabled and healthy). Summary always shows both counts.
1137
- - **i18n audit** (Rev105, Rev112, Rev121) — every hard-coded
1138
- English string in the ES bundle purged ("engagear" → "activar",
1139
- KIP paths table fully translated, alarm messages), backend
1140
- suggestion reason / effect exposed as i18n keys + args so the
1141
- frontend renders in the user's language.
1142
- - **Sensor Quality thresholds** (Rev98) — dropped `minHz` from the
1143
- defaults; the 1 Hz sampler capped observed Hz, so the threshold
1144
- produced spurious "degraded" flags. Freshness (`age`) is now
1145
- the only signal that penalises status.
1146
-
1147
- **Fixed**
1148
-
1149
- - **Fullscreen tile portal** (Rev110-111) — Setup fullscreen tiles
1150
- are now portaled to `<body>` so the `.tab-panel` transform
1151
- (used by the tab animation) cannot break `position: fixed`.
1152
- Fullscreen always covers the viewport now, no matter which
1153
- card was scrolled into.
1154
- - **Copy button on HTTP** (Rev114) — `navigator.clipboard` is a
1155
- secure-context API; on plain http://192.168.x.y it silently
1156
- no-ops. Compat helper falls back to `document.execCommand("copy")`.
1157
- - **Doctor `elapsedSec`** (Rev104) — was counting past the target
1158
- duration once the session completed. Now freezes at the target.
1159
- - **Sliders baseline eaten by refresh** (Rev117-118) — the
1160
- Calibration "before:" value used to reset to the just-edited
1161
- number on every `/values` poll. Now captured once per name and
1162
- kept frozen across renders.
1163
- - **Aproado restore** (Rev90) — mode / target / engaged restore on
1164
- SALIR was skipping steps after one failure. Now each step is
1165
- guarded independently and logged in the console.
1166
- - **Sensor Quality parsing** (Rev94) — `navigation.attitude` is a
1167
- composite `{pitch, roll, yaw}` object, not a scalar path.
1168
- Custom subfield reader added for heel.
1169
- - Cosmetic Firefox warnings (`::-moz-focus-inner`, comment-nested
1170
- `*/`) purged (Rev95-106).
1171
-
1172
- ### Español
1173
-
1174
- **Nuevo**
1175
-
1176
- - **Modo Aproado** (Rev87-92): pseudo-modo AP para subir velas.
1177
- Elige POR PROA / POR POPA en un modal de 5 s, el plugin pasa
1178
- pypilot a modo viento con objetivo 0° (proa al viento), y un
1179
- HUD flotante sobre el barco muestra tiempo transcurrido,
1180
- distancia lineal desde el arranque y el rumbo compás capturado
1181
- al activar. SALIR restaura modo + objetivo + engaged previos.
1182
- POR POPA fuerza el swing por popa con una cadena de objetivos
1183
- intermedios cada 60° para que pypilot no atajaje por proa.
1184
- - **Historian de telemetría + pestaña Gráfica** (Rev93-96):
1185
- buffer circular en RAM a 1 Hz durante 30 min (backend, Pi 5).
1186
- Nueva pestaña Gráfica con 9 tarjetas de estadísticas de sesión
1187
- (tiempo activo, distancia, energía, error medio / RMS / p95,
1188
- servo activo, viradas / trasluchadas, pico servo A) y canvas
1189
- con ventanas 30 s / 2 min / 10 min.
1190
- - **Panel Calidad de sensores** (Rev97-98): edad / Hz / jitter /
1191
- fuente por cada ruta SK crítica con semáforo verde / ámbar /
1192
- rojo.
1193
- - **Tarjeta Salud del servo** (Rev99): baseline aprendido de los
1194
- primeros ~10 min de navegación activa, luego ratio de
1195
- desviación marca "elevated" (>1.20×) o "high" (>1.65×) —
1196
- alerta temprana de timón duro, incrustaciones o rozamientos.
1197
- - **Motor de alarmas** (Rev100-101): 7 reglas built-in
1198
- (desviación de rumbo, incapaz de gobernar, sobrecorriente
1199
- servo, temperatura servo, batería baja, sensor perdido,
1200
- pypilot desconectado) publicando notifications SK canónicas.
1201
- Banner sticky en el visor con ACK y cierre X, beeps WebAudio
1202
- + speechSynthesis del mensaje, panel de configuración en
1203
- Setup (enable / mute / estado por regla).
1204
- - **Comprobación del piloto** (Rev102): tarjeta única con
1205
- veredicto (LISTO / LISTO CON RESERVAS / NO ACTIVAR) que
1206
- agrega conectividad, calidad de sensores, salud del servo y
1207
- voltaje.
1208
- - **Asesoramiento y diagnóstico** (Rev103-104, Rev120-121):
1209
- botón DIAGNOSTICAR, el plugin graba 2-3 min con el AP activo,
1210
- aplica reglas heurísticas (sesgo / oscilación / autoridad) y
1211
- propone ajustes P / I / D con explicación clara del porqué y
1212
- del efecto esperado. Nada se aplica automáticamente — cada
1213
- sugerencia tiene botones Aplicar y Descartar. Rev120: el
1214
- primer Aplicar de la sesión duplica el perfil actual en uno
1215
- nuevo `doctor-AAAAMMDD-HHMM` para que el original quede
1216
- intacto a un toque en Ajustes → Perfil.
1217
- - **Setup como launcher grid** (Rev105-119, 124): cada bloque
1218
- de Setup es un tile cuadrado en un grid 2 / 3 / 4 columnas.
1219
- Tap abre pantalla completa con X para cerrar, header sticky
1220
- y bloqueo de scroll de fondo. Etiqueta resumen en vivo en
1221
- cada tile. Conexión pypilot une 4 secciones. "Emergencia"
1222
- renombrada a "Consola control remoto".
1223
- - **Consola SSH interactiva** (Rev113-115): input libre con
1224
- botones ↑▲ ↓▼ para navegar el historial en móvil, textarea
1225
- acumulativo, fallback de portapapeles para LAN HTTP y 14
1226
- presets con etiquetas humanas (Memoria RAM / Tabla IP / WiFi
1227
- / Temperatura / Quien está conectado / ...). Todos los
1228
- comandos con fallbacks BusyBox / piCore.
1229
- - **Sliders completos de calibración** (Rev116-119): todos los
1230
- `RangeSetting` de pypilot agrupados por categoría (timón /
1231
- servo / imu / piloto / otros) con la misma apariencia que
1232
- Ajustes, candado azul con emoji, valor "antes:" congelado
1233
- que NUNCA se mueve al arrastrar, y un botón ↺ por slider
1234
- para restaurar el valor original.
1235
- - **Botones corrección rumbo dentro de Calibración** (Rev124):
1236
- una tarjeta menos en el grid; los pasos ±1 / ±10 quedan
1237
- donde corresponde conceptualmente.
1238
-
1239
- **Cambiado**
1240
-
1241
- - **Swap de formas en la Gota Chain** (Rev122-126): tras varias
1242
- iteraciones con Carlos en Tunatunes, la flecha AMARILLA "A"
1243
- (viento aparente) es ahora la GRANDE (su punta llega hasta
1244
- el centro de la rosa) y la flecha VERDE "T" (viento real)
1245
- es la PEQUEÑA externa. Colores y letras mantienen su
1246
- significado canónico; solo se movieron los paths SVG para
1247
- que la amarilla domine visualmente.
1248
- - **Semántica de alarmas** (Rev107-108): "activa" era ambiguo.
1249
- Nueva jerga separa `sonando` (disparada ahora) de `vigilando`
1250
- (habilitada y OK). El resumen muestra siempre ambos contadores.
1251
- - **Auditoría i18n** (Rev105, Rev112, Rev121): purga de cadenas
1252
- inglesas hardcoded en el bundle ES ("engagear" → "activar",
1253
- tabla de KIP totalmente traducida, mensajes de alarma).
1254
- Reason / effect de las sugerencias del Doctor expuestos como
1255
- claves i18n para render en el idioma del visor.
1256
- - **Umbrales Calidad de sensores** (Rev98): quitado `minHz` de
1257
- los defaults; el sampler a 1 Hz capaba la Hz observada y
1258
- generaba "degraded" falsos.
1259
-
1260
- **Corregido**
1261
-
1262
- - **Portal de tile fullscreen** (Rev110-111): tiles fullscreen
1263
- ahora se mueven al `<body>` para que el `transform` de
1264
- `.tab-panel` (animación entre pestañas) no rompa `position:
1265
- fixed`.
1266
- - **Botón Copiar en HTTP** (Rev114): `navigator.clipboard`
1267
- requiere secure context. Fallback a `document.execCommand("copy")`.
1268
- - **Doctor `elapsedSec`** (Rev104): contaba pasado el objetivo.
1269
- Ahora congela al final.
1270
- - **Baseline de sliders comido por refresh** (Rev117-118): el
1271
- valor "antes" se reseteaba a lo recién editado en cada poll.
1272
- Ahora se captura una vez y no se toca.
1273
- - **Restore de Aproado** (Rev90): SALIR saltaba pasos tras un
1274
- fallo. Ahora cada paso protegido por su try/catch.
1275
- - **Parsing Calidad de sensores** (Rev94): `navigation.attitude`
1276
- es objeto compuesto, no path escalar. Fix del subfield heel.
1277
- - Warnings cosméticos Firefox (`::-moz-focus-inner`, `*/` en
1278
- comentarios) purgados (Rev95-106).
1279
-
1280
- ### Français
1281
-
1282
- **Ajouté**
1283
-
1284
- - **Mode Aproado** (Rev87-92) : pseudo-mode AP pour hisser les
1285
- voiles. PAR AVANT / PAR ARRIÈRE, l'AP passe en mode vent avec
1286
- cible 0° (nez au vent), HUD flottant avec temps, distance et
1287
- cap boussole capturé. SORTIR restaure mode / cible / engagé
1288
- précédents. PAR ARRIÈRE force le virage par l'arrière avec
1289
- une chaîne d'objectifs intermédiaires de 60°.
1290
- - **Historian de télémétrie + onglet Graphique** (Rev93-96) :
1291
- buffer 30 min en RAM à 1 Hz, canvas d'historique et 9 cartes
1292
- Trip Stats.
1293
- - **Panneau Qualité des capteurs** (Rev97-98) : âge / Hz /
1294
- jitter / source par chaque chemin SK critique.
1295
- - **Carte Santé du servo** (Rev99) : baseline appris + ratio de
1296
- déviation avec alertes "elevated" et "high".
1297
- - **Moteur d'alarmes** (Rev100-101) : 7 règles built-in,
1298
- notifications SK canoniques, banner avec ACK et fermeture,
1299
- audio WebAudio + parole synthétique, config par règle.
1300
- - **Vérification pré-départ** (Rev102) : verdict READY / avec
1301
- caveats / NE PAS ENGAGER.
1302
- - **Docteur pypilot** (Rev103-104, Rev120-121) : 2-3 min
1303
- d'enregistrement, règles heuristiques, propositions P / I / D
1304
- avec Appliquer et Rejeter. Le premier Appliquer crée un
1305
- nouveau profil `doctor-AAAAMMJJ-HHMM`.
1306
- - **Setup en grille launcher** (Rev105-119, 124) : tiles carrés,
1307
- fullscreen par tap, résumé en direct, connexion consolidée,
1308
- "Consola contrôle remote" (renommé).
1309
- - **Console SSH interactive** (Rev113-115) : entrée libre,
1310
- historique navigable au mobile, 14 presets aux noms humains,
1311
- fallbacks BusyBox / piCore.
1312
- - **Sliders complets de calibration** (Rev116-119) : chaque
1313
- RangeSetting groupé, cadenas, baseline gelée, bouton restaurer.
1314
- - **Boutons correction cap dans Calibration** (Rev124).
1315
-
1316
- **Modifié**
1317
-
1318
- - **Swap des formes Gota Chain** (Rev122-126) : la flèche AMBRE
1319
- "A" (vent apparent) est maintenant la GRANDE, la TEAL "T"
1320
- (vent réel) la petite. Couleurs et lettres conservent leur
1321
- sens canonique.
1322
- - **Sémantique alarmes** (Rev107-108) : `sonando` vs
1323
- `vigilando`, résumé affiche les deux compteurs.
1324
- - **Audit i18n** (Rev105, Rev112, Rev121) : purge de l'anglais
1325
- dans les bundles ES / FR / DE.
1326
- - **Seuils Qualité capteurs** (Rev98) : `minHz` supprimé.
1327
-
1328
- **Corrigé**
1329
-
1330
- - Portal du tile fullscreen (Rev110-111).
1331
- - Bouton Copier en HTTP (Rev114).
1332
- - Doctor `elapsedSec` gelé au final (Rev104).
1333
- - Baseline des sliders congelée (Rev117-118).
1334
- - Restore Aproado protégé par try/catch (Rev90).
1335
- - Lecture composite de `navigation.attitude` (Rev94).
1336
- - Warnings cosmétiques Firefox purgés (Rev95-106).
1337
-
1338
- ### Deutsch
1339
-
1340
- **Neu**
1341
-
1342
- - **Aproado-Pseudomodus** (Rev87-92) — AP-Modus zum Segelhissen.
1343
- ÜBER BUG / ÜBER HECK, AP schaltet in Wind-Modus mit Ziel 0°,
1344
- HUD über dem Boot mit Zeit / Distanz / Kompass-Kurs. AUS
1345
- stellt Modus / Ziel / Engaged wieder her. ÜBER HECK erzwingt
1346
- den langen Schwenk mit Zwischen-Zielen alle 60°.
1347
- - **Telemetrie-Historian + Chart-Tab** (Rev93-96) — Ringpuffer
1348
- 30 min RAM bei 1 Hz, 9 Trip-Stats-Karten, Canvas mit Fenstern
1349
- 30 s / 2 min / 10 min.
1350
- - **Sensor-Qualität-Panel** (Rev97-98).
1351
- - **Servo-Health-Karte** (Rev99) — gelernter Baseline mit
1352
- Alarmen "elevated" / "high".
1353
- - **Alarm-Engine** (Rev100-101) — 7 built-in Regeln, SK
1354
- Notifications, Banner mit ACK und X, WebAudio + Sprache,
1355
- Config-Panel.
1356
- - **Pre-Departure-Check** (Rev102).
1357
- - **Pypilot Doctor** (Rev103-104, Rev120-121) — Diagnose-
1358
- Sitzung, Heuristik-Regeln, P/I/D-Vorschläge mit Anwenden /
1359
- Verwerfen. Erstes Anwenden erzeugt neues Profil.
1360
- - **Setup als Launcher-Grid** (Rev105-119, 124) — quadratische
1361
- Tiles, Vollbild-Tap, Live-Zusammenfassung.
1362
- - **Interaktive SSH-Konsole** (Rev113-115) — freie Eingabe,
1363
- Pfeil-Buttons mobil, 14 Presets mit menschlichen Labels.
1364
- - **Vollständige RangeSetting-Slider in Kalibrierung**
1365
- (Rev116-119) — Schloss, eingefrorene Baseline,
1366
- Wiederherstellen-Knopf pro Slider.
1367
- - **Nudge-Buttons in Kalibrierung** (Rev124).
1368
-
1369
- **Geändert**
1370
-
1371
- - **Gota-Chain-Form-SWAP** (Rev122-126).
1372
- - **Alarm-Semantik** (Rev107-108) — `sonando` / `vigilando`.
1373
- - **i18n-Audit** (Rev105, Rev112, Rev121).
1374
- - **Sensor-Qualität-Schwellen** (Rev98) — `minHz` entfernt.
1375
-
1376
- **Behoben**
1377
-
1378
- - Fullscreen-Tile-Portal (Rev110-111).
1379
- - Copy-Button auf HTTP (Rev114).
1380
- - Doctor `elapsedSec` (Rev104).
1381
- - Slider-Baseline eingefroren (Rev117-118).
1382
- - Aproado-Restore geschützt (Rev90).
1383
- - `navigation.attitude`-Zusammensetzung (Rev94).
1384
- - Kosmetische Firefox-Warnungen (Rev95-106).
1385
-
1386
- ## 2.1.0 — 2026-08-25 — Rev68..Rev87
1387
-
1388
- Twenty in-session Revs of iterative visual + reliability work rolled up
1389
- into a proper minor release. Themes: a full redesign of the compass-rose
1390
- autopilot overlays ("Gota Chain"), a hard-earned fix for AP mutations
1391
- racing each other across the SignalK bus, and a permanent seamless-auth
1392
- story so a Signal K restart never again silently disables the visor's
1393
- mutation buttons.
1394
-
1395
- ### Added
1396
-
1397
- - **Gota Chain overlay** — the target diamond and the AWA/TWA wind
1398
- arrows are now three concentric SVG pieces that interlock into one
1399
- clean droplet when `target ≡ AWA ≡ TWA`. Each piece carries its own
1400
- letter (A / T) and its own color ring-tick that marks the exact
1401
- angle on the compass. When any of the three drifts, its V-hembra
1402
- opens as a visible hollow. Iterated over 12 Revs (Rev68..Rev82)
1403
- with Carlos on Tunatunes.
1404
- - **COG line** — chain of yellow chevrons from the bow toward the
1405
- course-over-ground direction. Visible only when `SOG > 0.15 kn`.
1406
- - **Current vector** — two parallel wavy blue lines with three
1407
- triangles pointing at the boat. Computed as `SOG − SOW` (added
1408
- new `navigation.speedThroughWater` subscription). Visible when
1409
- `|current| > 0.2 kn`.
1410
- - **4-card overlay selector** — long-press any rose corner OR the
1411
- boat sprite to open the popup with the four toggle cards (AWA,
1412
- TWA, COG, Current), each with a large preview and a data-
1413
- requirement line ("Needs a wind sensor", "Only shows when SOG >
1414
- 0.15 kn", etc). Toggles persist in `localStorage`.
1415
- - **Rose cardinals colour-coded**: N orange, E green, S blue, W red.
1416
- All at the same radius `r = 70`. Thin black stroke around each
1417
- letter for legibility on any background.
1418
- - **Seamless auth via SK Access Request** — the visor now uses a
1419
- device-scoped, permanent SK token instead of a short-lived JWT.
1420
- On first install the user approves once from `SK admin → Security
1421
- → Access Requests`; from then on the token survives every SK
1422
- server restart, every plugin redeploy, and every browser reload,
1423
- so mutations (engage / mode / target) NEVER silently 401 again.
1424
- - **`.\deploy.ps1 -FrontOnly`** — new script flag that syncs
1425
- `public/` only (no build, no npm install, no `systemctl restart
1426
- signalk`). Use it when the diff is HTML / CSS / JS only —
1427
- `Ctrl+F5` in the browser picks it up. Avoids the plugin-socket
1428
- reconnect to `pypilot_web` on the Pi Zero W that pushed the
1429
- TinyPilot to hang after a big batch of deploys.
1430
-
1431
- ### Fixed
1432
-
1433
- - **AP mutation race (`DIA jumps to a stale target for a beat`).**
1434
- Frontend used to `Promise.all([engage, target])` and the backend's
1435
- `pushAutopilotUpdate` published ALL SK autopilot fields on every
1436
- notify — so whichever POST landed first emitted a delta that
1437
- carried the OTHER field's STALE value (typically an old target
1438
- from the previous session, e.g. `115°` when the intent was `75°`).
1439
- Fixed by refactoring `AutopilotProvider.notifyChanged(fields)` to
1440
- accept `"engaged" | "target" | "all"` and `pushAutopilotUpdate` to
1441
- only emit the paths that actually changed. Parallel POSTs no
1442
- longer clobber each other with stale copies.
1443
- - **Wind-mode target capture on engage.** `doEngage` always sent
1444
- `state.heading` as the target regardless of mode; pypilot in wind
1445
- mode interpreted that compass value as a wind angle and steered
1446
- the boat toward nonsense. Now captures `state.windAngle` (apparent)
1447
- or `state.windAngleTrue` (true wind) when the active mode contains
1448
- "wind"; falls back to heading only for compass / gps / nav.
1449
- - **AP button "click missed" and engage / disengage bounce**
1450
- hardened: switched from `click` to `pointerdown` (fires once per
1451
- press on both touch and mouse without waiting for the
1452
- browser-synthesised `click`), swallow the trailing `click` with
1453
- `preventDefault + stopPropagation`, `touch-action: manipulation`
1454
- on the button, plus an 800 ms cooldown gate that survives the
1455
- ~200 ms POST round-trip. Ghost taps from `touchend` no longer
1456
- produce a phantom second engage.
1457
- - **Wind speed chip tap regressions.** Chip was picked up by the
1458
- corner-selector `forEach` (auto-attached its 500 ms long-press
1459
- timer → opened the wrong popup) AND the `<main>` tab-swipe
1460
- handler treated any tap-with-drift on it as a horizontal swipe
1461
- → the chip appeared to slide right. Renamed the chip class to
1462
- `.wind-chip` so the forEach ignores it and replaced the shared
1463
- handler with a bespoke `pointerdown/up` short-tap detector that
1464
- stops touch propagation.
1465
- - **HDG chip / compass labels covered by DIA** on engage: reordered
1466
- the SVG stack so the compass card and HDG chip render AFTER the
1467
- gota pieces, never behind them.
1468
- - **DIA arc rendering asymmetry** ("bump on one side") when
1469
- rotated: `stroke-linejoin` switched from `round` to `miter`,
1470
- raya `stroke-linecap` switched from `round` to `butt` to remove
1471
- the semi-circle that overshot the ring line.
1472
-
1473
- ### Changed
1474
-
1475
- - **Wind speed chip moved from an SVG group to a positioned HTML
1476
- `.wind-chip` div** overlaid on the rose bottom-center. Text
1477
- baseline now aligns with the two bottom corner cells so all
1478
- three values (BL / BC / BR) read as one row.
1479
- - **SVG stacking order** finalised (bottom → top): ring · wind
1480
- sectors · boat sprite · TWA · AWA · DIA · raya · AWA / TWA
1481
- colour ring-ticks · COG line · current vector · compass card ·
1482
- HDG chip. Wind chip is a separate HTML overlay above the SVG.
1483
- - **Restart engine** already switched to `sv restart` (piCore
1484
- runit) in 2.0.4; the docs, i18n text and Info-tab quick-start
1485
- now match — the earlier "three escalated restarts" wording was
1486
- outdated (Emergency section has been 2 buttons since Rev66:
1487
- `RESTART pypilot` + `reboot Pi`; the safe `restart pypilot_web`
1488
- lives in the Debug console).
1489
- - **Info-tab quick-start** rewritten in EN + ES to describe the
1490
- Gota Chain, the 4-card overlay selector, the new cardinal
1491
- colors, and boat-long-press as an alternative selector opener.
1492
- - **README** overview and "Remote restart / reboot" section
1493
- rewritten to match the current 2-button + Debug-console layout,
1494
- and to describe the Gota Chain + optional overlays.
1495
-
1496
- ## 2.0.4 — 2026-08-21 — Rev67
1497
-
1498
- Reintroduces (with proper QA) the UI polish + reliability work that was
1499
- withdrawn in 2.0.3 when 2.0.2 hit regressions. Every item below was
1500
- tested on Carlos's Tunatunes Pi Zero W setup before publish.
1501
-
1502
- ### Fixed
1503
-
1504
- - **Restart engine now uses `sv` (runit), not `systemctl`.** piCore does
1505
- not ship systemd; the previous `sudo systemctl restart pypilot pypilot_web`
1506
- was silently falling through to `|| true` on every install. Only the
1507
- `pkill -9` had any effect, and runit's `runsv` was auto-relaunching
1508
- the killed processes into a slightly inconsistent state that
1509
- accumulated zombies + I2C contention until the Pi Zero locked up.
1510
- Fixed to `sudo sv restart /etc/sv/pypilot /etc/sv/pypilot_web /etc/sv/pypilot_hat`
1511
- with a selective `pkill -9 -f 'python.*pypilot --version'` safety net.
1512
- - **`pypilot --version` preset in Debug console wrapped in `timeout 3`.**
1513
- On piCore that command starts the full pypilot process and never
1514
- exits cleanly; each click was leaving a zombie holding I2C and TCP
1515
- resources. Falls back to reading the packaged version file.
1516
- - **Target diamond drifted after tapping AP on laggy links.** The
1517
- optimistic-target assign now happens INSTANTLY on click (same tick),
1518
- not after the parallel promise resolves - so the diamond snaps to
1519
- the captured heading immediately, without a jump to the stale value
1520
- then back. Nudge also updates `state.target` optimistically so the
1521
- diamond moves in the same tick as the button press.
1522
- - **socket.io auto-reconnect no longer gives up.** `reconnectionAttempts`
1523
- changed from 30 (which could exhaust on a long outage) to Infinity;
1524
- backoff capped at 30 s. Restarts done via `/debug-cmd` (`restart.web`
1525
- / `reboot.pi`) now schedule an explicit `pause+resume` after the
1526
- operation.
1527
- - **Reconnection watchdog** (every 60 s, was 30 s): if the socket has
1528
- been disconnected > 90 s, force a pause+resume once. Max 3 forced
1529
- reconnects in a rolling 15-min window so a permanently-dead
1530
- pypilot_web is not turned into a local DDoS. Watchdog interval
1531
- loosened per Carlos: "con la Pi Zero debemos ser suuuuuper ratas".
1532
-
1533
- ### Changed - Emergency section simplified
1534
-
1535
- The 3-level escalated restart triad shipped in 2.0.2 has been trimmed
1536
- to 2 levels in Setup -> Emergency: **RESTART pypilot** (level 2, ~10-15 s
1537
- outage) + **reboot Pi** (level 3, ~35-60 s). The old level 1
1538
- (`restart pypilot_web`) rarely helps in practice - when the Pi Zero is
1539
- saturated a full reboot is what actually helps - so it moved to the
1540
- Debug console below as an advanced preset for the ~5 % edge cases.
1541
-
1542
- ### Compass rose
1543
-
1544
- - HDG in a small dark chip that straddles the top of the ring, narrowed
1545
- from 48 to 40 units wide so the enlarged target diamond can peek out
1546
- on all four sides even when target is dead ahead.
1547
- - Target diamond enlarged (28 wide, 30 tall) so its top point asoma
1548
- above the HDG chip and its bottom point below it.
1549
- - Wind arrows both solid (no dashed shaft on TWA), amber AWA (big head)
1550
- + sea-green (#5ce8c0) TWA (small head), each with its own rotating
1551
- text label placed at very different radii so they never overlap even
1552
- when both arrows point in the same direction.
1553
- - Wind speed at the bottom of the rose now inside a small dark chip
1554
- (same look as the HDG chip on top). Short-tap on the chip OR on a
1555
- corner mapped to "wind" flips AWS/TWS. Long-press opens the popup.
1556
- - Corner options `AWA` and `TWA` now show angle AND matching wind speed
1557
- (AWA/AWS and TWA/TWS) as a combined 2-line cell, consistent with the
1558
- `wind` combo.
1559
-
1560
- ### Rudder angle indicator in the swipe-hint bar
1561
-
1562
- - Default range **±70°** (was ±45°), matching typical TinyPilot configs.
1563
- - Explicit numeric labels at both ends showing the actual range from
1564
- the pypilot catalog.
1565
- - Intermediate tick marks every 10° plus numeric mid-labels at 20°, 40°
1566
- and 60° each side, so the operator reads the rudder angle at a glance.
1567
-
1568
- ### Internal
1569
-
1570
- - Debug console preset `pypilot.version` now `timeout 3 python3 -c
1571
- 'import pypilot; print(pypilot.__version__)'` with fallback.
1572
- - Merge-based `_pluginConfigMerge()` helper preserved.
1573
- - Per-cell state for the long-press / short-tap handler on the corner
1574
- dashboard (Rev65's shared timer flags were race-flipping and killing
1575
- the tap toggle intermittently).
1576
-
1577
- ## 2.0.3 — 2026-08-20 — Rev66 - rollback of 2.0.2
1578
-
1579
- Codebase is functionally identical to 2.0.1. Version 2.0.2 introduced
1580
- regressions in the socket auto-reconnect and the new "escalated restart"
1581
- triad that could leave the plugin showing `connected: false` against a
1582
- pypilot_web that is in fact alive and serving other consumers. Rolling
1583
- back to the 2.0.1 behaviour and reintroducing the 2.0.2 UI polish
1584
- (target diamond snapshot, redesigned AWA/TWA arrows, HDG chip, wind
1585
- speed chip, tap wind toggle, rudder bar, debug console, escalated
1586
- restarts) in a later 2.0.4+ release, one item at a time with proper QA
1587
- before publish. 2.0.2 has been deprecated on npm.
1588
-
1589
- ## 2.0.1 — 2026-08-20 — Rev64
1590
-
1591
- Patch release to validate the GitHub Actions publish pipeline with
1592
- [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers)
1593
- (OIDC-based, no NPM_TOKEN secret in the repo). No functional changes
1594
- versus 2.0.0. Look for the "verified provenance" badge on the npm page.
1595
-
1596
- ## 2.0.0 — 2026-08-20 — Rev63
1597
-
1598
- **Breaking release addressing feedback from Sean D'Epagnier (pypilot author,
1599
- [#1](https://github.com/Aitonos/signalk-pypilot-newui/issues/1) and
1600
- [#2](https://github.com/Aitonos/signalk-pypilot-newui/issues/2)).**
1601
-
1602
- ### Breaking - path standardisation (issue #1)
1603
-
1604
- Every pypilot key now maps 1:1 to `steering.autopilot.pypilot.<key>`
1605
- verbatim. The old `FIXED_MAPPINGS` table with hand-picked renames and
1606
- C -> K / deg -> rad conversions is gone. Signal K infers units cleanly
1607
- enough from the path type, consumers pick their display unit, and we
1608
- surface pypilot's own `meta.units` verbatim.
1609
-
1610
- **If you have KIP widgets or WilhelmSK dashboards wired to 1.0.0 paths,
1611
- rewire them per the table below**:
1612
-
1613
- | Old (1.0.0) | New (2.0.0) |
1614
- |-------------------------------------------------------------------|----------------------------------------------------------|
1615
- | `steering.autopilot.pypilot.availableModes` | `steering.autopilot.pypilot.ap.modes` |
1616
- | `steering.autopilot.pypilot.pilot` | `steering.autopilot.pypilot.ap.pilot` |
1617
- | `steering.autopilot.pypilot.gains.<pilot>.<gain>` | `steering.autopilot.pypilot.ap.pilot.<pilot>.<gain>` |
1618
- | `steering.autopilot.pypilot.servo.controllerTemperature` (K) | `steering.autopilot.pypilot.servo.controller_temp` (C) |
1619
- | `steering.autopilot.pypilot.servo.motorTemperature` (K) | `steering.autopilot.pypilot.servo.motor_temp` (C) |
1620
- | `steering.autopilot.pypilot.servo.ampHours` | `steering.autopilot.pypilot.servo.amp_hours` |
1621
- | `steering.autopilot.pypilot.calibration.imuHeadingOffset` (rad) | `steering.autopilot.pypilot.imu.heading_offset` (deg) |
1622
- | `steering.autopilot.pypilot.calibration.rudderRange` (rad) | `steering.autopilot.pypilot.rudder.range` (deg) |
1623
- | `steering.autopilot.pypilot.calibration.rudderOffset` | `steering.autopilot.pypilot.rudder.offset` |
1624
- | `steering.autopilot.pypilot.calibration.rudderScale` | `steering.autopilot.pypilot.rudder.scale` |
1625
- | `steering.autopilot.pypilot.calibration.rudderNonlinearity` | `steering.autopilot.pypilot.rudder.nonlinearity` |
1626
- | `steering.autopilot.pypilot.calibration.state` | `steering.autopilot.pypilot.rudder.calibration_state` |
1627
- | `steering.autopilot.pypilot.tack.state` | `steering.autopilot.pypilot.ap.tack.state` |
1628
- | `steering.autopilot.pypilot.tack.direction` | `steering.autopilot.pypilot.ap.tack.direction` |
1629
- | `steering.autopilot.pypilot.tack.timeout` | `steering.autopilot.pypilot.ap.tack.timeout` |
1630
- | `steering.autopilot.pypilot.errors.imu` | `steering.autopilot.pypilot.imu.error` |
1631
- | `steering.autopilot.pypilot.warnings.imu` | `steering.autopilot.pypilot.imu.warning` |
1632
- | `steering.autopilot.pypilot.errors.controller` | `steering.autopilot.pypilot.servo.controller` |
1633
-
1634
- `steering.autopilot.pypilot.profile`, `.profiles`, `.availablePilots`,
1635
- `.version`, `.runtime`, `.servo.voltage`, `.servo.current`,
1636
- `.servo.engaged`, `.servo.flags`, `.servo.controller` are unchanged
1637
- (their pypilot keys already matched the SK-side name).
1638
-
1639
- ### Fixed (issue #2) - `pypilot_values` is not one-shot
1640
-
1641
- Sean noted that `pypilot_values` can arrive multiple times, and on
1642
- subsequent deliveries it carries only the newly-appeared keys (typically
1643
- during boot while calibration is still loading). Our code assumed
1644
- one-shot AND replaced `this.catalog` wholesale on each delivery.
1645
-
1646
- - Comment in `pypilot-client.ts` fixed.
1647
- - Catalog is now MERGED, not replaced.
1648
- - `catalog` event carries `{ isDelta, newKeys }` so downstream handlers
1649
- can register put-handlers only for the new keys and avoid re-emitting
1650
- meta for paths already published.
1651
-
1652
- ### Added - Debug console in Setup
1653
-
1654
- New closed-whitelist SSH endpoint `POST /plugins/*/debug-cmd` accepting
1655
- one of the following presets (nothing arbitrary):
1656
-
1657
- - `logs.pypilot` - last 200 lines of `journalctl -u pypilot`
1658
- - `logs.pypilot_web` - last 200 lines of the web unit
1659
- - `logs.follow` - last 30 s (polled every 3 s from the UI when
1660
- "Follow logs" is on)
1661
- - `dmesg.tail` - last 100 lines of `dmesg`
1662
- - `top.snapshot` - one-shot `top -bn1`
1663
- - `uptime`, `df`, `pypilot.version`
1664
- - `restart.web` - restart only `pypilot_web` (level 1 of the triad)
1665
- - `reboot.pi` - full `sudo reboot` (level 3 of the triad)
1666
-
1667
- UI in Setup: preset buttons, scrollable output textarea, follow-logs
1668
- toggle (3 s polling), copy-to-clipboard, share-to-anywhere (Web Share
1669
- API), clear.
1670
-
1671
- ### Changed - three escalated restart levels in Setup / Emergency
1672
-
1673
- The old single "RESTART pypilot" button becomes a triad, colour-coded
1674
- from green (safest) to red (loudest):
1675
-
1676
- - **1. restart pypilot_web** (~3-5 s) - AP keeps steering.
1677
- - **2. RESTART pypilot** (~10-15 s) - the previous behaviour: AP drops
1678
- the heading briefly and re-engages after reconnect.
1679
- - **3. reboot Pi** (~35-60 s) - full Raspberry Pi reboot, no AP for
1680
- almost a minute.
1681
-
1682
- All three go through a shared **helm-manned confirmation modal** that
1683
- states the outage duration and scope in the current UI language and
1684
- asks the operator to confirm someone is at the helm before executing.
1685
- Cancel button, click outside and Escape all decline safely.
1686
-
1687
- ## 1.0.0 — 2026-08-18 — Rev32..Rev62 first public release
1688
-
1689
- The webapp went from a minimum viable control panel to a full-blown touch
1690
- visor + first-class Signal K bridge in this batch. Highlights:
1691
-
1692
- ### UI - Control tab
1693
-
1694
- - **Compass rose** (Rev47..Rev58): cardinals + 30° tick labels, boat-fixed
1695
- green/red sailing wedges reaching under the hull, AWA (amber) + TWA
1696
- (magenta dashed) arrows, cyan target diamond outside the ring, HDG at
1697
- the top rim, wind speed at the bottom.
1698
- - **4 configurable corner tiles** (Rev48): long-press to pick datum (HDG,
1699
- AWA/TWA/TWS/TWD, SOG, COG, TGT, depth, mode, polar-performance keys,
1700
- custom SK path).
1701
- - **Wind arrow independent toggles** (Rev58): AWA and TWA on/off separately,
1702
- persisted in localStorage.
1703
- - **Custom SK path** (Rev58): add any published Signal K path as a corner
1704
- tile with label + unit. Datalist auto-suggests paths seen on the bus.
1705
- - **Polar-performance opt-in** (Rev58): checkboxes for the 10 keys of
1706
- `signalk-polar-performance-plugin` + water temperature.
1707
- - **Optimistic AP engage** (Rev56): visual state flips instantly on click,
1708
- auto-retries on Tailscale flakes, rolls back with red-border flash on
1709
- hard failure. Guard blocks double-taps from firing opposite POSTs.
1710
- - **KIP-friendly Simple Switches** (Rev33..Rev38): `electrical.switches.pypilot.*`
1711
- for ap.state (momentary boolean), nudge (4 momentary), tack (3 momentary),
1712
- mode (radio), dynamic profile (radio).
1713
-
1714
- ### UI - Tune tab
1715
-
1716
- - **Locked-by-default gain sliders** (Rev42): unlock checkbox, auto-relocks
1717
- on tab exit.
1718
- - **±10 / ±1 fine-tune buttons** (Rev43) flanking each slider.
1719
- - **Orange "was" reference** shown when unlocked so the operator sees what
1720
- they are moving from.
1721
- - All `RangeSetting` config sliders from the pypilot catalog appear here
1722
- automatically (Rev45).
98
+ Bilingual README update. No code change.
1723
99
 
1724
- ### UI - Race tab (new, Rev35)
100
+ ## 2.5.0 — 2026-09-09 — Race helpers
1725
101
 
1726
- - T1/T2 countdown timer with audible beeps, screen flash, spoken seconds
1727
- ("10, 5, 4, 3, 2, 1, Start"). Runs in the background across tabs.
1728
- - SYNC rounds to the nearest minute mid-countdown; RESET returns to idle.
102
+ Tack countdown, post-tack stats, persistent tack log, Smart Pilot
103
+ "auto-brain" (auto profile by wind + gust strategy + auto-disengage
104
+ on lost authority). Info modal safety notice.
1729
105
 
1730
- ### UI - Setup tab
106
+ ## 2.4.1 — 2026-09-08 — TWS alt paths, watch focus cut, popup polish
1731
107
 
1732
- - **Remote `RESTART pypilot`** (Rev55): SSH into the TinyPilot using the
1733
- user + password stored in plugin config (via `ssh2` npm), runs
1734
- `sudo systemctl restart pypilot pypilot_web`, reconnects local socket.
1735
- Falls back to reconnect-only if SSH fails or password not set.
1736
- - **`_pluginConfigMerge` helper** (Rev55): GETs the full plugin config from
1737
- `/skServer/plugins/<id>/config`, overlays only the patched fields, POSTs
1738
- back - so saving Nudge or Absorb no longer wipes the SSH credentials
1739
- (or vice versa) via full-replace.
1740
- - Pause/Resume buttons removed from the UI (backend endpoints kept).
1741
- - Absorb-provider hint reworded, security note added for the plain-text
1742
- plugin config storage.
108
+ Support for alternative TWS paths, watch-focus reduction on the
109
+ Pi Zero W, redesigned rose colour palette, popup header + close
110
+ button consistency.
1743
111
 
1744
- ### UI - Paths & API tab
112
+ ## 2.4.0 — 2026-09-07 — Dynamic watches, session recorder, log capture, plain-language gain help
1745
113
 
1746
- - KIP mini-table trimmed to the current recommended surface (Simple Switch
1747
- + Autopilot API v2 Put). Legacy `.actions.engage/nudge/tack/*Int` paths
1748
- hidden from the UI (backend handlers still registered for backwards
1749
- compatibility).
1750
- - Horizontal-scroll wrapper on both tables so long SK paths remain readable
1751
- on portrait phones.
114
+ Dynamic watch focus (per Sean D'Epagnier's advice), navigation
115
+ session recorder + shared-advice loop, Pi Zero log capture over
116
+ SSH, plain-language help on every gain slider, alarm engine and
117
+ Doctor now speak the user's language.
1752
118
 
1753
- ### UI - Info tab (new, Rev58..Rev59)
119
+ ## 2.3.0 — 2026-08-31 — Chart hover, Tune fork, Setup chips
1754
120
 
1755
- - Safety notice, quick-start (10 steps), About, Acknowledgements
1756
- (Sean D'Epagnier, Jean-Marc at Navitop, Signal K community, Panaaj),
1757
- Apache 2.0 license attribution, GitHub issue links, coexistence rules
1758
- with `pypilot-autopilot-provider`.
1759
- - **Config export/import** (Rev59): Web Share API to email / WhatsApp /
1760
- Signal / AirDrop, `.json` download, clipboard copy, file / text import,
1761
- reset. Payload includes corners, wind-arrow flags, polar toggles, custom
1762
- paths. Never includes SSH credentials.
1763
- - Version + Rev header, link to the classic pypilot_web UI on the TinyPilot.
121
+ Chart hover-freeze, per-slider ↺ restore, first-change profile-fork
122
+ prompt, live status chips on every Setup tile, Doctor Hide / Discard
123
+ buttons that stay visible.
1764
124
 
1765
- ### i18n (Rev61)
125
+ ## 2.2.1 — 2026-08-24 — Screenshots on the NPM page
1766
126
 
1767
- - Full **English / Español / Deutsch / Français** with browser auto-detect
1768
- on first visit, override in Setup → Language.
1769
- - ~90 keys covering tabs, Setup, popup, Paths, Regatta, alerts, prompts,
1770
- confirms. EN + ES complete; DE + FR cover tabs + core Setup + calibration
1771
- and fall back to EN for the rest.
1772
- - Info tab uses `data-lang` sibling blocks (EN + ES fully written).
1773
- - `<html lang="…">` reflects the active language.
127
+ Docs-only: embed screenshots in the README so they render on the NPM
128
+ website.
1774
129
 
1775
- ### Layout / responsiveness (Rev49..Rev57)
130
+ ## 2.2.0 — 2026-08-24 — Intelligence layer over pypilot
1776
131
 
1777
- - Landscape: rose stretches to fill the left column; control column becomes
1778
- a `1fr 1fr auto` grid so tack + AP rows fill the height instead of
1779
- clustering at the top. Text auto-shrinks via `clamp(min, min(vw, vh), max)`.
1780
- - Portrait: nav bar and swipe hint always visible; corner tiles inside the
1781
- rose square; long paths get horizontal scroll wrappers.
1782
- - **Swipe navigation** (Rev43..Rev57) between tabs: 80px threshold, 2.2×
1783
- H/V ratio, first-move axis lock so scrolling never triggers a stray
1784
- tab flip. Excluded over unlocked sliders and `.paths-scroll` wrappers.
1785
- - Nav-bar auto-hide toggle via yellow triangle in the swipe-hint strip.
132
+ The release that turned the plugin from a UI into a **layer of
133
+ intelligence over pypilot**: Head-to-wind ("Aproado") pseudo-mode,
134
+ telemetry historian + Chart tab, Sensor Quality, Servo Health, Alarm
135
+ engine, Pre-departure Autopilot Check, Pypilot Doctor with profile
136
+ fork, Setup as a launcher grid, interactive SSH console, full
137
+ RangeSetting sliders in Calibration, first swap of Target + Wind
138
+ arrow shapes, full i18n audit across EN / ES / DE / FR.
1786
139
 
1787
- ### Backend (src/index.ts)
140
+ ## 2.1.0 — 2026-08-10 — Race + Info + config export
1788
141
 
1789
- - `PLUGIN_REVISION` bumped Rev32 → Rev61 (11 packaged Revs since 0.1.0).
1790
- - New `sshUser` + `sshPassword` plugin config fields (Rev55).
1791
- - New `POST /plugins/*/restart-pypilot` router using `ssh2` (Rev55).
1792
- - `/status` exposes `sshUser`, `sshPasswordSet` (never the password itself),
1793
- and the package `version` from `package.json` (Rev59).
1794
- - Wildcard subscription to `performance.*` for polar-performance keys +
1795
- `environment.water.temperature` (Rev58).
1796
- - Optimistic UI + retry helper in the frontend (Rev56); no backend change.
142
+ Race timer tab, Info tab with quick-start, config export / import
143
+ via Web Share, minor UI polish.
1797
144
 
1798
- ### Screenshots + logo (Rev61)
145
+ ## 2.0.4 — 2026-07-30 — Infinite reconnect
1799
146
 
1800
- - App Store screenshots in `public/screenshots/` (JPEG < 500 KB each,
1801
- 4 shots: Control / Tune / Info / Setup).
1802
- - Vector logo `public/Pypilot_New_UI.svg` (33 KB) wired as
1803
- `signalk.appIcon`.
1804
- - `signalk.screenshots[]` populated for the App Store card.
147
+ Socket reconnection attempts uncapped so long outages recover
148
+ without a plugin restart.
1805
149
 
1806
- ### Docs
150
+ ## 2.0.3 — 2026-07-28 — Access request flow
1807
151
 
1808
- - README rewritten: full feature list, coexistence table, remote restart,
1809
- updated endpoint table, acknowledgements, safety, reporting.
1810
- - App Store `description` in `package.json` replaced with a
1811
- user-facing one-paragraph blurb.
152
+ Signal K access-request flow reworked so admins approve the visor
153
+ once and it just works.
1812
154
 
1813
- ## 0.1.0 — 2026-07-21
155
+ ## 2.0.2 — 2026-07-25 — Wind corner display
1814
156
 
1815
- ### Rev1..Rev6 first-boot iteration on the Tunatunes Pi
157
+ Preference for AWA / TWA corner display; auto-follows AP mode when
158
+ unset.
1816
159
 
1817
- - Rev1 scaffold.
1818
- - Rev2 fix: factory function forgot to `return plugin` -> SK crashed with
1819
- `Cannot read properties of undefined (reading 'id')`.
1820
- - Rev3 fix: `publishMeta` sent `updates:[{meta:[...]}]` without `values`,
1821
- triggering `update.values is not iterable` in the SK delta iterator.
1822
- - Rev4 refactor: attach `meta` INLINE on the first `publishValue` of each
1823
- path so no delta ever carries an empty `values` array. Dropped the
1824
- separate `publishMeta` call.
1825
- - Rev5 diagnostic: catalog counts in `setPluginStatus`.
1826
- - Rev6 fix: pypilot exposes gains as `ap.pilot.<pilot>.<gain>` (SINGULAR),
1827
- not `ap.pilots.*` as the initial brief assumed. Confirmed by inspecting
1828
- `pypilot_values` on the socket.io wire. Corrected in publisher.ts,
1829
- index.ts (setupWatches + status), and public/app.js (renderGains).
160
+ ## 2.0.1 — 2026-07-23 — Rose colours
1830
161
 
1831
- ### Known limitation
162
+ Rose colour palette refined. Minor visual fixes.
1832
163
 
1833
- `signalk-pushover-plugin@0.0.6` emits two `Unhandled rejection: TypeError:
1834
- update.values is not iterable` at every SK restart, even before our plugin
1835
- publishes anything. The stack is pushover's own (`index.js:193:71`) and
1836
- does not reference our plugin. Ignore, or file upstream against
1837
- pushover-plugin.
164
+ ## 2.0.0 — 2026-07-20 — Signal K path rewrite (breaking)
1838
165
 
1839
- ### Backend
1840
- - src/pypilot-client.ts: socket.io client to `pypilot_web`, reconnect,
1841
- `pypilot_values` catalog discovery, watch/set primitives, ping/pong.
1842
- - src/publisher.ts: fixed + dynamic mapping table pypilot names to
1843
- `steering.autopilot.pypilot.*` (gains, servo, calibration, tack,
1844
- errors, warnings, runtime, version). Deg->rad and C->K conversions.
1845
- - src/scanner.ts: /24 LAN scan, TCP probe on ports 80 and 8000, HTTP
1846
- fingerprint on `<title>pypilot control</title>`.
1847
- - src/index.ts: plugin lifecycle + JSON schema + PUT handlers (with
1848
- reverse rad->deg on angle paths) + HTTP router (`/status`, `/paths`,
1849
- `/catalog`, `/scan`, `/raw`).
166
+ **Breaking release addressing feedback from Sean D'Epagnier**. Every
167
+ pypilot key now maps 1:1 to `steering.autopilot.pypilot.<key>`
168
+ verbatim — no more hand-picked renames or unit conversions.
169
+ Setup → Emergency ships the restart triad (`restart pypilot_web` /
170
+ `RESTART pypilot` / `reboot Pi`), each guarded by a helm-manned
171
+ confirmation modal. Debug console with SSH-based preset diagnostics.
1850
172
 
1851
- ### Reserved for pypilot-autopilot-provider (never republished)
1852
- `ap.enabled`, `ap.heading`, `ap.heading_command`, `ap.mode`. Both plugins
1853
- coexist.
173
+ ## 1.0.0 — 2026-06-12 — Initial release
1854
174
 
1855
- - Backend: socket.io client to `pypilot_web`, catalog discovery via
1856
- `pypilot_values`, watch subscriptions, delta publisher with
1857
- configurable per-path enable/disable.
1858
- - Paths: full `steering.autopilot.pypilot.*` tree (gains, servo,
1859
- calibration, tack, errors, warnings, runtime, version).
1860
- - HTTP: `/scan` LAN discovery, `/status`, `/paths` KIP-friendly catalog,
1861
- `/raw` protected write.
1862
- - Webapp: mobile-first dark, four tabs (Control, Ajustes, Paths & API,
1863
- Setup). Control tab fully wired against the Autopilot API v1 of
1864
- Signal K (engage / disengage / mode / target / tack / heading nudges);
1865
- Ajustes tab with translucent-while-adjusting sliders for gains and
1866
- calibration; Paths & API tab with live path list + copy-for-KIP;
1867
- Setup tab with LAN scan.
1868
- - Coexists with the official `pypilot-autopilot-provider` — this plugin
1869
- never publishes `steering.autopilot.state / mode / target / engaged`
1870
- (those stay with the provider).
175
+ First public release. Touch webapp, compass rose, Tune tab with
176
+ gain sliders, Setup with LAN scan and manual host, initial Signal K
177
+ path publisher.