nandi/frqpublic Fork
⑂ 1c7cb6d
Commits
⬇ Clone ▾
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

Removing the worker, not replacing it with an empty one

A correction to the last commit. It said that with no
`flutter_service_worker.js` to fetch, a browser update check would 404
and drop the registration. `--pwa-strategy=none` does still emit that
file -- empty, a 200 of zero bytes -- so a browser holding the old
worker replaces it with a no-op rather than losing it, and waits its
turn to do even that. A no-op registration is still a registration.

So the page removes it: `index.html` unregisters any worker and drops
any cache it left. Nothing is lost with them, this app having no
offline worth the name, and what they cost was a deploy that would
not arrive.

What is verified and what is not. A fresh load registers nothing --
checked in a browser against the built bundle: no registrations, no
controller, no caches. The deployed origin now answers `cache-control:
no-cache` and 304s a conditional request, both checked against the
live URL. The cleanup path itself is not: the preview browser refuses
to register a worker over this local server at all, so there was no
old one to remove. It is wrapped in catch either way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-09-20T15:56:19-07:00 Browse files
1c7cb6d parent: 364ea65
modified .modal/web/Dockerfile +3 -1
@@ -32,7 +32,9 @@ COPY . .
3232 # and is not worth an install for one call. Inlined means duplicated, so the
3333 # flags have to be kept in step with that recipe -- `--pwa-strategy=none`
3434 # below is one of them, and the deployment is the place it matters most,
35-# being the one people reach for by URL. The core is compiled to
35+# being the one people reach for by URL. See that recipe for what it does
36+# and what it does not: an already-installed worker is replaced by an empty
37+# one rather than removed, and `flutter/web/index.html` removes it. The core is compiled to
3638 # JavaScript and copied into `flutter/web/` rather than into the output,
3739 # because `flutter build web` copies that directory into the bundle -- which
3840 # is what makes the page's `<script src="frq_core.js">` resolve the same
@@ -32,7 +32,9 @@ COPY . .
32 # and is not worth an install for one call. Inlined means duplicated, so the32 # and is not worth an install for one call. Inlined means duplicated, so the
33 # flags have to be kept in step with that recipe -- `--pwa-strategy=none`33 # flags have to be kept in step with that recipe -- `--pwa-strategy=none`
34 # below is one of them, and the deployment is the place it matters most,34 # below is one of them, and the deployment is the place it matters most,
35-# being the one people reach for by URL. The core is compiled to35+# being the one people reach for by URL. See that recipe for what it does
36+# and what it does not: an already-installed worker is replaced by an empty
37+# one rather than removed, and `flutter/web/index.html` removes it. The core is compiled to
36 # JavaScript and copied into `flutter/web/` rather than into the output,38 # JavaScript and copied into `flutter/web/` rather than into the output,
37 # because `flutter build web` copies that directory into the bundle -- which39 # because `flutter build web` copies that directory into the bundle -- which
38 # is what makes the page's `<script src="frq_core.js">` resolve the same40 # is what makes the page's `<script src="frq_core.js">` resolve the same
modified flutter/web/index.html +28 -0
@@ -37,6 +37,34 @@
3737 uses it, which this page needs because freeq's broker will only redirect
3838 back to loopback or its own origins — and this is neither.
3939 -->
40+ <!--
41+ No service worker, and no leftovers of one.
42+
43+ The bundle is built `--pwa-strategy=none`, so nothing here registers
44+ one any more. That settles a new visitor, but not a browser that
45+ installed one before: `none` still emits `flutter_service_worker.js` as
46+ an empty file, so the old worker is replaced by a no-op rather than
47+ removed, and a no-op registration is still a registration.
48+
49+ This removes it. The app is a window onto a live IRC connection and has
50+ no offline worth the name, so nothing is lost with the caches -- and
51+ what they cost was a deploy that would not arrive: a new worker waits
52+ until every tab on the origin is closed, so reloading went on serving
53+ the previous bundle and the cure was clearing the site data by hand.
54+ -->
55+ <script>
56+ if ("serviceWorker" in navigator) {
57+ navigator.serviceWorker.getRegistrations()
58+ .then(function (rs) { rs.forEach(function (r) { r.unregister(); }); })
59+ .catch(function () { /* nothing to undo */ });
60+ }
61+ if (window.caches && caches.keys) {
62+ caches.keys()
63+ .then(function (ks) { ks.forEach(function (k) { caches.delete(k); }); })
64+ .catch(function () { /* nothing to undo */ });
65+ }
66+ </script>
67+
4068 <script src="frq_core.js"></script>
4169 <script src="frq_dpop.js"></script>
4270 <script src="frq_oauth.js"></script>
@@ -37,6 +37,34 @@
37 uses it, which this page needs because freeq's broker will only redirect37 uses it, which this page needs because freeq's broker will only redirect
38 back to loopback or its own origins — and this is neither.38 back to loopback or its own origins — and this is neither.
39 -->39 -->
40+ <!--
41+ No service worker, and no leftovers of one.
42+
43+ The bundle is built `--pwa-strategy=none`, so nothing here registers
44+ one any more. That settles a new visitor, but not a browser that
45+ installed one before: `none` still emits `flutter_service_worker.js` as
46+ an empty file, so the old worker is replaced by a no-op rather than
47+ removed, and a no-op registration is still a registration.
48+
49+ This removes it. The app is a window onto a live IRC connection and has
50+ no offline worth the name, so nothing is lost with the caches -- and
51+ what they cost was a deploy that would not arrive: a new worker waits
52+ until every tab on the origin is closed, so reloading went on serving
53+ the previous bundle and the cure was clearing the site data by hand.
54+ -->
55+ <script>
56+ if ("serviceWorker" in navigator) {
57+ navigator.serviceWorker.getRegistrations()
58+ .then(function (rs) { rs.forEach(function (r) { r.unregister(); }); })
59+ .catch(function () { /* nothing to undo */ });
60+ }
61+ if (window.caches && caches.keys) {
62+ caches.keys()
63+ .then(function (ks) { ks.forEach(function (k) { caches.delete(k); }); })
64+ .catch(function () { /* nothing to undo */ });
65+ }
66+ </script>
67+
40 <script src="frq_core.js"></script>68 <script src="frq_core.js"></script>
41 <script src="frq_dpop.js"></script>69 <script src="frq_dpop.js"></script>
42 <script src="frq_oauth.js"></script>70 <script src="frq_oauth.js"></script>
modified justfile +7 -3
@@ -203,9 +203,13 @@ _web-bundle:
203203 # the only cure was clearing the site data by hand. Twice in one
204204 # afternoon a fix looked broken because of it.
205205 #
206- # It also retires the workers already installed out there: with no
207- # `flutter_service_worker.js` left to fetch, a browser update check 404s
208- # and the registration is dropped.
206+ # For workers already installed out there it is less immediate than it
207+ # looks. `none` still emits `flutter_service_worker.js`, empty -- a 200
208+ # of zero bytes rather than a 404 -- so a browser holding the old one
209+ # fetches it, sees it changed, and installs the empty worker, which
210+ # waits its turn like any other. It has no fetch handler once it takes
211+ # over, so nothing is served from a cache after that; and the page
212+ # unregisters it outright, which is what `index.html` does.
209213 #
210214 # The comment lives out here rather than inside the quoted script below,
211215 # which is single-quoted -- an apostrophe in there ends the string, and
@@ -203,9 +203,13 @@ _web-bundle:
203 # the only cure was clearing the site data by hand. Twice in one203 # the only cure was clearing the site data by hand. Twice in one
204 # afternoon a fix looked broken because of it.204 # afternoon a fix looked broken because of it.
205 #205 #
206- # It also retires the workers already installed out there: with no206+ # For workers already installed out there it is less immediate than it
207- # `flutter_service_worker.js` left to fetch, a browser update check 404s207+ # looks. `none` still emits `flutter_service_worker.js`, empty -- a 200
208- # and the registration is dropped.208+ # of zero bytes rather than a 404 -- so a browser holding the old one
209+ # fetches it, sees it changed, and installs the empty worker, which
210+ # waits its turn like any other. It has no fetch handler once it takes
211+ # over, so nothing is served from a cache after that; and the page
212+ # unregisters it outright, which is what `index.html` does.
209 #213 #
210 # The comment lives out here rather than inside the quoted script below,214 # The comment lives out here rather than inside the quoted script below,
211 # which is single-quoted -- an apostrophe in there ends the string, and215 # which is single-quoted -- an apostrophe in there ends the string, and