Make Crystal an installable web app with an offline shell (#39)
Checks / web (push) Failing after 16s
Private ROM checks / private-rom (push) Failing after 17m47s

A manifest and icons rendered from icon.svg make the app installable.
The native build emits a service worker that precaches exactly the
built files and passes everything else to the network, so ROMs never
reach its cache. New versions wait for the player's Update, which saves
before reloading. Savegames offers installation, iPhone steps and
persistent storage, and a full disk gets its own save error.

Acceptance on physical iPhone and Android devices remains.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
samyandClaude Opus 5.5 committed 2026-10-03 20:51:57 +02:00
1 parent f289615bd2
commit ce8129703f
20 files changed
+661 -15

No files matched your search

+10 -1
View File
@@ -235,6 +235,15 @@ The numbered sections below keep their full scope. Each one opens with a checkli
- "Open in this port" lists the landmarks with at least one map in `NATIVE_MAPS`, so the atlas never suggests travel through unported areas.
- `tests/atlas.test.ts` compares every landmark position with `data/maps/landmarks.asm` and the flypoints with `data/maps/flypoints.asm`.
## Implemented: the installable web app
- `public/manifest.webmanifest` makes Crystal installable (standalone, relative start URL and scope, so subdirectory hosting works). `scripts/make-icons.mjs` renders the PNG icons, including a maskable one and the Apple touch icon, from the project's own `icon.svg`.
- The native build emits `sw.js` (`scripts/service-worker.mjs`, a Vite plugin). It precaches exactly the built files under a cache named by their contents, serves them first and passes every other request to the network. ROMs are read from local files and never fetched, so they never reach the cache. The reference build has no worker, and development registers none.
- Updates wait. A new version installs in the background, and a banner offers Update; the page saves, then the worker takes over and the page reloads. Nothing reloads during play without that choice. Activation deletes older shells.
- Savegames gains App and Storage. Install uses the browser's prompt where it exists; iPhone and iPad get Safari's Share, Add to Home Screen steps. Storage shows whether saves are persistent and how much is used, and asks for persistence on request (and once at start when installed). A full disk gets its own save error, which points to backups.
- `check:release` accepts only the four known icons beside the text assets and checks that the worker's list matches the build. Tests run the generated worker against a fake cache, and a Chromium test opens the production build offline.
- The ROM is still selected at each launch; keeping it is a separate, explicit preference for later.
## Implemented: interface settings
- Settings, beside Help, sets the text size (Standard, Large, Largest: 100, 125 and 150 %) and the keyboard key of each button. Every font size in the stylesheet scales with `--text-scale`; the dialogue box scrolls instead of growing past the screen.
@@ -478,7 +487,7 @@ Acceptance: main-game/postgame playthroughs, representative optional content, ba
- [x] Scalable text and configurable keyboard controls
- [ ] Fuller accessibility: announcements for every native screen and touch control remapping
- [ ] Save slots with migration, atomic writes and import previews
- [ ] Installable web app with offline app shell, safe updates and quota handling
- [x] Installable web app with offline app shell, safe updates and quota handling
- [ ] Acceptance on physical iPhone and Android devices
The first presentation increment replaces the landing page and handheld frame with a restrained Crystal-style interface. Game, Savegames, Map, Pokédex, Link and Friends stay above a viewport-filling native canvas. The renderer draws more world tiles as space grows instead of stretching a 160 × 144 image. The game and Map tab share a live local minimap; the atlas and unsupported tabs remain honest planned states. The linked UI reference could not be inspected through automated access because the site returned a Cloudflare challenge, so exact screenshot matching is not claimed.