All notable changes to this project are documented in this file.
The format is based on Keep a Changelog.
make docker-build-multiarch: builds a multi-arch image (linux/amd64+linux/arm64, covering Linux and macOS on both Intel and Apple Silicon) via Docker Buildx, to validate the cross-arch build without pushing anywhere.make docker-push: builds and pushes a multi-arch image to a registry, interactively prompting for registry username, password/access token (hidden input, piped todocker login --password-stdin), repository name, and image tag.make docker-buildx-setup: creates/reuses thedocker-container-driver Buildx builder the two targets above require (the defaultdockerdriver can't produce multi-platform output).docker buildxadded tomake check-deps' tool checks.- Root
VERSIONfile: the single source of truth for the app's release version, read by theMakefileintoAPP_VERSION. make helm-sync-version: writesVERSION's content intocharts/my-world-cup-app/Chart.yaml'sappVersion(portablesed -i.bakfor both GNU/Linux and BSD/macOS sed); run automatically bymake docker-pushandmake helm-docsbefore every publish/docs regeneration, soappVersioncan't go stale.Dockerfile's runtime stage now accepts anAPP_VERSIONbuild-arg (defaultdev) and sets it as theorg.opencontainers.image.versionimage label.CONTRIBUTING.mdnow reminds contributors to bumpVERSION(and add aCHANGELOG.mdentry) for release-worthy changes.
DOCKER_TAGnow defaults to the version in theVERSIONfile instead of a hardcodedlatest;DOCKER_PLATFORMS(defaultlinux/amd64,linux/arm64) is also an overridableMakefilevariable, e.g.make docker-push DOCKER_PLATFORMS=linux/amd64.make docker-build/make docker-up(docker compose build/up) anddocker-compose.ymlnow passAPP_VERSIONthrough as a build-arg too, so a plain Compose-based build carries the same version label as the Buildx-based targets.
- "Expand all matches" / "Collapse all matches" button on the Groups page (
#toggle-all-matchesinweb/templates/groups.html, wired up byinitGroupMatchesToggleinweb/static/js/app.js) toggles every group's match-results<details class="group-matches">at once instead of requiring one click per group.
- Home page's Upcoming Matches / Recent Results cards moved from a side-by-side
.grid-2layout (each card capped at ~538px even on wide desktops) to a new full-width.home-gridsingle column, so venue names have room to stay on one line at common desktop resolutions.
- Match venues (home page,
/matches) now link to the official fifa.com host-city page instead of a stadium-name lookup that never matched:models.Match.Groundcarries the host city (e.g. "Mexico City"), not the stadium name ("Estadio Azteca"), so rendering switched fromStadiumLinkstoCityLinks(internal/handlers/render.go,web/templates/home.html,web/templates/matches.html). - Reworked responsive layout across the Groups, Home, and Matches pages so tables and cards fit without a horizontal scrollbar at common phone/tablet/desktop widths:
.cardnow setsmin-width: 0so a wide table can no longer blow out its CSS grid track (and the whole page) instead of scrolling within its own card — grid items default to a content-based minimum width, not0..group-gridsizes cards withminmax(min(460px, 100%), 1fr)instead of a bareminmax(460px, 1fr):auto-fill/auto-fitonly ever reduce column count, never theminmaxminimum, so the un-capped version still forced a 460px-wide column (and page-wide horizontal scroll) on any phone screen..tableno longer forcesmin-width: max-content, which was defeating the existingoverflow-wrap/word-breakrules and always triggering.table-responsive's scrollbar instead of letting long cell content wrap.- New
.nowrap-cellutility keeps short fixed-format values (dates, round, group) from wrapping character-by-character once table columns are free to shrink. .standings-table(group tables) and the new.matches-table(fixture-list tables on the home,/matches, and group match-detail views) restack into team-first blocks with labeled fields below 640px instead of scrolling sideways, using newdata-labelattributes on the affected table cells.
- Official fifa.com links for every team, stadium, and host city (
baseData.TeamLinks/StadiumLinks/CityLinks, built ininternal/handlers/fifa_links.go), rendered next to team names, match venues, and the Stadiums table on the FIFA Links page. - Three more official resources on the FIFA Links page: Official Match Ball, Official Posters, and Mascots.
- Official FIFA article on the FIFA Links page explaining the group stage format and tie-breaking rules ("Groups: How Teams Qualify & Tie-Breakers").
- Graphical knockout bracket on
/knockout: rounds connected with bracket lines, winning teams highlighted, penalty shoot-outs annotated, and the match for third place rendered as its own standalone fixture — shown alongside the existing round-by-round detail tables (internal/handlers/pages.go,web/templates/knockout.html). - Matches page filtering:
/matchesnow accepts combinableround,group, andteamquery parameters (services.FilterMatches/MatchFilterOptionsininternal/services/matches.go), with a filter form, a "Showing X of Y matches" summary, and a "Clear filters" link.
- Refreshed the dark/light theme color palettes and general UI polish: sticky header with shadow, pill-style active nav highlighting, card shadows, button hover/focus states, and table row hover (
web/static/css/style.css). - Team flags now render as bordered flag "chip" badges (new
flagtemplate func) instead of a bare emoji; match results render as a colored score pill (newscoretemplate func), with unplayed matches shown as a "scheduled" badge. - Updated the screenshots in
images/to reflect the refreshed UI.
models.Match.Winner()now accounts for a penalty shoot-out when full time ends level, so knockout draws decided on penalties correctly report a winner (previously only the full-time score was checked). This backs the new bracket's winner highlighting.
- Initial release of My World Cup App.
- Go 1.25 web server (standard library only) with clean architecture:
models,data,services,handlers. - Group standings, knockout stage, and full match list pages, rendered server-side with
html/template. - Live data fetching from openfootball/worldcup.json on startup and via a manual "Update data" action (
POST /refresh), with an embedded fallback snapshot for offline/degraded operation. - Dark/light theme toggle with
localStoragepersistence. - Links page with official FIFA World Cup 2026 resources (teams, standings, stadiums, articles, scores & fixtures, Club World Cup 2025) and the official Spotify playlist.
- Unit tests for JSON parsing/normalization and standings/knockout computation; HTTP integration tests for all routes.
- Dockerfile (multi-stage, distroless runtime) and Docker Compose setup.
- Makefile with
run,build,test,test-coverage,fmt,vet,docker-*targets. /metricsendpoint exposing Prometheus metrics (internal/metrics): HTTP request counts and latency, and data refresh outcome/timestamp. First third-party dependency (prometheus/client_golang), added specifically for this./statspage andinternal/services/stats.go: top scorers (aggregated goal tally per player) and overall team statistics (played/won/drawn/lost/goals/points across group and knockout stages).- Helm chart (
charts/my-world-cup-app) for Kubernetes deployment, with liveness/readiness probes on/healthz, Prometheus scrape annotations, and optional Ingress/HorizontalPodAutoscaler. - Unit tests for the stats service and additional handler integration tests for
/statsand/metrics.
- Periodic automatic background refresh (in addition to startup and manual refresh).
- Optional persistent cache (e.g. local file) to survive restarts without a network call.