From 124ba0dbe574e9c993917cf5333d94db82a5eefe Mon Sep 17 00:00:00 2001 From: OpenClaw Sub-agent Date: Mon, 3 Aug 2026 19:24:28 +1000 Subject: [PATCH] fix: stop iOS Safari heuristic-caching stale UI pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User reported 'still broken' on /ui/tags/ai/ right after the 20-card cap shipped — the deployed HTML was correct (verified in-pod: 20 cards + View all 363 posts) but Safari was serving a heuristically cached copy of the previous template. UI HTML responses had no Cache-Control at all. - core/middleware.NoCacheUiPagesMiddleware: /ui/* HTML → Cache-Control: no-cache, no-store, must-revalidate (+ Pragma/Expires). API untouched. - tag_detail: defensive width:100% + box-sizing on .tags-page/.apple-card --- core/middleware.py | 21 +++++++++++++++++++++ core/settings.py | 1 + links/templates/links/tag_detail.html | 2 ++ 3 files changed, 24 insertions(+) diff --git a/core/middleware.py b/core/middleware.py index 9a75b9f..acf573c 100644 --- a/core/middleware.py +++ b/core/middleware.py @@ -3,6 +3,27 @@ from django.urls import resolve from django.utils import translation from django.middleware.locale import LocaleMiddleware + +class NoCacheUiPagesMiddleware: + """Never let browsers (esp. iOS Safari's heuristic cache) serve stale UI pages. + + Deployments change templates frequently; without an explicit Cache-Control + Safari may cache the HTML heuristically and users keep seeing the previous + version even after a fix ships ("did you fix it? still broken"). UI pages + are cheap to render; always fetch fresh. + """ + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + response = self.get_response(request) + if request.path.startswith('/ui/') and response.get('Content-Type', '').startswith('text/html'): + response['Cache-Control'] = 'no-cache, no-store, must-revalidate' + response['Pragma'] = 'no-cache' + response['Expires'] = '0' + return response + + class CustomLocaleMiddleware(LocaleMiddleware): def process_request(self, request): url_path = request.path_info.lstrip('/') diff --git a/core/settings.py b/core/settings.py index 44931cd..d1ed264 100644 --- a/core/settings.py +++ b/core/settings.py @@ -190,6 +190,7 @@ LOCALE_INDEPENDENT_PATHS = [ MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', + 'core.middleware.NoCacheUiPagesMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'core.middleware.CustomLocaleMiddleware', # 使用自定义中间件 'django.middleware.common.CommonMiddleware', diff --git a/links/templates/links/tag_detail.html b/links/templates/links/tag_detail.html index c6753df..721d73c 100644 --- a/links/templates/links/tag_detail.html +++ b/links/templates/links/tag_detail.html @@ -13,12 +13,14 @@ .tags-page { max-width: 1080px; margin: 0 auto; padding: 2rem 1.25rem 3rem; color: var(--apple-text); + width: 100%; box-sizing: border-box; } .apple-card { background: #fff; border-radius: var(--apple-radius-lg); border: 1px solid rgba(0, 0, 0, 0.05); box-shadow: 0 4px 18px rgba(0, 0, 0, 0.05); padding: 1.5rem; margin-bottom: 1.25rem; + width: 100%; box-sizing: border-box; } /* ── Tag header ── */