mirror of
https://github.com/kbenestad/mdcms.git
synced 2026-06-18 15:24:32 +00:00
Fix two bugs: SPA-routing page load failure and stale service worker
fetchPageFile now rejects text/html responses so servers with SPA routing (e.g. Cloudflare Pages with /* /index.html 200) no longer trick the renderer into treating a fallback index.html as a found markdown file. Category-variant pages (page.current.md with no plain page.md) now fall through correctly to their variant URL. mdcms build now writes a self-unregistering service-worker.js when pwa: no, evicting any stale caching worker left over from a previous pwa: yes build. manifest.json is also removed when pwa: no. https://claude.ai/code/session_01Xs5GyREFhjWxhS1UhW2wA8
This commit is contained in:
parent
0bf8cf319b
commit
b9410d4b88
4 changed files with 71 additions and 2 deletions
|
|
@ -1135,11 +1135,19 @@ body {
|
||||||
if (b) b.remove();
|
if (b) b.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _isMdResponse(r) {
|
||||||
|
// Reject HTML responses — servers with SPA routing (e.g. Cloudflare Pages with
|
||||||
|
// "/* /index.html 200") return index.html with 200 for missing files, which would
|
||||||
|
// be mistaken for a found markdown file.
|
||||||
|
const ct = r.headers.get('content-type') || '';
|
||||||
|
return !ct.startsWith('text/html');
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchPageFile(conceptualFile) {
|
async function fetchPageFile(conceptualFile) {
|
||||||
// conceptualFile like "pages/foo.md". Returns { ok, text, resolvedFile } or { ok: false }.
|
// conceptualFile like "pages/foo.md". Returns { ok, text, resolvedFile } or { ok: false }.
|
||||||
if (!categoriesUse) {
|
if (!categoriesUse) {
|
||||||
const r = await fetch(conceptualFile);
|
const r = await fetch(conceptualFile);
|
||||||
if (r.ok) return { ok: true, text: await r.text(), resolvedFile: conceptualFile };
|
if (r.ok && _isMdResponse(r)) return { ok: true, text: await r.text(), resolvedFile: conceptualFile };
|
||||||
return { ok: false };
|
return { ok: false };
|
||||||
}
|
}
|
||||||
const base = conceptualFile.replace(/\.md$/, '');
|
const base = conceptualFile.replace(/\.md$/, '');
|
||||||
|
|
@ -1169,7 +1177,7 @@ body {
|
||||||
if (seen.has(url)) continue;
|
if (seen.has(url)) continue;
|
||||||
seen.add(url);
|
seen.add(url);
|
||||||
const r = await fetch(url);
|
const r = await fetch(url);
|
||||||
if (r.ok) return { ok: true, text: await r.text(), resolvedFile: url };
|
if (r.ok && _isMdResponse(r)) return { ok: true, text: await r.text(), resolvedFile: url };
|
||||||
}
|
}
|
||||||
return { ok: false };
|
return { ok: false };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,26 @@ Bugs that have been identified but not yet fixed. Fixed bugs are moved to the re
|
||||||
|
|
||||||
## Fixed in development (not yet released)
|
## Fixed in development (not yet released)
|
||||||
|
|
||||||
|
### Category-variant pages fail to load on servers with SPA routing
|
||||||
|
|
||||||
|
**Symptom:** On Cloudflare Pages (and any other server configured to serve `index.html` with HTTP 200 for missing paths), clicking a nav item whose page only exists as a category-variant file (e.g. `page.current.md`, no plain `page.md`) showed garbled content — the raw HTML of `index.html` rendered as markdown, with the site's `<title>` text visible in the content area.
|
||||||
|
|
||||||
|
**Root cause:** `fetchPageFile` tried the base filename (`pages/page.md`) first. Servers with SPA routing return this with HTTP 200 (serving `index.html`), so `r.ok` was true and the function returned without trying the actual variant file (`pages/page.current.md`).
|
||||||
|
|
||||||
|
**Fix:** `fetchPageFile` now checks the `Content-Type` response header and skips any response with `text/html`, continuing to the next candidate URL.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Stale service worker not removed when `pwa: no`
|
||||||
|
|
||||||
|
**Symptom:** After changing a site from `pwa: yes` to `pwa: no` and rebuilding, the old service worker remained active in browsers that had previously visited the site. Cached responses from the old build continued to be served.
|
||||||
|
|
||||||
|
**Root cause:** `mdcms build` stopped generating PWA files when `pwa: no`, but `index.html` unconditionally registers `service-worker.js` on every page load. With no new SW to replace it, the old worker stayed installed indefinitely.
|
||||||
|
|
||||||
|
**Fix:** `mdcms build` now writes a self-unregistering stub `service-worker.js` when `pwa: no`. On the visitor's next visit, the browser installs the stub which immediately calls `self.registration.unregister()`, evicting the stale worker. `manifest.json` is also deleted if present.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### `config.yml` YAML parse errors were silently swallowed
|
### `config.yml` YAML parse errors were silently swallowed
|
||||||
|
|
||||||
**Symptom:** A malformed `config.yml` (e.g. a stray tab character, which YAML forbids as a token starter) caused `read_config` to catch the `YAMLError` and return an empty dict. The build would proceed with no config — categories disabled, no default category code — producing a `nav.yml` that omitted `variants` fields and listed category variant files (e.g. `page.current.md`) as plain pages. Pages with category variants would not appear in the sidebar.
|
**Symptom:** A malformed `config.yml` (e.g. a stray tab character, which YAML forbids as a token starter) caused `read_config` to catch the `YAMLError` and return an empty dict. The build would proceed with no config — categories disabled, no default category code — producing a `nav.yml` that omitted `variants` fields and listed category variant files (e.g. `page.current.md`) as plain pages. Pages with category variants would not appear in the sidebar.
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,22 @@ A rebuild is required for existing sites to pick up the change.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Fix: category-variant pages fail to load on servers with SPA routing (e.g. Cloudflare Pages)
|
||||||
|
|
||||||
|
When a site uses category-suffixed page files (e.g. `page.current.md`) and is hosted on a server configured with SPA fallback routing (serving `index.html` with HTTP 200 for any unknown path), the renderer's `fetchPageFile` mistook the HTML fallback for a found markdown file. It returned `index.html` content instead of falling through to try the `.current.md` variant. The page rendered the raw HTML of `index.html` as markdown, showing the `<title>` text (`sitename`) in the content area.
|
||||||
|
|
||||||
|
`fetchPageFile` now checks the `Content-Type` response header and rejects any response with `text/html`, continuing to the next candidate URL instead.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fix: stale service worker not removed when `pwa: no`
|
||||||
|
|
||||||
|
`index.html` unconditionally registers `service-worker.js` on every page load. When a site switched from `pwa: yes` to `pwa: no`, `mdcms build` stopped generating a new service worker, but the old one remained active in browsers that had visited the site before. The stale worker continued to serve cached responses from the old build.
|
||||||
|
|
||||||
|
`mdcms build` now writes a self-unregistering `service-worker.js` when `pwa: no`. On the visitor's next page load, the browser installs this stub worker, which immediately unregisters itself and evicts any previously cached content. `manifest.json` is also removed if present.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Fix: `config.yml` YAML parse errors now abort the build with a clear message
|
## Fix: `config.yml` YAML parse errors now abort the build with a clear message
|
||||||
|
|
||||||
A malformed `config.yml` (e.g. a stray tab character, which YAML forbids as a token starter) previously caused `read_config` to silently return an empty dict. The build would proceed with no config — categories disabled, no default category code — producing a broken `nav.yml` with wrong filenames and missing `variants` fields, so category-variant pages would not appear in the sidebar.
|
A malformed `config.yml` (e.g. a stray tab character, which YAML forbids as a token starter) previously caused `read_config` to silently return an empty dict. The build would proceed with no config — categories disabled, no default category code — producing a broken `nav.yml` with wrong filenames and missing `variants` fields, so category-variant pages would not appear in the sidebar.
|
||||||
|
|
|
||||||
25
mdcms.py
25
mdcms.py
|
|
@ -528,6 +528,8 @@ def run_build(site_path: Path):
|
||||||
pwa_enabled = str(cfg.get("pwa", "no")).lower() in ("yes", "true")
|
pwa_enabled = str(cfg.get("pwa", "no")).lower() in ("yes", "true")
|
||||||
if pwa_enabled:
|
if pwa_enabled:
|
||||||
generate_pwa(site_path, cfg)
|
generate_pwa(site_path, cfg)
|
||||||
|
else:
|
||||||
|
cleanup_pwa(site_path)
|
||||||
|
|
||||||
asset_warnings = validate_assets(site_path, cfg)
|
asset_warnings = validate_assets(site_path, cfg)
|
||||||
for w in asset_warnings:
|
for w in asset_warnings:
|
||||||
|
|
@ -543,6 +545,29 @@ def run_build(site_path: Path):
|
||||||
|
|
||||||
# ─── PWA generation ───────────────────────────────────────────
|
# ─── PWA generation ───────────────────────────────────────────
|
||||||
|
|
||||||
|
def cleanup_pwa(site_path: Path):
|
||||||
|
"""When pwa: no, write a self-unregistering service worker and remove manifest.json.
|
||||||
|
|
||||||
|
Browsers keep the previously installed service worker active until a new one is
|
||||||
|
installed. Writing a stub that immediately unregisters itself ensures any stale
|
||||||
|
caching worker is evicted on the next visit after a pwa: yes → pwa: no change.
|
||||||
|
"""
|
||||||
|
sw = site_path / "service-worker.js"
|
||||||
|
sw.write_text(
|
||||||
|
"// mdcms: PWA disabled — unregisters any previously installed service worker.\n"
|
||||||
|
"self.addEventListener('install', () => self.skipWaiting());\n"
|
||||||
|
"self.addEventListener('activate', event => {\n"
|
||||||
|
" event.waitUntil(self.registration.unregister());\n"
|
||||||
|
"});\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
manifest = site_path / "manifest.json"
|
||||||
|
if manifest.exists():
|
||||||
|
manifest.unlink()
|
||||||
|
click.echo(" Removed manifest.json (pwa: no)")
|
||||||
|
click.echo(" Wrote service-worker.js (self-unregistering stub, pwa: no)")
|
||||||
|
|
||||||
|
|
||||||
def generate_pwa(site_path: Path, cfg: dict):
|
def generate_pwa(site_path: Path, cfg: dict):
|
||||||
"""Generate manifest.json and service-worker.js when pwa: yes."""
|
"""Generate manifest.json and service-worker.js when pwa: yes."""
|
||||||
pwa_name = cfg.get("pwa-name", cfg.get("sitename", "MD-CMS Site"))
|
pwa_name = cfg.get("pwa-name", cfg.get("sitename", "MD-CMS Site"))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue