fix: add favicon (SVG + PNG + ICO), fix static MIME types (#613)

Squash-merges PR #613. Adds favicon to the app (was missing entirely — blank tab icon). 1371 tests passing, QA harness green. Review by independent agent (see PR comments). Follow-up commit addresses all three reviewer notes: hoisted _STATIC_MIME to module scope, fixed charset=utf-8 being appended to binary MIME types, confirmed correct MIME types on all three favicon formats.

Co-authored-by: tiansiyuan <tiansiyuan@users.noreply.github.com>
This commit is contained in:
nesquena-hermes
2026-04-16 20:11:02 -07:00
committed by GitHub
parent 692ba68e42
commit a2ea15b557
5 changed files with 58 additions and 6 deletions

View File

@@ -420,8 +420,19 @@ def handle_get(handler, parsed) -> bool:
return j(handler, {"auth_enabled": is_auth_enabled(), "logged_in": logged_in})
if parsed.path == "/favicon.ico":
handler.send_response(204)
handler.end_headers()
static_root = Path(__file__).parent.parent / "static"
ico_path = (static_root / "favicon.ico").resolve()
if ico_path.exists() and ico_path.is_file():
data = ico_path.read_bytes()
handler.send_response(200)
handler.send_header("Content-Type", "image/x-icon")
handler.send_header("Content-Length", str(len(data)))
handler.send_header("Cache-Control", "public, max-age=86400")
handler.end_headers()
handler.wfile.write(data)
else:
handler.send_response(204)
handler.end_headers()
return True
if parsed.path == "/health":
@@ -1313,6 +1324,25 @@ def handle_post(handler, parsed) -> bool:
# ── GET route helpers ─────────────────────────────────────────────────────────
# MIME types for static file serving. Hoisted to module scope to avoid
# rebuilding the dict on every request.
_STATIC_MIME = {
"css": "text/css",
"js": "application/javascript",
"html": "text/html",
"svg": "image/svg+xml",
"png": "image/png",
"jpg": "image/jpeg",
"jpeg": "image/jpeg",
"ico": "image/x-icon",
"gif": "image/gif",
"webp": "image/webp",
"woff": "font/woff",
"woff2": "font/woff2",
}
# MIME types that are text-based and should carry charset=utf-8
_TEXT_MIME_TYPES = {"text/css", "application/javascript", "text/html", "image/svg+xml", "text/plain"}
def _serve_static(handler, parsed):
static_root = (Path(__file__).parent.parent / "static").resolve()
@@ -1326,11 +1356,10 @@ def _serve_static(handler, parsed):
if not static_file.exists() or not static_file.is_file():
return j(handler, {"error": "not found"}, status=404)
ext = static_file.suffix.lower()
ct = {"css": "text/css", "js": "application/javascript", "html": "text/html"}.get(
ext.lstrip("."), "text/plain"
)
ct = _STATIC_MIME.get(ext.lstrip("."), "text/plain")
ct_header = f"{ct}; charset=utf-8" if ct in _TEXT_MIME_TYPES else ct
handler.send_response(200)
handler.send_header("Content-Type", f"{ct}; charset=utf-8")
handler.send_header("Content-Type", ct_header)
handler.send_header("Cache-Control", "no-store")
raw = static_file.read_bytes()
handler.send_header("Content-Length", str(len(raw)))