/** * Pure formatting helpers for the admission queue UI (spec `docs/QUEUE_SPECIFICATION.md` §7.2, §7.3). * Deliberately framework- and DOM-free so they can be unit tested without a browser; the DOM * wiring and polling state machine live in app.js. */ export const QUEUE_POLL_INTERVAL_MS = 2000; /** * English ordinal suffix for a 1-based rank: 1st, 2nd, 3rd, 4th, 11th-13th, 21st, ... * @param {number} n * @returns {string} */ export function ordinal(n) { const rem100 = n % 100; if (rem100 >= 11 && rem100 <= 13) return `${n}th`; switch (n % 10) { case 1: return `${n}st`; case 2: return `${n}nd`; case 3: return `${n}rd`; default: return `${n}th`; } } /** * Formats the estimate clause: "estimating…" when unknown, "under a minute" below 60s, * otherwise "about N min" rounded up (spec §5, §7.2). * @param {number|null|undefined} etaSeconds * @returns {string} */ export function formatEta(etaSeconds) { if (etaSeconds === null || etaSeconds === undefined) return 'estimating…'; if (etaSeconds < 60) return 'under a minute'; const minutes = Math.ceil(etaSeconds / 60); return `about ${minutes} min`; } /** * Formats the queued sub-state status line (spec §7.2 table, `queued` row). * Position 1 reads as "You're next" instead of "You're 1st in line". * @param {{ position: number, eta_seconds: number|null }} status * @returns {string} */ export function formatQueuedStatus({ position, eta_seconds: etaSeconds }) { const place = position === 1 ? "You're next" : `You're ${ordinal(position)} in line`; return `${place} · ${formatEta(etaSeconds)}`; } /** * Formats the "Exit queue" button's visible label based on time waited since the join * response (spec §7.3). The button's `aria-label` stays "Leave the queue" regardless. * @param {number} waitedMs * @returns {string} */ export function formatExitQueueLabel(waitedMs) { const waitedMinutes = waitedMs / 60000; if (waitedMinutes < 1) return 'I will try next time'; if (waitedMinutes <= 3) return "Ohhh, it's so long"; return "I'm dying in this queue"; }