feat: players page redesign — deltas, KPI tiles, sparklines, expanded row #9

Merged
shcizo merged 27 commits from feat/shared-visual-layer-topbar-4 into main 2026-05-21 16:14:01 +02:00
Owner

Summary

Bundles the players-page redesign on top of the shared visual layer + topbar foundation.

Issues closed: closes #3, closes #4, closes #5, closes #6, closes #7

What's in this PR

  • #4 — shared visual layer + redesigned topbar (foundation work). New token system (--paper, --ink/2/3, --accent, --up/-soft, --down/-soft, etc.) bridging legacy aliases. Sticky topbar with brand, segmented nav, meta (Next update / Last refresh), Refresh-all button.
  • #3 — Δ-column clarification. Split the old "Change" column into two semantically distinct pills: Δ month (real change since last PDGA update, in rating cell) and Δ predicted (gap to next predicted rating, in predicted cell). Headers get small hint text.
  • #5 — Four KPI summary tiles above the table (Tracked / Avg rating / Climbing / Slipping). 4-col desktop → 2-col under 880px. Climbing/Slipping have green/red left-rails.
  • #6 — Inline 96×28 SVG sparkline per row from a new monthlyHistory field (12 most-recent months, oldest→newest). Toolbar pill "Trend chart" toggles all sparklines on/off, persisted in localStorage('ratingtracker.sparklines').
  • #7 — Redesigned expanded row: 240px 1fr grid with detail-grid (Current / Last month / Δ month / Predicted / Δ predicted) on the left and a redesigned 880×240 SVG history chart on the right. Only one row open at a time. Keyboard support (tabindex="0" + Enter/Space). 3px accent stripe on the open row's left edge. expandIn animation.
  • Cleanup — removed orphaned public/js/progress.js (dead since the topbar redesign).

Notes

  • Data contract introduced across the boundary: each player object now carries lastMonthRating, deltaPredicted, and monthlyHistory: number[]. The / page route also passes a kpis local. Both implemented as pure derivations / aggregates — no new DB columns.
  • The Δ-pill markup is centralized in views/partials/delta-pill.ejs (used at 4 EJS sites) and a renderDeltaPill() helper in public/js/players.js (used by refreshPlayer for in-place DOM updates).
  • The history chart in public/js/chart.js was rewritten to use the SVG DOM API instead of innerHTML, matching the design spec exactly (4 dashed grid ticks, 8% area fill, 2px accent stroke, 3px dots, 4px+halo last point, 5 evenly-spaced "MMM YY" x-labels).

Test plan

Browser-only — no test infra in the repo. Run npm start (or docker compose up) and open http://localhost:3000/.

  • KPI strip renders above the table with 4 tiles. Tracked/Avg/Climbing/Slipping show plausible numbers. Resize browser below 880px → tiles collapse to 2 columns.
  • Δ-month pill under each rating value (mint for positive, peach for negative, grey for zero/null). Header reads "Δ month" with "since last update" hint.
  • Δ-predicted pill under each predicted value. Header reads "Predicted" with "gap from today" or similar hint.
  • Sparklines render inline in each row's rating cell (96×28 SVG, accent line + 10% area fill + dot on last point).
  • Trend-chart pill in the table toolbar toggles all sparklines on/off. Reload preserves the state. aria-pressed flips correctly.
  • Click a row → expanded panel opens with the new detail-grid + chart. Chart shows dashed grid lines, 8% area fill, dots, halo on the last point, "MMM YY" x-labels.
  • Open a second row → first row closes automatically. Only one row open at a time.
  • Tab to a row + Enter or Space also toggles. Space does not scroll the page.
  • Refresh-all button in the topbar still works — table refreshes, topbar shows new "Last refresh" time.
  • Per-player refresh icons (rating cell, predicted cell) still work without triggering row expansion.
  • Tooltips on rating/predicted values still show std-dev info on hover.
## Summary Bundles the players-page redesign on top of the shared visual layer + topbar foundation. **Issues closed**: closes #3, closes #4, closes #5, closes #6, closes #7 ### What's in this PR - **#4** — shared visual layer + redesigned topbar (foundation work). New token system (`--paper`, `--ink/2/3`, `--accent`, `--up/-soft`, `--down/-soft`, etc.) bridging legacy aliases. Sticky topbar with brand, segmented nav, meta (Next update / Last refresh), Refresh-all button. - **#3** — Δ-column clarification. Split the old "Change" column into two semantically distinct pills: `Δ month` (real change since last PDGA update, in rating cell) and `Δ predicted` (gap to next predicted rating, in predicted cell). Headers get small hint text. - **#5** — Four KPI summary tiles above the table (Tracked / Avg rating / Climbing / Slipping). 4-col desktop → 2-col under 880px. Climbing/Slipping have green/red left-rails. - **#6** — Inline 96×28 SVG sparkline per row from a new `monthlyHistory` field (12 most-recent months, oldest→newest). Toolbar pill "Trend chart" toggles all sparklines on/off, persisted in `localStorage('ratingtracker.sparklines')`. - **#7** — Redesigned expanded row: `240px 1fr` grid with detail-grid (Current / Last month / Δ month / Predicted / Δ predicted) on the left and a redesigned 880×240 SVG history chart on the right. Only one row open at a time. Keyboard support (`tabindex="0"` + Enter/Space). 3px accent stripe on the open row's left edge. `expandIn` animation. - **Cleanup** — removed orphaned `public/js/progress.js` (dead since the topbar redesign). ### Notes - Data contract introduced across the boundary: each player object now carries `lastMonthRating`, `deltaPredicted`, and `monthlyHistory: number[]`. The `/` page route also passes a `kpis` local. Both implemented as pure derivations / aggregates — no new DB columns. - The Δ-pill markup is centralized in `views/partials/delta-pill.ejs` (used at 4 EJS sites) and a `renderDeltaPill()` helper in `public/js/players.js` (used by `refreshPlayer` for in-place DOM updates). - The history chart in `public/js/chart.js` was rewritten to use the SVG DOM API instead of `innerHTML`, matching the design spec exactly (4 dashed grid ticks, 8% area fill, 2px accent stroke, 3px dots, 4px+halo last point, 5 evenly-spaced "MMM YY" x-labels). ## Test plan Browser-only — no test infra in the repo. Run `npm start` (or `docker compose up`) and open `http://localhost:3000/`. - [ ] **KPI strip** renders above the table with 4 tiles. Tracked/Avg/Climbing/Slipping show plausible numbers. Resize browser below 880px → tiles collapse to 2 columns. - [ ] **Δ-month pill** under each rating value (mint for positive, peach for negative, grey for zero/null). Header reads "Δ month" with "since last update" hint. - [ ] **Δ-predicted pill** under each predicted value. Header reads "Predicted" with "gap from today" or similar hint. - [ ] **Sparklines** render inline in each row's rating cell (96×28 SVG, accent line + 10% area fill + dot on last point). - [ ] **Trend-chart pill** in the table toolbar toggles all sparklines on/off. Reload preserves the state. `aria-pressed` flips correctly. - [ ] **Click a row** → expanded panel opens with the new detail-grid + chart. Chart shows dashed grid lines, 8% area fill, dots, halo on the last point, "MMM YY" x-labels. - [ ] **Open a second row** → first row closes automatically. Only one row open at a time. - [ ] **Tab to a row + Enter or Space** also toggles. Space does not scroll the page. - [ ] **Refresh-all** button in the topbar still works — table refreshes, topbar shows new "Last refresh" time. - [ ] **Per-player refresh icons** (rating cell, predicted cell) still work without triggering row expansion. - [ ] **Tooltips** on rating/predicted values still show std-dev info on hover.
shcizo added 15 commits 2026-05-21 14:40:37 +02:00
Introduce new design token set (paper/ink/line/accent + radius/shadow)
with backward-compat aliases for legacy --surface/--navy/--text names.
Swap DM Sans for Plus Jakarta Sans, add JetBrains Mono with tabular
numerics. Replace .app-header with sticky .topbar partial (brand +
segmented nav + Next update / Last refresh meta + Refresh all button).

Add POST /api/refresh-all that runs refreshAllPlayersInDB() with an
in-memory mutex and returns the rendered topbar so HTMX can swap it
in. "Next update" is computed as first Tuesday of next month
(approximation of PDGA's monthly cycle). "Last refresh" derives
from MAX(players.last_updated).
The topbar's "Refresh all" button (introduced in #4) supersedes this
link. Leaving the gear icon for clearCache() — that's a separate
concern.
The new topbar's "Refresh all" button replaces the old SSE-driven
"Load All" link and progress UI. With those gone, several pieces of
infrastructure had no callers left:

- GET /api/load-all-players, POST /api/populate-database, and
  GET /api/database-status — SSE endpoints with no frontend consumers
- #progress-section / #loading divs in players + courses pages
- .progress-container / .progress-bar / .progress-text / .loading CSS
- public/js/progress.js script (defines fetchRatingsWithProgress, never
  called, and loadAllPlayers, no longer wired) — to be deleted manually
  since the sandbox blocks rm
Add two derived fields to all player objects returned by
getPlayerDataFromDB and the error branch in getAllRatingsFromDB.
No new DB columns — both fields are pure arithmetic derivations.
monthlyHistory placeholder [] included ahead of A2 implementation.
Add getMonthlyHistory() to models/player for single-player use and
getAllMonthlyHistoriesFromDB() for bulk fetches (one query, grouped in
memory). Wire monthlyHistory into all player objects returned by
getPlayerDataFromDB and getAllRatingsFromDB. Bulk path pre-fetches in
one query to avoid N extra per-player queries.
shcizo added 1 commit 2026-05-21 15:03:06 +02:00
The page body is assembled as a JS template literal inside <% ... %>;
EJS tags inside that string break the EJS parser (it sees the first %>
as the close of the outer tag). Switch to ${kpis.x} interpolation since
we're already inside a backtick string.
shcizo added 4 commits 2026-05-21 15:18:47 +02:00
shcizo added 1 commit 2026-05-21 15:23:56 +02:00
The dl + button + chart were 3 direct children of .player-detail (a
2-column grid). Auto-placement put the button in the right column,
forcing the chart to wrap to a second row in the left column.
Wrap dl + button in .player-detail-left so the chart occupies col 2.
shcizo added 1 commit 2026-05-21 15:27:12 +02:00
The thead had position: sticky; top: var(--topbar-height), pinning it
to 64px from the viewport. Inside the new .table-card with a toolbar
above, this pulled the header up out of its natural flow and overlapped
the toolbar and first data row. Let thead flow normally — design
doesn't require sticky behavior.
shcizo added 1 commit 2026-05-21 15:34:45 +02:00
shcizo added 1 commit 2026-05-21 15:51:26 +02:00
shcizo added 1 commit 2026-05-21 15:59:08 +02:00
shcizo added 1 commit 2026-05-21 16:04:27 +02:00
shcizo added 1 commit 2026-05-21 16:11:40 +02:00
re-swap table after refresh for consistent in-place updates
Release Please / release-please (push) Failing after 44s
Release Please / docker (push) Has been skipped
15adddc2f1
shcizo merged commit 15adddc2f1 into main 2026-05-21 16:14:01 +02:00
Sign in to join this conversation.