Split the live demo into pinned stable and always-latest edge release channels, promoted via a committed ref file
Context
docs/technical-architecture.md §3.4 (decision 0042) required the live demonstration surface to be split into a pinned, deliberately promoted /play (stable) and a directly-tracking /play-edge or equivalent (edge) channel, decoupling what's publicly demonstrated from active development -- tracked as roadmap item RM-025. The site's build is fully stateless (lustre_ssg regenerates the whole dist/ tree from scratch every run, no incremental state carried between deploys), so the real design problem was finding a persistence mechanism for "what glemy commit is currently promoted to stable" that survives across independent, from-scratch CI runs.
Options considered
Decision
Added STABLE_GLEMY_REF (repo root, one line, a glemy commit SHA) as the persistence mechanism: promoting a new stable build is a real, committed, pushed change to this file, exactly matching technical-architecture.md's "deliberate action" requirement and using git's own history as the audit trail rather than inventing one. .github/workflows/deploy.yml now checks out glemy twice per run -- glemy-edge always at the default branch (also the source for decisions.jsonl/development-plan.jsonl, since those track main regardless of which build is currently stable), glemy-stable at whatever ref STABLE_GLEMY_REF names -- builds both JS targets, and copies each into its own static/play-demo-{stable,edge}/ directory. build.gleam's existing directory-content-hashing (decision 0045, RM-024) was generalized to take a base directory name parameter rather than being hardcoded to one path, so both channels get independently hashed and renamed with no duplicated logic. game_card.gleam gained a DemoPaths record (stable/edge path pair) threaded through home.gleam and play.gleam alongside the existing base_url/style_hash parameters, and now renders both links on the one game card rather than adding a second page.
Verification
gleam test passes (17/17, unchanged -- this is infrastructure/wiring, not new pure logic needing new unit tests). A full local build simulating both channels produced correctly distinct, independently hashed paths (play-demo-stable-<hash>, play-demo-edge-<hash>) with both buttons on the rendered game card linking to the right one. A real headless-browser pass against both channel URLs directly showed both genuinely rendering via WebGPU (119 and 120 real frames respectively) with zero errors beyond the same pre-existing, already-diagnosed glemy favicon gap. The real CI workflow change (dual checkout, dual build, dual copy) was written to reuse the exact same copy-step shape as the existing single-channel version, just once per channel, keeping the risk of a CI-only failure low despite not being able to fully simulate GitHub's cross-repo checkout locally.
Consequences
Closes RM-025 and, with it, every concretely buildable item from the original technical-architecture.md deployment requirements (RM-017 remains correctly policy-only, with nothing to implement until persistence is actually needed). Deploys now check out and build glemy twice instead of once, roughly doubling that portion of CI time (still well under a minute total, confirmed by the real run) -- accepted as the honest cost of having two genuinely independent builds rather than one. Promoting a new stable build is now a real, if manual, step someone has to remember to take (documented in the README) -- the intended behavior, not an oversight: a stale stable build is a visible, git-diffable fact (STABLE_GLEMY_REF's own age), not a silent one.