Skip to content

feat(client): stale-PWA guard — fingerprint and auto-reload - #163

Open
jonocodes wants to merge 1 commit into
mainfrom
feat/client-freshness
Open

jonocodes wants to merge 1 commit into
mainfrom
feat/client-freshness

Conversation

@jonocodes

Copy link
Copy Markdown
Owner

Summary

Installed PWA clients can no longer get stuck on a stale bundle. The daemon fingerprints its client dist into /health, serves the shell uncached (no-store), keeps hashed assets immutable, and the client auto-reloads when the fingerprint changes.

Changes

Client side:

  • client/src/update-check.ts: New module that polls /health every 30s (and on resume events like pageshow or online) comparing the daemon's client_build fingerprint against stored state. Reloads when the fingerprint changes.
  • client/src/update-check.test.ts: Comprehensive unit tests for the update probe logic; probeForUpdate is pure so the decision table is testable without a daemon.
  • client/src/main.tsx: Start the update check on app load.
  • client/src/socket.ts: Export resolve_health_url() so update-check can reach the health endpoint.

Daemon side:

  • daemon/deckd/__main__.py:
    • _client_build_id(dist): Hash every file in the client dist (relative path + bytes) into a stable 12-char fingerprint. Rebuilt bundle always yields a new id; computed once per daemon start.
    • _client_cache_headers: Middleware that sets Cache-Control: no-store on shell files and public, max-age=31536000, immutable on Vite's content-hashed /assets/*.
    • _add_client_routes(server, dist): Refactored SPA route setup. Fixes a regression in the fallback regex (was anchoring ^ inside the named group, breaking deep-link reloads).
  • daemon/deckd/server.py: Store and expose client_build on /health (absent when no --client-dist, dev server's "don't check" signal).
  • tests/conftest.py: Add client_build param to test server.
  • tests/test_client_serving.py: Test fingerprinting, cache headers, deep-link SPA fallback, and /health response.

Documentation:

  • docs/adr/0011-client-freshness.md: ADR explaining the design, decisions (no service worker, no-store shell, fingerprinted bundle), and out-of-scope items (protocol negotiation, offline mode, affordances).
  • docs/GUIDE.md: Developer guidance on the freshness mechanism.
  • docs/adr/README.md: Index entry for ADR-0011.
  • README.md: PWA feature now links to ADR-0011.

How it works

  1. Daemon hashes the client dist on startup → client_build fingerprint, exposed on /health.
  2. Shell and manifest answer Cache-Control: no-store; client always fetches fresh.
  3. Hashed assets are immutable and cacheable for a year.
  4. Running client polls /health every 30s and on resume (pageshow, online, visibilitychange).
  5. If client_build differs from stored state, the client records the new value before reloading (so a blocked reload can't loop) and reloads.
  6. Any unreadable state (offline, demo mode, no client_build field) is treated as "don't check".

Result

A phone gets a fresh shell within 30s of a daemon rebuild + restart. Installed PWAs resume to a live bundle instead of a stale one.

…to-reload

An installed PWA could keep driving an old bundle after the daemon was
rebuilt and restarted. The daemon now fingerprints client-dist at startup
and reports it as client_build on /health; the SPA shell, manifest, and
icon answer Cache-Control: no-store while Vite's content-hashed /assets/*
stay immutable. The client remembers the fingerprint and re-checks on an
interval plus pageshow/visibilitychange/online, reloading when it moves.

Also repairs the SPA fallback regex: its ^ anchor sat inside the named
group, so it never matched and every extension-less deep link (/settings,
/editor, ...) 404'd on the daemon-served client. An auto-reload at a view
path would have hit that immediately; the fallback now admits dot-free
paths only, leaving dotted files to the static handler.

See docs/adr/0011-client-freshness.md.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant