From 0b5f7d586912d469351f5d471d9e170895a9dc1e Mon Sep 17 00:00:00 2001 From: ciotlosm Date: Fri, 7 Aug 2026 18:43:32 +0300 Subject: [PATCH] fix(station-boards): push last snapshot on subscribe when one exists Symptom: switching views (station -> home, or any other view that uses createStationBoardsController) showed scheduled-only vehicles for up to one poll cycle (15 s today) before live GPS buses reappeared, even when data was already flowing on the previous view. Root cause: subscribeStationBoards explicitly skipped the late-subscribe catch-up that the previous implementation had (`void pushOne(sub, getReconciledSnapshot())`). PR #454 removed it in 2026-07 to fix an incoming-first flicker in StationCard groups on cold start. That reasoning was correct for the FIRST-EVER subscription (before tickLive has run, getReconciledSnapshot returns null, and pushing scheduled-only vehicles does cause a flicker). It was wrong for every subsequent subscription, where the snapshot exists, has merged GPS data, and is what the stream is currently broadcasting. Fix: push the last good snapshot on subscribe, but only when getReconciledSnapshot() returns non-null. The cold-start path (snapshot === null) is unchanged, so the PR #454 flicker fix is preserved. The new view now paints the same merged-with-GPS data the previous view was showing, within a microtask of subscribing. The next tickLive (within livePollMs) replaces it with a fresher snapshot. --- src/lib/workers/gtfs/stationSubscribers.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/lib/workers/gtfs/stationSubscribers.ts b/src/lib/workers/gtfs/stationSubscribers.ts index 96cca8cf..6a2d4f89 100644 --- a/src/lib/workers/gtfs/stationSubscribers.ts +++ b/src/lib/workers/gtfs/stationSubscribers.ts @@ -43,12 +43,22 @@ export async function subscribeStationBoards( const key = Symbol(); const sub: StationSub = { stopIds: new Set(initialStopIds), cb }; subscribers.set(key, sub); - // NOTE: no immediate catch-up push here. Every `tickLive` call - // broadcasts to all station subscribers via `pushAllStationSubscribers`, - // so new subscribers automatically receive the next poll's merged - // data. Pushing immediately with `getReconciledSnapshot()` before the - // first tick would send scheduled-only vehicles (no GPS) and cause - // a flicker in StationCard groups before the merged push arrives. + // Late-subscribe catch-up. If the worker already has a merged + // snapshot (the user is navigating between views while data is + // flowing — e.g. station view -> home, then home -> station + // again), hand the new subscriber the same merged-with-GPS data + // the stream is currently showing. Without this, the new view + // renders scheduled-only vehicles for up to one poll cycle + // (15 s today) before the next tickLive broadcast arrives. + // + // The first-ever subscription, before the first tick has run, + // has `getReconciledSnapshot() === null`; the catch-up is + // skipped for that case so the new subscriber doesn't paint + // with an empty snapshot and then re-paint with the merged one + // (the regression that PR #454 closed — see that PR for the + // incoming-first flicker this guards against). + const snap = getReconciledSnapshot(); + if (snap) void pushOne(sub, snap); // Proxy the whole handle so Comlink keeps it as a remote reference. // Per-method Comlink.proxy() doesn't help here — Comlink only checks // the proxy marker at the TOP of the returned value, so a plain