Skip to content

feat(web): show each game's cover on the Games page - #193

Merged
Bakhomious merged 3 commits into
mainfrom
step/games-page-covers
Oct 30, 2026
Merged

Bakhomious merged 3 commits into
mainfrom
step/games-page-covers

Conversation

@Bakhomious

@Bakhomious Bakhomious commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Brief

Goal: the Games page shows each game's cover from IGDB, which the games service has sent since #186 (decision 306). The last of games-exophase's eleven steps (decision 308).

Scope

  • Game gains cover, the address of the game's cover on IGDB, left out when it has none, as the service sends it.
  • Each row of "Recently played" shows the cover, 48 by 68 px, in a column of its own between the day and the title. On phones, where the day shares the first line with the playtime, the cover starts the second line, beside the text. It has a 1 px --rule border and fills its box, cropped rather than letterboxed. It's the background of a blank, which screen readers skip, since the title beside it names the game.
  • A game without a cover, a cover still loading and one that fails to load leave that blank alone: --fill in the same border.
  • End-to-end: the games' demo data moves from demo.ts, which is at Biome's 200 lines, to e2e/games.ts, with made-up covers (SVG shapes, since no game's real art can be in the repo) that the tests answer at IGDB's address. A new spec checks the covers' size and place on both screens, the blank, and that the covers load under the gateway's Content Security Policy.
  • Docs: §0 row, §7's Games bullet, the §8.2 bullet, decision 308.

Out of scope

Acceptance criteria

  1. A game with a cover shows it from the service's address as the background of its blank, which screen readers skip, so each row reads as before.
  2. On a desktop, the cover is 48 by 68 px, 20 px after the label's 120 px column and 20 px before the title, centred on the row.
  3. On a phone, the cover is 48 by 68 px at the row's start, 6 px under the day's line, with the text 20 px after it.
  4. A game without a cover, and a cover that fails to load, leave a blank of the same size and place, --fill in a --rule border, so the titles stay in line.
  5. The covers load from IGDB's address with no console error and no Content Security Policy violation.
  6. A long title still wraps, and the page never scrolls sideways at 320 px.
  7. mise run check passes.

What changed

  • src/games/games.ts: Game.cover, and coverImage(), which turns the address into a CSS url("…"), with its quotes and backslashes escaped.
  • src/games/GamesPage.svelte: each row gets a .cover blank with the cover as its background. A scoped grid gives the cover its column on a desktop and the start of the second line on a phone.
  • End-to-end: e2e/games.ts holds the demo games, moved from demo.ts, which was at 199 of its 200 lines, now with made-up SVG covers, and answerGames and answerCovers. games-covers.spec.ts checks that the covers load, the frame, the blank, and the size and place on both screens. ink.ts gains colour(page, token), which inkMuted now calls.
  • Docs: the §0 row, §7's Games bullet, the §8.2 bullet and decision 308.

QA

  • AC 1: a unit test reads each blank's background, and finds no image role; the e2e reads the computed background. ACs 2 and 3: the e2e measures every row on both screens. AC 4: Sky Rally has no cover, and a throwaway probe answered every cover with a 404. AC 5: the e2e waits for the three covers' requests to finish, and fixtures.ts fails a test on any console error or CSP violation. AC 6: games.spec.ts's 320 px test, plus a probe with a cover beside a long title. AC 7: below.
  • Found: an <img alt=""> whose load fails shows a broken-image icon, in Chromium and in WebKit alike. The engineer made the cover the blank's background, and a failed load now leaves the plain blank in both. The lead updated the scope and ACs 1 and 4.
  • Hand mutants, 11 of 11 caught: no background, 40 px wide, 64 px high, no frame, no fill, the cover at the top left, contain, no phone layout, the cover before the day, coverImage without escaping, a row without its game class.
  • Row heights: 88 px each on a desktop, where a row with trophies was 86 and one without 65; 111.6 on a phone, where they were 109.6 and 88.6.
  • mise run check is green: Go lint 0 issues; govulncheck clean; Go coverage 96.7%; Vitest 1274 passed, web coverage 99.79% of statements; e2e 416 passed, 8 skipped; Biome, svelte-check, tsc and knip clean.

Review

Blocking: none. Non-blocking:

  • When a long title wraps, the cover centres on the row's height, as the day and the playtime do; a cover at the top of the text is the other common layout.
  • Until IGDB is set up, every row shows a blank; decision 308 rejects leaving the column out then.
  • colour() sits in ink.ts beside inkMuted, which calls it, so the specs that use inkMuted don't change.

Screenshots

The demo games, as a made-up user in Tokyo, with made-up covers; Sky Rally has none.

Light Dark
Desktop Desktop, light Desktop, dark
Phone Phone, light Phone, dark

A cover that fails to load leaves the blank:

A failed cover on a phone

The games service has sent each game's IGDB cover since #186 (decision
306). The Games page now shows it in a column between the day and the
title, as the background of a framed blank, so a game without a cover,
a cover still loading and one that fails to load all leave the same
blank, and screen readers skip it (decision 308).
@Bakhomious
Bakhomious merged commit dd6647f into main Oct 30, 2026
2 checks passed
@Bakhomious
Bakhomious deleted the step/games-page-covers branch October 30, 2026 04:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant