Skip to content

deploy: no-cache index.html/sw.js/version.json under /sundial/ (fixes #37) - #39

Merged
max-tet merged 5 commits into
mainfrom
fix/deploy-nginx-no-cache
Oct 14, 2026
Merged

max-tet merged 5 commits into
mainfrom
fix/deploy-nginx-no-cache

Conversation

@ClaydeCode

Copy link
Copy Markdown
Contributor

What & why

Fixes #37. The /sundial/ coexistence config (deploy/nginx-sundial.conf, the path added to the Android home screen) carried none of the Cache-Control: no-cache rules that data/nginx.conf ships for index.html, sw.js and version.json. Those files fell back to default nginx headers and heuristic browser caching, so the PWA "update" button's location.reload() could be satisfied from the browser's own cache on Firefox Android — reloading the old version.

The fix mirrors data/nginx.conf's no-cache block, nested inside location /sundial/ so expires -1 applies while the block still inherits the subpath's security add_header set (nginx drops inherited add_header only from a block that declares its own — the nested block declares only expires).

Verification

Beyond the suite, I ran a real nginx:alpine with this config over the repo files and confirmed via curl -D-:

  • /sundial/{index.html,sw.js,version.json} → Cache-Control: no-cache and all four security headers (CSP, X-Frame-Options, nosniff, Referrer-Policy) present (inheritance works).
  • /sundial/js/main.js and other assets → no forced no-cache (unchanged), headers still present.
  • /sundial/ and SPA fallback routes → resolve to index.html and inherit the no-cache rule.
  • nginx -t passes on both deploy/nginx-sundial.conf and data/nginx.conf.

Full suite green locally: unit 89/89 (incl. 3 new), e2e 45/45, check_i18n.py clean.

A new tests/unit/nginx-cache.test.js (sibling-style to csp.test.js) locks in: both confs no-cache the exact three files with expires -1; neither no-cache block sets its own add_header; and the deploy block is brace-nested inside location /sundial/ (not merely textually after it).

Scope

Per the issue, cache-busted asset filenames are a deliberate out-of-scope follow-up and are not included.

Out-of-scope observations (pre-existing, NOT fixed here — flagged for a human)

While reviewing, two pre-existing gaps surfaced that affect PWA correctness under /sundial/ but are unrelated to this caching bug and were left untouched to keep scope tight:

  1. deploy/nginx-sundial.conf is a partial mirror of data/nginx.conf — it lacks the two MIME default_type blocks (location = /manifest.webmanifest → application/manifest+json, location ~ \.mjs$ → application/javascript). The repo ships manifest.webmanifest and loads vendor/lean-qr.mjs as an ES module, so under /sundial/ these could be served as application/octet-stream (breaking the module load / QR view and the manifest type). Matters for PWA/QR under the subpath; a follow-up should add those two blocks.
  2. deploy/install-on-shard.sh does not pack sw.js or manifest.webmanifest (it tars index.html version.json css js vendor assets). So on a shard the new /sundial/sw.js no-cache rule is inert until the file is actually deployed, and PWA installability under /sundial/ (manifest + service worker) is itself incomplete. The Dockerfile (root / core-release) does copy both, so the root path is unaffected.

Both are worth a separate issue; neither blocks this fix (the two files that most matter for #37's reload — index.html and version.json — are packed and now no-cached).

Review panel

Sequential adversarial subagents, each given only the diff + issue:

  • Adversarial (general correctness) — no blocking findings. Verified nginx location precedence (nested regex is reached), header inheritance, exact regex scoping, SPA-fallback/301 intact, test soundness. Raised observation i18n: EN + DE (catalogs, t(), Intl, language switcher) #1 above (advisory, out of scope).
  • Test adversary — no blocking findings. Mutation-tested (all restored, tree verified clean): confirmed the tests fail on expires change, block removal, and add_header-in-block. Caught that the original nesting check used textual order, so a block moved to server level passed spuriously → fixed by asserting real brace containment (commit ae68018), plus added the same no-add_header guard for data/nginx.conf (advisory finding Automated tests + CI (72 tests: unit + Playwright e2e) #2).
  • DevEx / readability — no blocking findings. Advisory tidy-ups applied (commit on readability): whitespace-tolerant locator for the /sundial/ block, single NO_CACHE_BLOCK match reused, escaping comment. Kept the exact-three-files regex (deliberate, per the test adversary) and the test split (nesting + add_header are one inheritance invariant).

Lenses not run (triggers did not fire): Security (no auth/token/secret/input-parsing surface; the header-inheritance risk is covered by the adversarial lens, the live-nginx curl check, and the unit test), DB/migration, API-contract, UX.

🤖 Generated with Claude Code

ClaydeCode and others added 5 commits October 13, 2026 21:54
The /sundial/ coexistence config served none of the cache-control rules
data/nginx.conf carries, so the entry point, service worker and version
probe fell back to heuristic browser caching. Tapping "update" just calls
location.reload(), which on Firefox Android home-screen installs can be
satisfied from the browser cache — reloading the old version (#37).

Mirror data/nginx.conf's no-cache block, nested inside location /sundial/
so expires -1 applies while the block inherits the subpath's security
add_header set (nginx drops inherited add_header only from a block that
sets its own).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Static assertion (same style as csp.test.js) that data/nginx.conf and
deploy/nginx-sundial.conf both no-cache index.html, sw.js and version.json,
and that the deploy block stays nested under /sundial/ without its own
add_header so the security headers keep being inherited.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Record in deploy/README.md and agents.md that nginx-sundial.conf now
no-caches the three update-critical files, why it is nested, and that
tests/unit/nginx-cache.test.js enforces it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The nesting check used textual ordering, so a no-cache block moved out to
the server level (which would inherit no add_header and silently drop the
security headers for the three files) still passed. Match braces to assert
the block sits inside location /sundial/. Also guard data/nginx.conf's root
no-cache block against setting its own add_header, the same regression class.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Locate the /sundial/ block with a whitespace-tolerant regex instead of an
exact string, match NO_CACHE_BLOCK once (reuse .index + body) instead of
twice, and note what the \. escaping in the matcher means. No behaviour
change; the nesting mutation is still caught.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@max-tet
max-tet merged commit 5ba2147 into main Oct 14, 2026
3 checks passed
@max-tet
max-tet deleted the fix/deploy-nginx-no-cache branch October 14, 2026 06:04
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.

PWA update banner reloads to stale cached version on Firefox Android home-screen install

2 participants