Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
Commit2e32b5eunknown_key

Merge pull request #85 from ccantynz-alt/claude/fix-aa-loop-issue-PonMQ

Merge pull request #85 from ccantynz-alt/claude/fix-aa-loop-issue-PonMQ

Claude/fix aa loop issue pon mq
CC LABS App committed on May 16, 2026Parents: dcefebe 67f64a3
3 files changed+4792e32b5e3b69f9873c1540be60f532831016f1bad
3 changed files+47−9
Modifieddrizzle.config.ts+9−1View fileUnifiedSplit
11import { defineConfig } from "drizzle-kit";
22
3const databaseUrl = process.env.DATABASE_URL;
4if (!databaseUrl) {
5 throw new Error(
6 "DATABASE_URL is not set. Required for drizzle-kit (generate / migrate / studio). " +
7 "Set it in your environment or .env file before running drizzle-kit commands."
8 );
9}
10
311export default defineConfig({
412 schema: "./src/db/schema.ts",
513 out: "./drizzle",
614 dialect: "postgresql",
715 dbCredentials: {
8 url: process.env.DATABASE_URL!,
16 url: databaseUrl,
917 },
1018});
Modifiedsrc/__tests__/pwa.test.ts+11−0View fileUnifiedSplit
8787 expect(body).toContain("serviceWorker.register");
8888 expect(body).toContain("/sw.js");
8989 });
90
91 // AA-LOOP REGRESSION (2026-05-16): two SWs at the same scope = infinite
92 // reload. Layout MUST NOT auto-register `/sw-push.js`. The /sw-push.js
93 // route still exists for explicit-opt-in push subscriptions (gated to
94 // /settings) but no anonymous or default page may register it.
95 it("layout never auto-registers /sw-push.js at scope /", async () => {
96 const res = await app.request("/");
97 const body = await res.text();
98 expect(body).not.toContain("register('/sw-push.js'");
99 expect(body).not.toContain('register("/sw-push.js"');
100 });
90101});
Modifiedsrc/views/layout.tsx+27−8View fileUnifiedSplit
661661// 1. user is authenticated (gated server-side via the `{user &&}` JSX)
662662// 2. visit counter (localStorage) ≥ 3
663663// 3. dismissed-cooldown < 14 days ago
664// Also bootstraps the push + offline SW at /sw-push.js. Idempotent — safe
665// to load on every page; only inserts DOM nodes if all conditions match.
664//
665// AA-LOOP FIX (2026-05-16): this script previously also registered
666// `/sw-push.js` at scope `/`. Combined with `pwaRegisterScript` registering
667// `/sw.js` at the same scope, every page load alternated which SW
668// controlled the scope. `pwaRegisterScript`'s `updatefound → reload` hook
669// then fired on every subsequent load — infinite reload loop on the admin
670// dashboard (input wiped, deploy pill flashing). Per the SW spec, only one
671// SW can be active per scope; registering two different script URLs at the
672// same scope makes each replace the other.
673//
674// Fix: stop registering `/sw-push.js` from the banner script entirely, and
675// proactively unregister any existing `/sw-push.js` SW so users already
676// caught in the loop recover on next refresh. Push notifications opted in
677// via /settings still register the SW from settings.tsx when (and only
678// when) the user clicks Subscribe.
666679const pwaInstallBannerScript = `
667680(function(){
668681 try {
672685 var visits = parseInt(localStorage.getItem(VISITS_KEY) || '0', 10) || 0;
673686 visits++;
674687 try { localStorage.setItem(VISITS_KEY, String(visits)); } catch(_){}
675 // Bootstrap the push + offline SW (separate from /sw.js's locked behaviour).
676 if ('serviceWorker' in navigator) {
677 window.addEventListener('load', function(){
678 navigator.serviceWorker.register('/sw-push.js', { scope: '/' })
679 .catch(function(){});
680 });
688 // Recover stuck tabs: if a previous build registered /sw-push.js at
689 // scope '/', it is still fighting /sw.js for that scope. Unregister
690 // only the /sw-push.js registration; leave /sw.js intact.
691 if ('serviceWorker' in navigator && navigator.serviceWorker.getRegistrations) {
692 navigator.serviceWorker.getRegistrations().then(function(regs){
693 regs.forEach(function(reg){
694 var url = (reg.active && reg.active.scriptURL) || '';
695 if (url.indexOf('/sw-push.js') !== -1) {
696 try { reg.unregister(); } catch(_){}
697 }
698 });
699 }).catch(function(){});
681700 }
682701 var deferredPrompt = null;
683702 window.addEventListener('beforeinstallprompt', function(e){
684703