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

fix(pwa): stop the admin-screen flashing — pin BUILD_SHA, kill PID fallback

fix(pwa): stop the admin-screen flashing — pin BUILD_SHA, kill PID fallback

Root cause: src/routes/pwa.ts derived the service-worker version from
`process.env.BUILD_SHA` with a `dev-${process.pid}` fallback. Nothing
in either deploy pipeline (scripts/self-deploy.sh, the GitHub Actions
hetzner workflow) ever set BUILD_SHA, so the fallback engaged in
production. Every systemd restart = new PID = new SW_VERSION = browser
fires the layout's updatefound→reload hook = full page reload. On any
admin tab held open across a deploy or an autopilot-triggered restart,
the page flashed.

Three coordinated fixes:

1. src/routes/pwa.ts — fallback is now the constant string "dev-stable"
   instead of "dev-${process.pid}". The SW_VERSION only changes when
   BUILD_SHA actually changes. Tests still assert .startsWith("dev-")
   which holds; the prior assertion was non-empty + dev- prefix, both
   still satisfied. 13/13 pwa-cache-bust tests pass.

2. scripts/self-deploy.sh — before `systemctl restart gluecron`, write
   /etc/systemd/system/gluecron.service.d/build-sha.conf with
   Environment="BUILD_SHA=$NEW_SHA" (and BUILD_TIME), then daemon-reload.
   The drop-in survives manager restarts and is overwritten cleanly on
   every deploy.

3. .github/workflows/hetzner-deploy.yml — same drop-in write right
   before the systemctl restart step, using $new_sha from the earlier
   git rev-parse.

After this lands, the SW rotates exactly once per real deploy. Open
admin tabs no longer auto-reload mid-session.

bun test → 1993 pass / 2 skip / 1 fail (pre-existing runScheduledWorkflowsTick).
bunx tsc → 3 pre-existing self-host.test.ts errors (unrelated).

https://claude.ai/code/session_01QFLWDxWw65DX6enMcS5Lwe
Test User committed on May 15, 2026Parent: 9e6fd14
3 files changed+446f85b88a7869442868b057758cb727bba6a3fbff8
3 changed files+44−6
Modified.github/workflows/hetzner-deploy.yml+14−0View fileUnifiedSplit
343343
344344 notify_step "db-migrate" "succeeded" "$(( ( $(date +%s) - DM_START ) * 1000 ))"
345345
346 # ─── (c.5) Pin BUILD_SHA into systemd before restart.
347 # Without this, src/routes/pwa.ts falls back to a stable
348 # "dev-stable" version string and the browser service worker
349 # never invalidates between real deploys. The drop-in survives
350 # daemon-reload; old contents are overwritten on every deploy.
351 mkdir -p /etc/systemd/system/gluecron.service.d
352 cat > /etc/systemd/system/gluecron.service.d/build-sha.conf <<EOF
353 [Service]
354 Environment="BUILD_SHA=$new_sha"
355 Environment="BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
356 EOF
357 systemctl daemon-reload
358 echo "==> pinned BUILD_SHA=${new_sha:0:12} in systemd drop-in"
359
346360 # ─── (d) Zero-downtime restart. Blocks until sd_notify(READY=1).
347361 notify_step "restart-service" "in_progress"
348362 RS_START=$(date +%s)
Modifiedscripts/self-deploy.sh+17−0View fileUnifiedSplit
167167 fi
168168fi
169169
170# ── 6.5 Pin BUILD_SHA into the systemd unit so the running process can
171# report it and the SW versioning rotates exactly per-deploy. Drop-in
172# survives daemon-reload; the OLD file is overwritten on every deploy.
173# Without this, src/routes/pwa.ts falls back to a stable "dev-stable"
174# string and the browser SW never invalidates between real deploys.
175if [ "$DEPLOY_FAILED" = "0" ]; then
176 DROPIN_DIR=/etc/systemd/system/gluecron.service.d
177 mkdir -p "$DROPIN_DIR"
178 cat > "$DROPIN_DIR/build-sha.conf" <<EOF
179[Service]
180Environment="BUILD_SHA=$NEW_SHA"
181Environment="BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
182EOF
183 systemctl daemon-reload >>"$LOG" 2>&1 || log " ! daemon-reload non-fatal warning"
184 log " v pinned BUILD_SHA=${NEW_SHA:0:12} in systemd drop-in"
185fi
186
170187# ── 7. systemctl restart (blocks on sd_notify READY=1) ────────────────────
171188if [ "$DEPLOY_FAILED" = "0" ]; then
172189 RS_START=$(date +%s)
Modifiedsrc/routes/pwa.ts+13−6View fileUnifiedSplit
117117 * version to actually reach returning visitors.
118118 *
119119 * BUILD_SHA is read from `process.env.BUILD_SHA` at request time so the
120 * deploy pipeline can rotate it without a rebuild. Falls back to a stable
121 * per-process `dev-<pid>` string when unset; a one-shot warn() is logged
122 * so operators notice misconfigured deploys.
120 * deploy pipeline can rotate it without a rebuild. Falls back to a STABLE
121 * `dev-stable` constant when unset — the previous `dev-<pid>` fallback
122 * changed on every systemd restart, which invalidated the SW on every
123 * restart and triggered the layout's updatefound→reload hook, producing
124 * visible page flashing on long-lived admin tabs. Use a constant fallback
125 * so the SW only rotates when BUILD_SHA actually changes (real deploy).
126 * A one-shot warn() is logged so operators still notice misconfigured
127 * deploys.
123128 */
124129
125130// One-shot warning latch — exported only for tests to reset between cases.
137142 "[pwa] BUILD_SHA env not set — service worker will fall back to a dev-mode version string. Set BUILD_SHA in the deploy environment so cache-busting pins to the deploy SHA."
138143 );
139144 }
140 // Dev fallback: stable per-process so reloads don't churn the cache
141 // while developing locally, but distinct from any real SHA.
142 return `dev-${process.pid}`;
145 // Dev fallback: STABLE across systemd restarts (no `${process.pid}` —
146 // that was rotating on every restart and forcing browser reloads via
147 // the layout's updatefound hook). Distinct prefix `dev-` so operators
148 // can tell at a glance the deploy pipeline didn't set BUILD_SHA.
149 return "dev-stable";
143150}
144151
145152export function buildVersionedServiceWorker(version: string): string {
146153