Make the web app open offline from one visit, update cleanly, and keep what it stores - #108
Merged
Merged
Conversation
- Removed sqlite3.wasm from the project as it is now handled by eigen_flutter. - Updated GitHub Actions workflow to install root dependencies before building the web app. - Modified README to reflect changes in service worker generation and offline capabilities. - Updated package.json to include workbox-cli for service worker generation. - Added workbox-config.cjs for service worker configuration to precache assets. - Updated scaffold tests to verify service worker and asset handling. - Improved app lifecycle handling to refresh when coming back into view. - Enhanced update mechanism to allow service worker to take over before reloading. - Updated documentation to clarify web app behavior regarding offline play and updates.
…y budget A browser may clear a site's storage, and a game played on the device that has not uploaded yet is the one thing a sync cannot bring back. Asking the browser to keep it makes some browsers ask the player, so the app now explains first: the home screen offers it while such a game exists, once, and never on a device. Creating a local game no longer asks silently at a moment that explains nothing. `ReplicaHost` reports persistence as a state, so the offer is made only where the browser has neither granted nor refused it. How many ended online games keep their replay is now `ReplicaConfig.keptReplays` on `AppConfig`, which answers an open question of decision 0013. It matters most in a browser, where drift holds the whole replica in memory. Also drops the storage chooser's rule that kept an existing database in the storage it was created in: that is compatibility for data no player has yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… fixes it A browser may terminate the worker hosting the replica, which Chrome for Android documents for a backgrounded app, and nothing tells the page: drift's channel cannot observe a worker going away, so a statement never answers, lists keep what they last read, and writes wait forever. The app looked frozen with nothing to act on, and only a reload recovered it. There is no signal to listen for, so statements are timed. One unanswered for 20 seconds reports the replica as not answering, and any answer reports it back; opening is not timed, because loading a large replica out of IndexedDB is slow rather than broken. Nothing recovers automatically: a banner says the app stopped responding and offers a reload, which opens everything again. Every committed write is already stored, so that is all it costs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An audit of the web build against decisions 0012 and 0013 found several browser
behaviours worked around where they needed designing for. This fixes them, and
records the design as
decision 0014.
What changes
The service worker is generated by Workbox and precaches the build. The
hand-written worker only began caching once it controlled the page, after
Flutter had already fetched its entry files, so an app visited once did not open
offline; and it served the unhashed entry points cache-first, so the
update-required reload came back to the old build. Now a deploy installs beside
the running build and takes over when the app asks, so a tab never mixes two
builds.
run_worker_firstand the worker'snavigateFallbackDenylistare heldtogether by a scaffold test.
Drift's web runtime ships with
eigen_flutteras web-only package assets,so no app or template carries a copy of
sqlite3.wasmanddrift_worker.js.Storage is chosen, then locked, then opened. The tab lock was taken when
SharedWorkerwas missing, but drift also falls back to per-tab IndexedDB whena shared worker exists and fails, so the unsafe case could open unlocked. A tab
refused the database now opens by itself once the tab holding it closes.
A sync request is answered by a pass that began after it. The lock was taken
before joining a pass in flight, so on the web a trigger arriving mid-pass
queued a second full pass. A burst of triggers now costs one pass, across tabs
too.
Coming back is
onShow. Flutter's web engine reports every window focus asa resume, so alt-tabbing, or closing the sign-in popup, ran a sync pass and
reconnected the open game's socket.
Connectivity starts from the current state.
connectivity_pluson the webreports only changes, so an app opened offline believed itself online and did
not sync when the network returned.
Sign-in outcomes are modelled. Dismissing Google's sign-in is no longer an
error, and no longer discards a guest's data mid-switch; a blocked popup says to
allow pop-ups. Web stays on the popup, with no redirect fallback.
Two drift defects are worked around, tracked in
docs/blockers.md. OnIndexedDB storage, drift 2.35.0 saves neither a transaction nor the schema
version until some later unrelated write, so a tab closing in between lost the
write, or left every later open failing to create tables that exist. Fixed
upstream in simolus3/drift#3865, unreleased.
A replica that stops answering says so. A browser may terminate the worker
holding the database — Chrome for Android documents this for a backgrounded app
— and nothing tells the page, so statements never answer and the app looks
frozen. Statements are timed; one unanswered for 20 seconds raises a banner
offering a reload, which opens everything again. Nothing recovers automatically,
and every committed write is already stored.
The replay budget is app configuration (
ReplicaConfig.keptReplays), andthe browser is asked to keep local games behind an explanation, once, rather
than silently as a game is created.
Verification
./tool/check.shpasses fordart,flutter,shell,docs,server,web, andscaffold web, which now builds a scaffolded game and its serviceworker end to end.
Browser behaviour is not in CI and was checked in headless Chrome 153:
every other host unresolvable, plus an offline
/game/:idlink;served;
closes, and reopening after the only tab closed;
through the replica host.
Follow-ups
device; the reload offer above is what the app does if it happens.
the web assets in the same change.
accounts.google.com/gsi/clientstill loads on web, pulled in by the Androidsign-in dependency and unused there. Left as is deliberately: removing it
would mean dropping
google_sign_inand moving Android off its nativeaccount picker.
🤖 Generated with Claude Code