From 1cd4fc8b4029b47cb7a22d5b6cbbf8753c17338a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 15:56:18 +0000 Subject: [PATCH 1/3] Initial plan From 3745eedd74d5760e2a398559e0a48646aa093007 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 15:59:50 +0000 Subject: [PATCH 2/3] Add unified template asset cache-busting version --- app/sovran_systemsos_web/server.py | 36 ++++++++++++++- app/sovran_systemsos_web/templates/index.html | 44 +++++++++---------- app/sovran_systemsos_web/templates/login.html | 4 +- 3 files changed, 59 insertions(+), 25 deletions(-) diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index b82820b..39aeb48 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -652,6 +652,35 @@ templates = Jinja2Templates(directory=os.path.join(_BASE_DIR, "templates")) # ── Static asset cache-busting ──────────────────────────────────── +def _compute_asset_version() -> str: + """Return a stable asset version string for cache-busting template assets.""" + nix_match = re.search(r"/nix/store/([a-z0-9]{32})-", os.path.realpath(_BASE_DIR)) + if nix_match: + return nix_match.group(1)[:16] + + hasher = hashlib.sha256() + for root in ( + os.path.join(_BASE_DIR, "static"), + os.path.join(_BASE_DIR, "templates"), + ): + if not os.path.isdir(root): + continue + for dirpath, dirnames, filenames in os.walk(root): + dirnames.sort() + for filename in sorted(filenames): + path = os.path.join(dirpath, filename) + try: + stat = os.stat(path) + except OSError: + continue + hasher.update(path.encode()) + hasher.update(f"{stat.st_mtime_ns}:{stat.st_size}".encode()) + return hasher.hexdigest()[:16] + + +ASSET_VERSION = _compute_asset_version() + + def _file_hash(filename: str) -> str: """Return first 8 chars of the MD5 hex digest for a static file.""" path = os.path.join(_BASE_DIR, "static", filename) @@ -1894,7 +1923,10 @@ def _verify_support_removed() -> bool: @app.get("/login", response_class=HTMLResponse) async def login_page(request: Request): - return templates.TemplateResponse("login.html", {"request": request}) + return templates.TemplateResponse("login.html", { + "request": request, + "asset_version": ASSET_VERSION, + }) @app.get("/auto-login") @@ -1961,6 +1993,7 @@ async def api_logout(request: Request): async def index(request: Request): return templates.TemplateResponse("index.html", { "request": request, + "asset_version": ASSET_VERSION, }) @@ -1969,6 +2002,7 @@ async def onboarding(request: Request): _ensure_onboarding_reopened_for_migration() return templates.TemplateResponse("onboarding.html", { "request": request, + "asset_version": ASSET_VERSION, "onboarding_js_hash": _ONBOARDING_JS_HASH, }) diff --git a/app/sovran_systemsos_web/templates/index.html b/app/sovran_systemsos_web/templates/index.html index fa2806b..3168de4 100644 --- a/app/sovran_systemsos_web/templates/index.html +++ b/app/sovran_systemsos_web/templates/index.html @@ -4,17 +4,17 @@ Sovran_SystemsOS Hub - - - - - - - - - - - + + + + + + + + + + + @@ -263,16 +263,16 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/app/sovran_systemsos_web/templates/login.html b/app/sovran_systemsos_web/templates/login.html index cb05415..c469965 100644 --- a/app/sovran_systemsos_web/templates/login.html +++ b/app/sovran_systemsos_web/templates/login.html @@ -4,8 +4,8 @@ Sovran Hub — Login - - + +
From 66cacaaf9d66a6fb370504cbc261e3d3ee9da679 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 16:03:31 +0000 Subject: [PATCH 3/3] Harden asset-version fallback hashing separators --- app/sovran_systemsos_web/server.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index 39aeb48..778da52 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -653,7 +653,7 @@ templates = Jinja2Templates(directory=os.path.join(_BASE_DIR, "templates")) # ── Static asset cache-busting ──────────────────────────────────── def _compute_asset_version() -> str: - """Return a stable asset version string for cache-busting template assets.""" + """Return a 16-char asset version from Nix store hash or static/template metadata.""" nix_match = re.search(r"/nix/store/([a-z0-9]{32})-", os.path.realpath(_BASE_DIR)) if nix_match: return nix_match.group(1)[:16] @@ -674,7 +674,9 @@ def _compute_asset_version() -> str: except OSError: continue hasher.update(path.encode()) + hasher.update(b"\0") hasher.update(f"{stat.st_mtime_ns}:{stat.st_size}".encode()) + hasher.update(b"\0") return hasher.hexdigest()[:16]