You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit dd3a867
Browse filesBrowse the repository at this point in the historyBrowse files
feat: offer installed apps a reload when a new build is live
iOS keeps the Home Screen app alive in the background and players rarely
force close it, so they kept playing an old build long after a deploy.
There was no way for a running copy to learn that a newer one was live.
Each production build now carries an id: BUILD_ID, else GITHUB_SHA (CI
and the Alchemy deploy), else the local commit with a timestamp when the
tree is dirty, else a timestamp. A small Vite plugin defines it as
__BUILD_ID__ and writes dist/version.json. The dev server uses "dev" and
never checks; the tests use "test".
The game fetches version.json with no-store and a time query when the
page becomes visible again, on a back forward cache restore, on window
focus, and every five minutes on the title, pause, end screens, and the
break between waves. update only records the newer build as an Option,
and failures are ignored. A "New version ready" chip shows on those quiet
screens, never mid-wave, and tapping it reloads. public/_headers serves
version.json no-store and the HTML no-cache.
Story and Scene tests cover the checks, the chip on each quiet screen,
the reload, failures, and the subscription filters. tools/audit/shots26.ts
shoots the chip layout and runs the end to end rebuild and reload check.
Copy file name to clipboardExpand all lines: AGENTS.md
+13-1Lines changed: 13 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -72,7 +72,7 @@ Before you finish a change, run `typecheck`, `lint`, `test`, and `build`. All fo
72
72
|`src/effectsView.ts`| Bullets, decals, particles, blasts, popups, and rattling doors |
73
73
|`src/hud.ts`| Hearts, money, the streak, gun and trap slots, and the round text |
74
74
|`src/wavePreview.ts`| The door markers that preview a wave, and their spoken text |
75
-
|`src/touchControls.ts`| Sticks, ghost sticks, icon buttons, gun slots, and the thumb corner |
75
+
|`src/touchControls.ts`| Sticks, ghost sticks, icon buttons, gun slots, and the thumb corner |
76
76
|`src/menus.ts`| Mode buttons, the new zombie card, corner icons, tips, rotate notice |
77
77
|`src/pauseHelp.ts`| The pause screen's panel of controls and tips for each device |
78
78
|`src/powerupView.ts`| Pickups, callouts, the boost aura, and HUD timers on canvas |
@@ -84,6 +84,8 @@ Before you finish a change, run `typecheck`, `lint`, `test`, and `build`. All fo
84
84
|`src/sprite.ts`| Pixel maps turned into canvas shapes |
85
85
|`src/icons.ts`| Pixel maps turned into SVG icons for the HTML buttons |
86
86
|`src/prng.ts`| The seeded random number generator |
87
+
|`src/version.ts`| The running build id, the `version.json` check, and reloading |
88
+
|`src/updateChip.ts`| The "New version ready" chip shown on quiet screens |
87
89
|`src/constants.ts`| The view size, speeds, and other tuning numbers |
88
90
89
91
### Deploying
@@ -107,6 +109,16 @@ Credentials: locally, the Cloudflare API token is in the macOS keychain under `c
107
109
108
110
GitHub Pages is gone: its workflow is deleted and the site is disabled. Cloudflare Workers Builds is not connected either; the Deploy workflow is the only deploy path, so do not connect the repo in the Cloudflare dashboard.
109
111
112
+
#### How updates reach installed apps
113
+
114
+
iOS keeps a Home Screen app alive in the background, so players can keep an old build for days after a deploy. There is no service worker, on purpose. Instead:
115
+
116
+
-**Build id.** A small plugin in `vite.config.ts` stamps every production build with an id: `BUILD_ID` if set, else `GITHUB_SHA` (CI and the Alchemy deploy in CI), else the local commit (with `-dirty-<timestamp>` when the tree has uncommitted changes, since a local deploy ships them), else a timestamp. The id is the compile-time constant `__BUILD_ID__` and is also written to `dist/version.json` as `{ "build": "<id>" }`. `bun dev` uses `dev` and never checks; the tests use `test`.
117
+
-**Never cached.**`public/_headers` serves `/version.json` with `no-store` and `/` and `/index.html` with `no-cache`, and the game fetches `version.json` with `cache: 'no-store'` and a time query.
118
+
-**When it checks.** When the page becomes visible again, on a back forward cache restore, on window focus, and every five minutes on the title, pause, Game Over, Victory, and the break between waves. Never on a timer mid-wave. A failed or offline check is ignored.
119
+
-**The prompt.** If the live build differs from the running one, a "New version ready" chip shows on those same quiet screens, never during a wave. Tapping it reloads the page. Its place lives in `.update-chip` in `src/styles.css`, as arena pixels (`--chip-arena-x`, `--chip-arena-y`), so it is easy to move.
120
+
-**Check it.**`PART=e2e bun shots26.ts` in `tools/audit` builds, serves, rebuilds under an open page, and checks that the chip appears and the reload lands on the new build.
121
+
110
122
### Rules
111
123
112
124
-**Keep the game deterministic.**`update` is pure. It never calls `Math.random` and never reads the clock. Randomness comes from the seeded generator whose state lives in the Model, so the same seed and the same Messages give the same game.
Copy file name to clipboardExpand all lines: PLATFORM.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,6 +8,7 @@ All on 2026-10-28.
8
8
9
9
-**Everything moves to Cloudflare.** GitHub Pages was only for testing on other devices. It was removed (the site and `.github/workflows/pages.yml`) on 2026-10-28, once the Cloudflare deploy was live.
10
10
-**The game is hosted at https://munch-manor.tanner-scadden.workers.dev**, the free `workers.dev` address, with no custom domain yet. The account's `workers.dev` subdomain is `tanner-scadden`.
11
+
-**Installed apps find new builds with `version.json`, not a service worker.** Each build stamps its commit into the bundle and into `version.json`, which is never cached. The game fetches it when it comes back into view, on focus, and every five minutes on quiet screens, and offers a tap to reload when the build differs. See "Deploying" in `AGENTS.md`.
11
12
-**GitHub Actions is the only deploy path.** Cloudflare Workers Builds (the dashboard's git integration) stays disconnected, so one push never fires two deploys.
12
13
-**Free plan first.** Upgrade to Workers Paid only if the game takes off.
13
14
-**Our own mini sync engine over scopes**, for personal and shared state. See "Sync engine".
@@ -59,7 +60,7 @@ Package names and APIs need checking against the Effect version the game uses (E
59
60
-**Token permissions.** CI deploys with an account-owned token, `munch-manor-ci`, minted by `infra/github.ts`: Workers Scripts Write, Secrets Store Write, Account Settings Read, Workers Tail Read. Secrets Store Write is needed even to read, because Alchemy reads the state store's token by binding the secret to a short-lived preview Worker. The keychain token used by hand is much broader (it can mint tokens) and never goes to CI. Cloudflare's Workers Builds API rejected that account-owned token, so whether Workers Builds is connected can only be seen in the dashboard or in a Worker's deployment history (a `wrangler` source).
60
61
-**The account had no `workers.dev` subdomain.** Alchemy's state store needs one, so `tanner-scadden` was claimed through the API before the first deploy.
61
62
-**Two deploy paths collide quietly.** While Workers Builds was still connected, one push ran `wrangler deploy` and overwrote the Worker with a single page fallback. Alchemy's next deploy saw no changed inputs, and `alchemy drift` saw no drift, so both reported success. `alchemy deploy --force` put the declared version back.
62
-
-**Routing and caching.** The game has no routes, so unknown paths answer 404 (`notFoundHandling: 'none'`). The default single page fallback served `index.html` at nested paths, where its relative asset paths missed. `public/_headers` makes the hashed files in `/assets` immutable for a year, and `index.html`revalidates on every load.
63
+
-**Routing and caching.** The game has no routes, so unknown paths answer 404 (`notFoundHandling: 'none'`). The default single page fallback served `index.html` at nested paths, where its relative asset paths missed. `public/_headers` makes the hashed files in `/assets` immutable for a year, `index.html`and `/` are `no-cache` so every load revalidates, and `version.json` is `no-store`.
Copy file name to clipboardExpand all lines: infra/README.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -46,6 +46,10 @@ bun alchemy deploy github.ts --stage prod
46
46
47
47
If the repository is renamed, change `REPOSITORY` and deploy twice, the second time with `--force`. The rename makes Alchemy replace each secret: it writes the new one, then deletes the old one, and GitHub redirects the old name to the same repository, so the delete removes the secret just written. The forced deploy writes it again. Check with `gh secret list`.
48
48
49
+
## Updates on installed apps
50
+
51
+
The Vite build stamps the bundle and `version.json` with `GITHUB_SHA`, which the Alchemy build child inherits in CI. A local deploy stamps the local commit, plus a timestamp when the tree is dirty. Alchemy skips the build when no input changed, so `version.json` only changes when the game does. See "How updates reach installed apps" in `AGENTS.md`.
0 commit comments