Skip to content

feat: worldcup-live-feed - Show match info via inbox#12

Open
bgreenier wants to merge 5 commits into
mainfrom
worldcup-town
Open

feat: worldcup-live-feed - Show match info via inbox#12
bgreenier wants to merge 5 commits into
mainfrom
worldcup-town

Conversation

@bgreenier

@bgreenier bgreenier commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

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 a status preset webhook object via @gathertown/webhook-object-sdk.

The service maps match state to status.set (working / on / brief alert on goals), info.set for 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=LIVE on competition endpoints, 429 retry), pure formatting helpers with Vitest coverage, and deduped activity updates seeded from webhook.ping so restarts do not reshuffle feed ordering.

Workspace wiring adds the package to pnpm-lock.yaml and exempts the Gather webhook SDK packages from minimumReleaseAge in pnpm-workspace.yaml.

Reviewed by Cursor Bugbot for commit 097f778. Bugbot is set up for automated code reviews on this repo. Configure here.

…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).
@bgreenier bgreenier changed the title feat: @webhook-objects/worldcup-live-feed feat: worldcup-live-feed - Show match info via inbox Jul 2, 2026
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread packages/worldcup-live-feed/src/index.ts
Comment thread packages/worldcup-live-feed/src/index.ts
- 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"),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.”

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 41a1527. Configure here.

data: { state: "on" },
})
.catch(() => {});
}, alertMs);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 41a1527. Configure here.

`/competitions/WC/matches?dateFrom=${today}&dateTo=${today}`,
apiToken,
);
return json.matches.filter((m: Match) => LIVE_STATUSES.has(m.status));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

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>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Fix All in Cursor

❌ 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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 097f778. Configure here.

await dispatchActivity(
`result-${id}`,
`🏆 FT: ${state.homeTeam} ${state.home}-${state.away} ${state.awayTeam}`,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 097f778. Configure here.

`🏆 FT: ${state.homeTeam} ${state.home}-${state.away} ${state.awayTeam}`,
);
lastMatchState.delete(id);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

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);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 097f778. Configure here.

try {
await updatePreviousDayResults();
console.log("polling football-data.org...");
const liveMatches = await fetchLiveMatches(footballToken);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 097f778. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants