← All decisions

Add Breakout to glemy-website's game catalog, generalizing game_card into a parameterized component at its second real caller

breakoutsecond-gamewebsitedeployment

Context

The final concretely-scoped item of the approved Breakout plan's Phase 3: publishing Breakout's live demo through glemy-website (a separate repository, decisions.jsonl-tracked from glemy's own log per this project's existing cross-repo convention) alongside Tiers', and making the catalog page (/play) list both games. glemy_website/game_card.gleam's own pre-existing doc comment already anticipated this moment explicitly: 'more games, in different genres, are the actual test of whether glemy generalizes... this component -- and the page it's used on -- are shaped for that from the start rather than needing a rewrite when the second game lands.' tiers_card was hardcoded (icon/name/tagline/description/button text all Tiers-specific inline), with exactly one real caller until now.

Options considered

Add a second, independent breakout_card function duplicating tiers_card's markup structure with Breakout-specific content — rejected
Would leave the actual <div>/<h3>/<p>/<a> structure duplicated across two functions with no shared source of truth, the same 'un-generalized because there was only ever one caller' state this project has repeatedly corrected elsewhere (glemy/games/tiers.gleam's own tick composition, decision 0049) the moment a genuine second caller appears. game_card.gleam's own doc comment already named this as the intended trigger for generalizing, not something to defer further now that the trigger is real.
Reuse the exact same static/play-demo-{stable,edge}/build/dev/javascript directory for both games' demo pages, pointing two different HTML entry points at one shared JS tree — rejected
build.gleam's prepare_play_demo_dir renames each unhashed play-demo-<name>/ directory to play-demo-<name>-<hash>/ in place (RM-024) -- a directory can only be renamed once, so sharing one physical tree between two independently-hashed demo names isn't possible without restructuring that content-hashing scheme. Each demo dir gets its own full copy instead (already the existing pattern between play-demo-stable and play-demo-edge, which independently duplicate the same glemy repo's build output today), keeping the hashing scheme unchanged.

Decision

Generalized glemy_website/game_card.gleam's tiers_card into a parameterized game_card(base_url, demo, icon, name, tagline, description) function; tiers_card and a new breakout_card are now both thin callers of it. Added breakout_icon() built from Breakout's own real on-screen colors (paddle #5af, ball white, bricks #fa5 -- matching breakout.html's CSS and games/breakout.ball_color, the same 'real colors, not a generic icon' principle tiers_icon() already established). game_card.catalog now takes one DemoPaths per game (tiers_demo, breakout_demo) and returns both cards; glemy_website/pages/play.gleam's view threads both through. build.gleam gained two more prepare_play_demo_dir calls (play-demo-breakout-stable/-edge) alongside the existing Tiers pair, producing a second DemoPaths passed into play.view. .github/workflows/deploy.yml gained a second checkout-copy block (breakout.html renamed to index.html on copy, plus its own full build/dev/javascript copy, mirroring the existing Tiers block exactly) for both stable and edge channels. The homepage (home.gleam) was deliberately left calling tiers_card directly, unchanged -- it's a single-game teaser by design (its own prose still reads 'glemy's first proof is Tiers, below'), not the full catalog; /play is where the generalized catalog now shows both games.

Verification

gleam build and gleam test both pass in glemy-website (17/17, unchanged -- no test exercises game_card/build.gleam directly, matching the pre-existing lack of coverage there). Ran a full local build simulating .github/workflows/deploy.yml's own steps (copied glemy's real decisions.jsonl/development-plan.jsonl and real index.html/breakout.html + build/dev/javascript into static/play-demo-edge and static/play-demo-breakout-edge, then gleam run -m build with a real GLEMY_WEBSITE_BASE_URL): build succeeded, dist/play-demo-breakout-edge-<hash>/ was produced as its own independently content-hashed directory containing a real, self-contained copy of breakout.html (renamed to index.html) and game_breakout.mjs/game_breakout_ffi.mjs, and dist/play/index.html contains both 'Play Tiers ↗'/'Play Breakout ↗' buttons linking to the correct hashed edge paths (stable correctly fell back to the unhashed /play-demo-stable and /play-demo-breakout-stable paths, since no stable checkout exists in this local simulation -- exactly prepare_play_demo_dir's documented, deliberate fallback behavior, not a bug).

Consequences

glemy-website's /play catalog now has two real entries, the first genuine test of whether game_card's shape holds up past one caller -- it did, with zero structural changes needed beyond parameterizing what was already hardcoded. A third game follows the exact same three-piece pattern (an icon function, a *_card wrapper calling game_card, one more prepare_play_demo_dir pair plus one more deploy.yml copy block) rather than needing new design. This is also the first time glemy's own repo (game_breakout.gleam/breakout.html, decision 0053) and glemy-website's independent deploy pipeline have been exercised together for a second game end-to-end, closing out Breakout's full roadmap (decisions 0051-0055) other than the deliberately-deferred hands-on feel-tuning (decision 0054).

References