From e5c9ca1a7da0dc87d8d703c92f5f0b121be14f63 Mon Sep 17 00:00:00 2001 From: Gregory Salaun Date: Mon, 17 Aug 2026 10:32:06 +0200 Subject: [PATCH] fix(rotator): the compass shows the heading it already knows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Opening Station Control left the compass blank for a second or two while the amplifiers, relay boards and power supply filled at once. Nothing was slow. The status bar keeps the shared heading loop running, so when the compass mounts there is already a tick pending — up to the full three-second idle interval away — and the new subscriber simply waited out the rest of it. The heading was known the whole time and had nowhere to be read from. The last heading is now kept and handed to whoever subscribes next. That matters more on the Alpha SPID this was reported on than it would elsewhere: every poll is an open, a read at 600 baud and a close, so even fetching immediately on mount would not have been immediate. In a microtask, so a subscriber is never called back before subscribeRotorHeading has returned to it. --- changelog.json | 6 ++++-- frontend/src/lib/rotorHeading.ts | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/changelog.json b/changelog.json index 3cf280c..ebf8bd4 100644 --- a/changelog.json +++ b/changelog.json @@ -8,7 +8,8 @@ "Lookup cache: a TTL of 0 switches it off, so a callbook record you are correcting is re-read every time.", "TCI radios: when the rig forbids transmitting, PTT says so instead of doing nothing silently.", "WAJA carried Japan’s civil prefecture numbers instead of the JARL’s: 35 of the 47 references are renumbered.", - "Award references can be renumbered in the editor — the number was the one field it would not let you correct." + "Award references can be renumbered in the editor — the number was the one field it would not let you correct.", + "The compass fills the moment Station Control opens, instead of waiting out the rest of a polling interval." ], "fr": [ "Une entité qui est un seul groupe d’îles remplit désormais la référence IOTA toute seule, sans abonnement callbook.", @@ -16,7 +17,8 @@ "Cache des recherches : un TTL à 0 le désactive, pour relire à chaque fois une fiche callbook en cours de correction.", "Radios TCI : quand la radio interdit l’émission, le PTT le dit au lieu de ne rien faire en silence.", "WAJA portait les numéros civils des préfectures japonaises et non ceux de la JARL : 35 des 47 références sont renumérotées.", - "Les références d’un diplôme se renumérotent dans l’éditeur : le numéro était le seul champ qu’il refusait de corriger." + "Les références d’un diplôme se renumérotent dans l’éditeur : le numéro était le seul champ qu’il refusait de corriger.", + "La boussole se remplit dès l’ouverture de Station Control, au lieu d’attendre la fin d’un intervalle d’interrogation." ] }, { diff --git a/frontend/src/lib/rotorHeading.ts b/frontend/src/lib/rotorHeading.ts index 2f2aac1..19aad25 100644 --- a/frontend/src/lib/rotorHeading.ts +++ b/frontend/src/lib/rotorHeading.ts @@ -36,6 +36,14 @@ let timer: number | undefined; let inFlight = false; let lastAz: number | null = null; let lastMoveAt = 0; +// The last heading anyone received, replayed to whoever subscribes next. +// +// Without it, opening Station Control left the compass blank for one or two +// seconds while every other panel filled at once. Nothing was slow: the status +// bar already had the loop running with a tick pending, so a component mounting +// halfway through an idle interval simply waited out the rest of it. The +// heading was known the whole time — it just had nowhere to be read from. +let last: RotorHeading | null = null; function schedule(delay: number) { if (timer !== undefined) window.clearTimeout(timer); @@ -54,6 +62,7 @@ async function tick() { if (lastAz !== null && h.azimuth !== lastAz) lastMoveAt = Date.now(); lastAz = h.azimuth; } + last = h; subs.forEach((fn) => { try { fn(h); } catch { /* a subscriber must not stop the loop */ } }); } catch { // Leave the last heading alone: a single failed poll on a shared serial port @@ -69,6 +78,12 @@ async function tick() { // unsubscribe. The loop stops when the last subscriber leaves. export function subscribeRotorHeading(fn: (h: RotorHeading) => void): () => void { subs.add(fn); + // Hand over what is already known, at once. An Alpha SPID poll is an open, + // a read at 600 baud and a close, so even an immediate one takes a moment — + // the cached heading is what makes the compass appear with the panel rather + // than after it. In a microtask, so a subscriber is never called back before + // subscribeRotorHeading has returned to it. + if (last) { const h = last; queueMicrotask(() => { if (subs.has(fn)) fn(h); }); } if (timer === undefined && !inFlight) void tick(); return () => { subs.delete(fn);