From c15521437c66fa64d12530da06926421cb90c9bf Mon Sep 17 00:00:00 2001 From: James Grugett Date: Tue, 21 Apr 2026 17:29:17 -0700 Subject: [PATCH] Load queue depths on landing so picker doesn't flash "No wait" After returnToFreebuffLanding, the picker applied bare {status:'none'} with no queueDepthByModel and nextDelayMs returns null for 'none', so stale zeros showed until POST. Kick off a fire-and-forget GET in landing mode that extracts just queueDepthByModel (ignoring status, so a stale pre-DELETE row can't trip the takeover branch), and render blank hints instead of "No wait" while the snapshot is missing. Co-Authored-By: Claude Opus 4.7 --- .../components/freebuff-model-selector.tsx | 8 ++++-- cli/src/hooks/use-freebuff-session.ts | 28 +++++++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/cli/src/components/freebuff-model-selector.tsx b/cli/src/components/freebuff-model-selector.tsx index d4cb7b918..a33d89540 100644 --- a/cli/src/components/freebuff-model-selector.tsx +++ b/cli/src/components/freebuff-model-selector.tsx @@ -49,10 +49,14 @@ export const FreebuffModelSelector: React.FC = () => { // subtract. In-queue ('queued'): for the user's queue, "ahead" is // `position - 1` (themselves don't count); for every other queue, switching // would land them at the back, so it's that queue's full depth. Null before - // any snapshot so the UI doesn't flash misleading zeros. + // any snapshot so the UI doesn't flash misleading zeros — in particular, + // landing mode after a session ends initially sets status='none' with no + // queueDepthByModel; returning null here keeps the hint blank until the + // fetch lands, instead of showing "No wait" on every row. const aheadByModel = useMemo | null>(() => { if (session?.status === 'none') { - const depths = session.queueDepthByModel ?? {} + if (!session.queueDepthByModel) return null + const depths = session.queueDepthByModel const out: Record = {} for (const { id } of FREEBUFF_MODELS) out[id] = depths[id] ?? 0 return out diff --git a/cli/src/hooks/use-freebuff-session.ts b/cli/src/hooks/use-freebuff-session.ts index d590d7633..b5497e43d 100644 --- a/cli/src/hooks/use-freebuff-session.ts +++ b/cli/src/hooks/use-freebuff-session.ts @@ -438,13 +438,29 @@ export function useFreebuffSession(): UseFreebuffSessionResult { // doesn't bounce a 'landing' restart straight back to 'ended'. previousStatus = null if (mode === 'landing') { - // Land on the picker without a probe GET. If the preceding - // DELETE hasn't propagated, a GET here could still see - // queued/active and trip the startup-takeover branch below into - // an auto-POST — the exact silent-rejoin this mode exists to - // avoid. Polling resumes when the user commits to a model via - // joinFreebuffQueue. + // Land on the picker immediately. We can't go through the normal + // tick/apply path because a server-side row that hasn't been + // swept yet would trip the startup-takeover branch into an + // auto-POST — the exact silent-rejoin this mode exists to + // prevent. But the picker still needs live queue depths for its + // "N ahead" hints, so kick off a fire-and-forget GET and extract + // just queueDepthByModel from the response, ignoring whatever + // status it claims. Polling resumes when the user commits to a + // model via joinFreebuffQueue. apply({ status: 'none' }) + const fetchController = abortController + callSession('GET', token, { signal: fetchController.signal }) + .then((response) => { + if (cancelled || fetchController.signal.aborted) return + const depths = + response.status === 'none' || response.status === 'queued' + ? response.queueDepthByModel + : undefined + if (depths) apply({ status: 'none', queueDepthByModel: depths }) + }) + .catch(() => { + // Silent — blank hints are acceptable if the fetch fails. + }) return } nextMethod = 'POST'