feat: worldcup-live-feed - Show match info via inbox#12
Conversation
…to a status object Drives status (working/on/alert), info.name (abbreviated on-map summary), and the activity feed (live scores, goals, finished results, next-match countdown).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- guard delayed alert revert so a match ending mid-alert doesn't force 'on' over idle - emit one goal entry per goal (both sides / multiple goals per poll) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| await dispatchActivity( | ||
| `goal-${match.id}-away-${n}`, | ||
| goalEntryText(match, "away"), | ||
| ); |
There was a problem hiding this comment.
Batch goal entries wrong score
Medium Severity
When more than one goal is detected between polls, each new goal activity line is built with the match’s current total score, not the score after that specific goal. Earlier goal entries in the same batch can show a final tally that had not happened yet at that “goal moment.”
Reviewed by Cursor Bugbot for commit 41a1527. Configure here.
| data: { state: "on" }, | ||
| }) | ||
| .catch(() => {}); | ||
| }, alertMs); |
There was a problem hiding this comment.
Stacked alert timers revert early
Medium Severity
Each detected goal schedules its own --alert-seconds timer to return status to on, without cancelling prior timers. An older timer can fire after a newer goal and set on while the latest goal’s alert period should still be active.
Reviewed by Cursor Bugbot for commit 41a1527. Configure here.
…E, filter client-side only
| `/competitions/WC/matches?dateFrom=${today}&dateTo=${today}`, | ||
| apiToken, | ||
| ); | ||
| return json.matches.filter((m: Match) => LIVE_STATUSES.has(m.status)); |
There was a problem hiding this comment.
Live match lost at midnight
High Severity
fetchLiveMatches only loads WC fixtures for the current UTC calendar day. A match still IN_PLAY after UTC midnight is tied to its kickoff date, so it disappears from the live query, is treated as finished, and the object can flip to idle with a premature full-time entry while the game is still running.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit be60f88. Configure here.
The workspace @webhook-objects/client is being removed in favor of the npm-published SDK (see #15): createWebhookObjectClient replaces new Client, send takes (type, data) and stamps the timestamp itself, and ping() replaces requestMetadata(). The @gathertown packages join the minimumReleaseAge exclusions since they are first-party. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 5 potential issues.
There are 8 total unresolved issues (including 3 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 097f778. Configure here.
| .slice(0, 10); | ||
| const json = await footballDataFetch( | ||
| `/competitions/WC/matches?status=SCHEDULED&dateFrom=${today}&dateTo=${dateTo}`, | ||
| apiToken, |
There was a problem hiding this comment.
Upcoming filter misses TIMED matches
High Severity
fetchUpcomingMatches filters with status=SCHEDULED only. On football-data.org, fixtures with a known kickoff are usually TIMED, a separate status from SCHEDULED. The idle "Next match" countdown will often see an empty list and fall back to No live World Cup matches right now even when games are scheduled.
Reviewed by Cursor Bugbot for commit 097f778. Configure here.
| await dispatchActivity( | ||
| `result-${id}`, | ||
| `🏆 FT: ${state.homeTeam} ${state.home}-${state.away} ${state.awayTeam}`, | ||
| ); |
There was a problem hiding this comment.
Finished score uses stale match state
Medium Severity
When a match leaves the live set, the FT activity entry is built from lastMatchState instead of the API's finished result. Any goal between the last successful live poll and full-time is missed, so the FT line can show the wrong score until the next day's results batch (if it differs).
Reviewed by Cursor Bugbot for commit 097f778. Configure here.
| `🏆 FT: ${state.homeTeam} ${state.home}-${state.away} ${state.awayTeam}`, | ||
| ); | ||
| lastMatchState.delete(id); | ||
| } |
There was a problem hiding this comment.
Stale LIVE entries survive restarts
Medium Severity
lastMatchState starts empty on every process boot, while same-day FT conversion only runs for ids still in that map. If a match ends while the feed is down, a restart leaves the old 🔴 LIVE activity row in place and never posts the corresponding result-* entry until the next day's batch.
Reviewed by Cursor Bugbot for commit 097f778. Configure here.
| if (knownEntryText.get(id) === text) return; | ||
| await client.send("activity.add", { id, text }); | ||
| knownEntryText.set(id, text); | ||
| }; |
There was a problem hiding this comment.
Dedup cache ignores buffer eviction
Medium Severity
knownEntryText only grows and never tracks the activity capability's ring buffer (ACTIVITY_BUFFER_SIZE is 20). After enough new goal or result ids arrive, the server drops older rows, but the local cache still treats them as present, so unchanged LIVE/FT text is not re-added and can vanish from the feed mid-match.
Reviewed by Cursor Bugbot for commit 097f778. Configure here.
| try { | ||
| await updatePreviousDayResults(); | ||
| console.log("polling football-data.org..."); | ||
| const liveMatches = await fetchLiveMatches(footballToken); |
There was a problem hiding this comment.
Results fetch failure blocks live polls
Medium Severity
Each poll awaits updatePreviousDayResults() before fetchLiveMatches. Any failure in the once-per-day finished-match lookup or its dispatches is caught by the outer poll handler and skips the live-match path entirely, so status, goals, and live entries stop updating until that call succeeds.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 097f778. Configure here.


Tracking PR
Note
Low Risk
Self-contained new package and lockfile/workspace config only; no changes to existing production services or auth/data paths.
Overview
Adds
@webhook-objects/worldcup-live-feed, a long-running CLI that polls football-data.org for World Cup (WC) fixtures and updates astatuspreset webhook object via@gathertown/webhook-object-sdk.The service maps match state to
status.set(working/on/ briefalerton goals),info.setfor on-map labels, and an activity feed (live lines, per-goal entries, idle next-match countdown, FT results). It includes a small football-data.org v4 client (live detection without broken?status=LIVEon competition endpoints, 429 retry), pure formatting helpers with Vitest coverage, and deduped activity updates seeded fromwebhook.pingso restarts do not reshuffle feed ordering.Workspace wiring adds the package to
pnpm-lock.yamland exempts the Gather webhook SDK packages fromminimumReleaseAgeinpnpm-workspace.yaml.Reviewed by Cursor Bugbot for commit 097f778. Bugbot is set up for automated code reviews on this repo. Configure here.