feat: add 10 UX improvements from interface review
CI / Lint (push) Failing after 10s
CI / Test (push) Has been skipped
CI / Build & Push (push) Has been skipped

- Stale data banner after 3 consecutive fetch failures, auto-clears on recovery
- Date navigation with left/right arrows (Yesterday/Today/Tomorrow labels),
  fetches from NHL API for non-today dates, disables auto-refresh on history
- Empty state message when no games are scheduled
- Series detail page auto-refreshes every 30s when a game is live
- Notification permission deferred until a playoff OT actually occurs
- Scroll position saved/restored when navigating to/from series detail
- Team records rendered with better contrast and tabular nums
- Active bracket round highlighted with gold heading + underline,
  completed rounds dimmed more aggressively, mobile accordion auto-opens
  current round
- Browser tab title shows live game count (e.g. "NHL Scoreboard (3 Live)")
- Service worker update shows a dismissable toast instead of force-reloading

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-23 20:22:03 -04:00
parent 58b27ddd20
commit 2da60e27ae
8 changed files with 313 additions and 39 deletions
+36 -10
View File
@@ -1,6 +1,8 @@
import json
import logging
from flask import abort, make_response, render_template, jsonify, send_from_directory
import requests as http_requests
from flask import abort, make_response, render_template, jsonify, request, send_from_directory
from app import APP_VERSION, app, static_v
from app.config import SCOREBOARD_DATA_FILE
@@ -19,6 +21,18 @@ from datetime import datetime
from zoneinfo import ZoneInfo
_EASTERN = ZoneInfo("America/New_York")
_logger = logging.getLogger(__name__)
def _fetch_date(date_str):
url = f"https://api-web.nhle.com/v1/score/{date_str}"
try:
resp = http_requests.get(url, timeout=10)
resp.raise_for_status()
return resp.json()
except http_requests.RequestException as e:
_logger.error("Failed to fetch scores for %s: %s", date_str, e)
return None
def _max_playoff_round(raw_games):
@@ -69,15 +83,27 @@ def index():
@app.route("/scoreboard")
def get_scoreboard():
try:
with open(SCOREBOARD_DATA_FILE, "r") as json_file:
scoreboard_data = json.load(json_file)
except FileNotFoundError:
return jsonify({"error": "Failed to retrieve scoreboard data. File not found."})
except json.JSONDecodeError:
return jsonify(
{"error": "Failed to retrieve scoreboard data. Invalid JSON format."}
)
date_param = request.args.get("date")
today_str = datetime.now(_EASTERN).strftime("%Y-%m-%d")
if date_param and date_param != today_str:
try:
datetime.strptime(date_param, "%Y-%m-%d")
except ValueError:
return jsonify({"error": "Invalid date format. Use YYYY-MM-DD."}), 400
scoreboard_data = _fetch_date(date_param)
if not scoreboard_data:
return jsonify({"error": "Failed to retrieve scoreboard data for that date."})
else:
try:
with open(SCOREBOARD_DATA_FILE, "r") as json_file:
scoreboard_data = json.load(json_file)
except FileNotFoundError:
return jsonify({"error": "Failed to retrieve scoreboard data. File not found."})
except json.JSONDecodeError:
return jsonify(
{"error": "Failed to retrieve scoreboard data. Invalid JSON format."}
)
if scoreboard_data:
raw_games = scoreboard_data.get("games", [])