Navbar returns a Safe node; drop redundant filter_presets mark_safe

Navbar is static chrome (a few reverse() URLs in otherwise-fixed markup), so
it now returns a single Safe node wrapping that markup instead of a mark_safe
string — consistent with "trusted HTML is a Safe node," and a full element
tree would be ~80 lines of nesting for no gain (it owns no component JS).
Page() interpolates it via str() exactly as before.

filter_presets.list_presets returned HttpResponse(mark_safe(...)); HttpResponse
never escapes its body, so the mark_safe was pure noise — dropped.

The mark_safe calls that remain are all load-bearing and not tree children:
the node engine itself (core: how a node emits its SafeString), the
script-tag / scripts= string helpers, and Page()'s final document string.

Full suite green (445).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 19:42:07 +02:00
parent 0527412265
commit 3f95692746
2 changed files with 10 additions and 5 deletions
+9 -3
View File
@@ -186,10 +186,16 @@ def _main_script(mastered: bool) -> str:
return _MAIN_SCRIPT_A + ("true" if mastered else "false") + _MAIN_SCRIPT_B return _MAIN_SCRIPT_A + ("true" if mastered else "false") + _MAIN_SCRIPT_B
def Navbar(*, today_played: str, last_7_played: str, current_year: int) -> SafeText: def Navbar(*, today_played: str, last_7_played: str, current_year: int) -> "Node":
"""Top navigation bar.""" """Top navigation bar.
Static chrome, so it's a single ``Safe`` node wrapping its markup rather
than a hand-built element tree — trusted HTML belongs in a ``Safe`` node,
not a ``mark_safe`` string."""
from common.components import Safe
logo = static("icons/schedule.png") logo = static("icons/schedule.png")
return mark_safe(f"""<nav class="bg-neutral-primary-soft border-b border-default"> return Safe(f"""<nav class="bg-neutral-primary-soft border-b border-default">
<div class="max-w-(--breakpoint-xl) flex flex-wrap items-center justify-between mx-auto p-4"> <div class="max-w-(--breakpoint-xl) flex flex-wrap items-center justify-between mx-auto p-4">
<a href="{reverse("games:index")}" <a href="{reverse("games:index")}"
class="flex items-center space-x-3 rtl:space-x-reverse"> class="flex items-center space-x-3 rtl:space-x-reverse">
+1 -2
View File
@@ -8,7 +8,6 @@ from django.contrib.auth.decorators import login_required
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse from django.urls import reverse
from django.utils.safestring import mark_safe
from games.models import FilterPreset from games.models import FilterPreset
@@ -40,7 +39,7 @@ def list_presets(request: HttpRequest) -> HttpResponse:
if not items: if not items:
items = ['<li class="px-4 py-2 text-sm text-body italic">No saved presets</li>'] items = ['<li class="px-4 py-2 text-sm text-body italic">No saved presets</li>']
return HttpResponse(mark_safe(f'<ul class="py-1">{"".join(items)}</ul>')) return HttpResponse(f'<ul class="py-1">{"".join(items)}</ul>')
@login_required @login_required