world-2026-hub/.agents/issues.md

7 KiB
Raw Permalink Blame History

Issues & Optimization Candidates

Tracked optimization proposals and known issues. Analyzed but not yet implemented.


Event-Driven Scheduling for Match State Transitions (2026-06-15)

Status: Analyzed, deferred (no implementation yet)

Issue

Latency in the Matches tab when a match transitions to "over" state. Currently, the system uses polling (OCC_TICK_MS = 60s) to check if any match has entered "over" status (either via JSON status==='finished' or clock reaching kickoff + window). This causes up to 60 seconds of delay between the actual state change and the UI update (the "Pendente de resultado" chip appearing on a match card).

Proposed Solution

Implement event-driven scheduling instead of polling:

  • Calculate exact timestamps when each match will transition states (kickoff → "live", kickoff + window → "over")
  • Use setTimeout to schedule precise callbacks for these moments
  • Render the list only when a timeout fires
  • Revalidate/reschedule timeouts when getData() updates (daily refresh)

Benefits

  • Latency: Reduced from up to 60s to ~0s
  • Efficiency: Zero CPU wasted on unnecessary checks between state changes
  • Deterministic: Transition moments are calculable with precision

Technical Feasibility

Viable. The matchState() function already computes state based on kickoff and window, so timestamps are known. Logic to manage ~200 timeouts (104 matches × 2 transitions) is straightforward but requires cleanup/reschedule logic on getData() updates.

Why Not Implemented Yet (Cost-Benefit Analysis)

Complexity vs. impact trade-off: The improvement is real but limited:

  1. Limited real-world UX impact

    • The "match over but JSON not updated" state is transitory (~minutes), lasting only until the daily manual refresh lands
    • Most users either watch the hero (which updates every 1s and already flips to the next match instantly) or check the Matches tab after a refresh
    • Polling at 60s is already so infrequent (0.017 Hz) that CPU cost is negligible
  2. Moderate implementation cost

    • Managing 200+ live timeouts and cleaning up old ones on data refresh adds complexity
    • Must handle race conditions: JSON update and timeout firing simultaneously
    • Adds another system to maintain/debug
  3. Narrow use case

    • Would matter if thousands of simultaneous matches existed, or if users commonly left the Matches tab open for hours
    • Current tournament is 72 group matches + 32 knockout matches (104 total); no real-time data updates (daily manual refresh)

When to Implement

Only if:

  • Latency in the Matches tab becomes a reported UX complaint
  • The tournament adds real-time data feeds (WebSocket/API polling) instead of manual daily refresh
  • Similar polling patterns accumulate elsewhere and warrant a systematic refactor

How to Implement (if revisited)

  1. Create scheduleMatchStateChanges() in schedule.js
  2. For each match, calculate kickoffTime and kickoffTime + matchWindowMs(match)
  3. Schedule setTimeout callbacks for both transitions
  4. On getData() refetch, cancel old timeouts and reschedule
  5. Callback directly fires renderList()
  6. Guard against duplicate timers (similar to startHeroClock pattern in app.js)

PWA Tier 2 — Service Worker + Offline (2026-06-16)

Status: Analyzed, deferred (Tier 1 shipped 2026-06-16 — see project-memory "PWA — installable app").

Context

The PWA install issue was delivered as Tier 1 (manifest + icons + meta tags), which already meets every acceptance criterion (installable, correct name/icon, standalone launch from the OS shortcut, no app-pipeline risk). Tier 2 — a service worker for offline launch and the strongest cross-browser "app feel" — was intentionally left out. It is not required for the install prompt in modern Chrome/Edge.

Why deferred (the real risk)

A naïve precaching SW would cache data/*.json and silently defeat the 2026-06-16 live-refresh system (the 90s results.json poll with cache:'no-store', plus the ?t=Date.now() cache-buster on every data fetch) — open tabs would stop seeing new scores. It would also make the "stale JS module" gotcha (#5) permanent (cached assets live until the cache name changes).

How to implement (if revisited) — constraints, not optional

  1. Never cache data/*.json. Use network-only, or network-first with the cache only as an offline fallback (so an offline launch shows the last-seen results). The 90s poll must stay the owner of freshness.
  2. Version the SW cache with a dedicated cache-name constant bumped on each deploy (there is no longer a DATA_VERSION to mirror — data freshness is handled by ?t=Date.now()); clean up old caches on activate — otherwise every code deploy risks serving stale JS forever (gotcha #5).
  3. Register at the subpath (worldcup2026/sw.js) so the SW scope matches the deploy (gotcha #7); keep start_url/scope relative as they already are.
  4. App-shell strategy: cache-first (versioned) for index.html + assets/css + assets/js + assets/icons; precache on install.
  5. Verify the poll still updates an open tab with the SW active (the easy thing to regress).

When to implement

Only if offline launch / a fuller install experience is actually wanted, and only with the data-cache exclusion + cache-versioning above. Otherwise Tier 1 is sufficient.


Live Data Refresh — Stale Results Until Page Reload (2026-06-15)

Status: Implemented 2026-06-16 (Option A⁺ — "Fixed polling done right"). Full implementation (functions, files, the 3 reinforcements, the bracket-config.json piggyback, verification) is documented in project-memory.mdArchitecture & Decisions → "Live data refresh — poll results.json without F5".

One-line problem it solved: an open tab loaded data/*.json once and never refetched, so a newly published results.json (daily push) only appeared after F5.

Key decisions worth keeping (rationale):

  • Not a live-feed problem. results.json is a manual post-match push, so there is no new server data during a match — a faster "during live" poll buys nothing. A fixed 90s poll is correct; dynamic/state-based polling was rejected (complexity for no gain, double-schedule risk per gotcha #6).
  • Cache-busting must use ?t=${Date.now()} + cache:'no-store' — a frozen per-tab constant would never refetch and Hostinger sends no cache headers. (As of 2026-06-18 the initial loadData() fetch also uses ?t=Date.now(); the old ?v=DATA_VERSION constant was removed.)
  • Signature = full response text, not a finished-count (a count misses score corrections, stats backfill on an already-finished match, and added penalties).
  • thirdPlaceAssignment lives in bracket-config.json, not results.json → on a detected change the poll refetches the config in the same cycle (it ships in the same daily push), avoiding a stale in-memory bracketConfig without per-tick config polling.