- Add .table-toolbar to mobile hide list (was rendering duplicate
'TRACKED PLAYERS' header above the mobile section-head)
- Change .m-card__body grid to minmax(0, 1fr) auto so the stats
column can shrink below content size
- Add min-width: 0 to .m-card__stats for proper grid shrinking
- Remove grid-row: span 2 from .m-card__sparkline (single-row grid)
- Remove overflow: visible from .m-chart-spark so the end-dot stays
within the SVG viewport
- Hide desktop .card-section on mobile, add .m-search-input with same
HTMX attrs for mobile course search (fixes horizontal overflow)
- Remove dead layoutCount var and .m-layouts-pill block in course-cards
- Remove dead 768px breakpoints from players.css (table hidden at 880px)
- Move .mobile-section-head inside else-block for empty state in both
ratings-cards and course-cards (fixes section head showing on empty)
- Add tabindex, role=button, aria-expanded, onkeydown to .m-card and
.m-course-card; toggle aria-expanded in JS toggle functions
- Fix data-history attribute to use <%= (HTML-escaped) instead of <%-
- Convert var to const/let in all new/changed JS blocks
After the binary search converges, also simulate predicted rating at
required±1 average. Display the three rows in the modal so the user can
see how sharp the requirement is — e.g. whether averaging 1 point lower
costs them 1 point of predicted rating or 5.
When a player has rating_history (graph) but no round_history (per-round
detail), calculating a target produced a dead-end error. Now the modal
detects the NO_ROUNDS case and shows a button that triggers the existing
refresh-round-history endpoint and re-runs the calculation on success.
Handles the 24h rate-limit and other refresh errors explicitly.
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.
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.
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
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).
Tour functionality has moved to its own project (HyzrTour).
Removes all tour-related code, Tjing integration, and associated
views/styles/scripts. Keeps the saveCourseToDB ON CONFLICT fix.
Search and import courses with layouts from Tjing's GraphQL API.
Total par is calculated from individual hole data. Courses are saved
with a tjing.se link as unique identifier to prevent duplicates.
Courses without scraped layouts can now be used in tours by entering
a layout name and par manually. The layout is saved to the database
for reuse. All courses are shown in the dropdown, not just those with
existing layouts.
Players can create tours with selected courses/layouts and a date range.
Others join via a 6-character tour code, play the courses, and report
their total strokes. Live leaderboard with points and +/- par display.
Includes: database schema, model, service, routes, views, and styling.
- Add sticky dark header with nav replacing inline text links
- Introduce CSS custom properties design system (colors, spacing, shadows)
- Use DM Sans + JetBrains Mono fonts replacing Arial
- Modernize tables with uppercase headers and subtle hover states
- Add gradient fill and rounded line to rating chart
- Unify card sections across players and courses pages
- Add backdrop blur to modals
- Clean up inline styles to use CSS variables
- Extract CSS into public/css/{shared,players,courses}.css
- Extract JS into public/js/{chart,tooltips,progress,players,courses}.js
- Consolidate 5 duplicated tooltip blocks into setupTooltip() helper
- Add EJS view engine with layout partial and nav partial
- Convert HTML pages to EJS templates (index.ejs, courses.ejs)
- Add /courses route with redirect from /courses.html
- Remove old monolithic HTML files (1478 + 612 lines)