feat(race): notification tiers, drift correction, and T=0 retry (#348)

## Problem

Race countdown had three reliability issues:
- Notified every second regardless of remaining time (noisy for
hours-long waits)
- Never re-fetched start times (stale if contest rescheduled)
- No retry on setup failure at T=0 — if the scraper failed at the most
critical moment, the entire countdown was wasted

## Solution

- **Notification tiers**: >1h every 15m, >5m every 1m, >1m every 10s,
≤60s every 1s
- **Drift correction**: re-fetch contest list every 10 minutes, update
`start_time` if changed
- **T=0 retry**: `race_try_setup` calls `scrape_contest_metadata` with
the new `on_error` callback, retrying up to 15 times at 3s intervals
(~45s). Caches metadata on success, then calls `setup_contest` on the
fast (cached) path. Token guard invalidates stale retries after
cancellation.
- **`on_error` for `scrape_contest_metadata`**: optional 4th param,
backward-compatible
This commit is contained in:
Barrett Ruth 2026-03-06 18:43:12 -05:00 committed by GitHub
parent bd0ae25d4b
commit 8398f3428e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 138 additions and 10 deletions

View file

@ -216,7 +216,7 @@ local function run_scraper(platform, subcommand, args, opts)
end
end
function M.scrape_contest_metadata(platform, contest_id, callback)
function M.scrape_contest_metadata(platform, contest_id, callback, on_error)
run_scraper(platform, 'metadata', { contest_id }, {
on_exit = function(result)
if not result or not result.success then
@ -227,6 +227,9 @@ function M.scrape_contest_metadata(platform, contest_id, callback)
),
{ level = vim.log.levels.ERROR }
)
if type(on_error) == 'function' then
on_error()
end
return
end
local data = result.data or {}
@ -238,6 +241,9 @@ function M.scrape_contest_metadata(platform, contest_id, callback)
),
{ level = vim.log.levels.ERROR }
)
if type(on_error) == 'function' then
on_error()
end
return
end
if type(callback) == 'function' then