Merge pull request #4776 from nesquena/stage-4774-release

Release v0.51.603 — Release VJ (cache app-shell template, #4774)
This commit is contained in:
nesquena-hermes authored and GitHub committed 2026-06-23 06:42:54 -07:00
commit 8a98626967
4 files changed
+157 -14

No files matched your search

+6
View File
@@ -3,6 +3,12 @@
## [Unreleased]
## [v0.51.603] — 2026-06-23 — Release VJ (cache the app-shell template)
### Changed
- **The app shell (`/`, `/index.html`, `/session/<id>`) is now served from an in-process template cache instead of re-reading and re-rendering `static/index.html` (~190 KB) on every navigation.** These are the hottest routes; each request previously re-read the file from disk and re-applied two process-constant token substitutions (`__WEBUI_VERSION__`, `__MAX_UPLOAD_BYTES__`). The partially rendered template is now cached and invalidated on `(size, mtime_ns)` change — mirroring the existing static-asset cache — so a redeploy is still picked up without a restart. The per-session CSRF token and runtime extension tags are still applied per request, so output is byte-identical to before. Measured ~5× faster shell render in a local microbenchmark and removes per-request extension-manifest disk reads. Thanks @boriken72. (#4774)
## [v0.51.602] — 2026-06-23 — Release VI (sidebar fetches only the active source bucket)
### Changed
+47 -8
View File
@@ -8849,6 +8849,48 @@ def _save_saved_prompts(prompts: list) -> None:
p.write_text(json.dumps(prompts, ensure_ascii=False, indent=2), encoding="utf-8")
# In-process cache for the app-shell template. The `/`, `/index.html`, and
# `/session/<id>` routes are the hottest navigations and each re-read the
# ~190 KB static/index.html from disk and re-ran the two process-constant
# substitutions (__WEBUI_VERSION__, __MAX_UPLOAD_BYTES__) on every request.
# Those values are fixed for the process lifetime, so we cache the partially
# rendered template here, keyed by (size, nanosecond mtime) exactly like
# _STATIC_CACHE so a redeploy is picked up without a restart. The two values
# that genuinely vary per request — the per-session CSRF token and the runtime
# extension tags (inject_extension_tags) — are still applied on each request
# against the cached base, so caching changes no observable output.
_INDEX_SHELL_CACHE: dict = {}
_INDEX_SHELL_CACHE_LOCK = threading.Lock()
def _render_index_shell_base() -> str:
"""Return static/index.html with the process-constant tokens substituted.
Cached and invalidated on (size, mtime_ns) change. The CSRF token and
extension-tag injection are intentionally NOT applied here — they vary per
request and are applied by the caller against this base string.
"""
from api.updates import WEBUI_VERSION
st = _INDEX_HTML_PATH.stat()
sig = (st.st_size, st.st_mtime_ns)
with _INDEX_SHELL_CACHE_LOCK:
cached = _INDEX_SHELL_CACHE.get("base")
if cached and cached[0] == sig:
return cached[1]
from urllib.parse import quote
version_token = quote(WEBUI_VERSION, safe="")
base = (
_INDEX_HTML_PATH.read_text(encoding="utf-8")
.replace("__WEBUI_VERSION__", version_token)
.replace("__MAX_UPLOAD_BYTES__", str(MAX_UPLOAD_BYTES))
)
with _INDEX_SHELL_CACHE_LOCK:
_INDEX_SHELL_CACHE["base"] = (sig, base)
return base
def handle_get(handler, parsed) -> bool:
"""Handle all GET routes. Returns True if handled, False for 404."""
@@ -8870,9 +8912,6 @@ def handle_get(handler, parsed) -> bool:
if parsed.path in ("/", "/index.html") or parsed.path.startswith("/session/"):
try:
from urllib.parse import quote
from api.updates import WEBUI_VERSION
version_token = quote(WEBUI_VERSION, safe="")
from api.extensions import inject_extension_tags
csrf_token = ""
@@ -8886,11 +8925,11 @@ def handle_get(handler, parsed) -> bool:
except Exception:
csrf_token = ""
html = (
_INDEX_HTML_PATH.read_text(encoding="utf-8")
.replace("__WEBUI_VERSION__", version_token)
.replace("__MAX_UPLOAD_BYTES__", str(MAX_UPLOAD_BYTES))
.replace("__CSRF_TOKEN_JSON__", json.dumps(csrf_token))
# The disk read + process-constant token substitutions are cached;
# only the per-session CSRF token and per-request extension tags are
# applied here (see _render_index_shell_base).
html = _render_index_shell_base().replace(
"__CSRF_TOKEN_JSON__", json.dumps(csrf_token)
)
return t(
handler,
+87
View File
@@ -0,0 +1,87 @@
"""Regression tests for the app-shell template cache (_render_index_shell_base).
The `/`, `/index.html`, and `/session/<id>` routes are the hottest navigations.
Each previously re-read the ~190 KB static/index.html from disk and re-ran the
two process-constant token substitutions (__WEBUI_VERSION__,
__MAX_UPLOAD_BYTES__) on every request. _render_index_shell_base() caches the
partially rendered template, keyed by (size, mtime_ns) like _STATIC_CACHE, while
the per-session CSRF token and per-request extension tags stay per-request.
These tests guard the load-bearing invariant: caching must change no observable
output, only avoid redundant work.
"""
from __future__ import annotations
import json
import os
import time
from urllib.parse import quote
import api.routes as routes
from api.updates import WEBUI_VERSION
def _old_inline_render(csrf_token: str) -> str:
"""Reproduce the pre-cache inline render exactly, for equivalence checks."""
return (
routes._INDEX_HTML_PATH.read_text(encoding="utf-8")
.replace("__WEBUI_VERSION__", quote(WEBUI_VERSION, safe=""))
.replace("__MAX_UPLOAD_BYTES__", str(routes.MAX_UPLOAD_BYTES))
.replace("__CSRF_TOKEN_JSON__", json.dumps(csrf_token))
)
def test_base_substitutes_process_constants_but_not_csrf():
base = routes._render_index_shell_base()
assert "__WEBUI_VERSION__" not in base
assert "__MAX_UPLOAD_BYTES__" not in base
# CSRF must remain a placeholder — it varies per request and is applied by
# the caller, not baked into the shared cache.
assert "__CSRF_TOKEN_JSON__" in base
def test_cached_render_is_byte_identical_to_old_inline():
csrf = "test-csrf-abc123"
new = routes._render_index_shell_base().replace(
"__CSRF_TOKEN_JSON__", json.dumps(csrf)
)
assert new == _old_inline_render(csrf)
def test_csrf_token_varies_per_request():
a = routes._render_index_shell_base().replace(
"__CSRF_TOKEN_JSON__", json.dumps("AAA")
)
b = routes._render_index_shell_base().replace(
"__CSRF_TOKEN_JSON__", json.dumps("BBB")
)
assert json.dumps("AAA") in a
assert json.dumps("BBB") in b
assert a != b
def test_second_call_returns_cached_object():
# Warm the cache, then assert the identical object comes back (no re-read).
first = routes._render_index_shell_base()
second = routes._render_index_shell_base()
assert first is second
def test_cache_invalidates_on_mtime_change(tmp_path, monkeypatch):
# Point the module at a temp copy so we can mutate its mtime safely.
src = routes._INDEX_HTML_PATH.read_text(encoding="utf-8")
fake = tmp_path / "index.html"
fake.write_text(src, encoding="utf-8")
monkeypatch.setattr(routes, "_INDEX_HTML_PATH", fake)
# Reset the shared cache so this test is order-independent.
monkeypatch.setattr(routes, "_INDEX_SHELL_CACHE", {})
routes._render_index_shell_base()
sig_before = routes._INDEX_SHELL_CACHE["base"][0]
future = time.time() + 5
os.utime(fake, (future, future))
routes._render_index_shell_base()
sig_after = routes._INDEX_SHELL_CACHE["base"][0]
assert sig_after != sig_before
+17 -6
View File
@@ -344,13 +344,24 @@ class TestIndexHtmlIntegration:
def test_index_route_url_encodes_asset_version(self):
src = ROUTES.read_text(encoding="utf-8")
idx = src.find('parsed.path in ("/", "/index.html")')
if idx == -1:
idx = src.find('parsed.path.startswith("/session/")')
assert idx != -1, "routes.py must handle /, /index.html, and /session/<id>"
block = src[idx:idx + 800]
# #4774 moved the app-shell render (incl. the version-token substitution)
# out of the route handler and into the cached `_render_index_shell_base()`
# helper. The security property — the cache-busting version token is
# URL-encoded before it's injected into script src / SW registration — must
# still hold, so assert it in whichever location renders the shell. Check the
# shell-render helper first (its current home), then fall back to the route
# handler block for older layouts.
helper_idx = src.find("def _render_index_shell_base")
if helper_idx != -1:
block = src[helper_idx:helper_idx + 1200]
else:
idx = src.find('parsed.path in ("/", "/index.html")')
if idx == -1:
idx = src.find('parsed.path.startswith("/session/")')
assert idx != -1, "routes.py must handle /, /index.html, and /session/<id>"
block = src[idx:idx + 800]
assert "quote(WEBUI_VERSION, safe=\"\")" in block, (
"index route must URL-encode the cache-busting version token before "
"the app-shell render must URL-encode the cache-busting version token before "
"injecting it into script src attributes and service worker registration"
)