Refine dateline format; fix stale page caching breaking theme hot swap

Dateline is now '1 Jan 2026', with ' – edited 3 Jan 2026' appended only
when the last edit came at least 24h after publishing.

The Last-Modified header added heuristic browser caching of pages (no
Cache-Control was sent), so the site editor's re-fetch after a theme
change served the cached page with the old stylesheet link. Pages now
send 'Cache-Control: no-cache' — always revalidate, still cheap via the
ETag.
This commit is contained in:
2026-08-18 06:20:38 +00:00
parent 87c0aac3c0
commit 1c0ef315ff
2 changed files with 20 additions and 11 deletions
+13 -3
View File
@@ -668,13 +668,20 @@ async def show_page(request: Request, path: str) -> HTMLResponse | Response:
node = chain[-1] if chain else None
if node is not None and node.published and node.content is not None:
# ETag on content + render version; clients revalidate cheaply,
# which keeps prefetched pages warm and current.
# which keeps prefetched pages warm and current. no-cache forces
# that revalidation: with Last-Modified but no Cache-Control,
# browsers would otherwise cache heuristically and serve stale
# pages (e.g. after a theme change) without asking us at all.
etag = f'"{path}@{node.modified.timestamp()}v{data.version}"'
if request.headers.get("if-none-match") == etag:
return Response(status_code=304)
return HTMLResponse(
views.render_page(data.menu, path, data.brand, data.custom_css, data.theme, data.favicon),
headers={"etag": etag, "last-modified": _http_date(node.modified)},
headers={
"etag": etag,
"last-modified": _http_date(node.modified),
"cache-control": "no-cache",
},
)
if node is not None and node.published and node.content is None:
# Category label without a landing page: placeholder with the pen
@@ -682,7 +689,10 @@ async def show_page(request: Request, path: str) -> HTMLResponse | Response:
return HTMLResponse(
views.render_category(data.menu, path, data.brand, data.custom_css, data.theme, data.favicon),
404,
headers={"last-modified": _http_date(node.modified)},
headers={
"last-modified": _http_date(node.modified),
"cache-control": "no-cache",
},
)
if node is None and not path:
# No front page (no top-level node with slug ""): "/" opens the
+7 -8
View File
@@ -20,7 +20,7 @@ classes, e.g. `![alt](photo.avif "Caption"){.right}`.
"""
import re
from datetime import datetime
from datetime import datetime, timedelta
from markdown_it import MarkdownIt
from markdown_it.common.utils import escapeHtml
@@ -156,13 +156,12 @@ def render(
def _dateline(created: datetime, modified: datetime | None) -> str:
"""Published/updated line for the ``{dates}`` tag. The updated date is
shown only when it falls on a later day than the publication."""
pub = f'<time datetime="{created.isoformat()}">{created.day} {created:%B %Y}</time>'
out = f"Published {pub}"
if modified is not None and modified.date() > created.date():
upd = f'<time datetime="{modified.isoformat()}">{modified.day} {modified:%B %Y}</time>'
out += f", updated {upd}"
"""Dateline for the ``{dates}`` tag: "1 Jan 2026", plus
" edited 3 Jan 2026" when the last edit came >= 24h after
publishing (quick fixes right after posting stay unmentioned)."""
out = f'<time datetime="{created.isoformat()}">{created.day} {created:%b %Y}</time>'
if modified is not None and modified - created >= timedelta(hours=24):
out += f' edited <time datetime="{modified.isoformat()}">{modified.day} {modified:%b %Y}</time>'
return f'<p class="dateline">{out}</p>'