Make component return types honest; drop str/mark_safe leftovers

Cleanup of hacky leftovers from the node-tree migration (no behaviour
change):

- Return annotations: the component builders return Node subtrees, not
  SafeText strings, but ~40 functions still declared `-> SafeText`. Correct
  them to `-> Node` across filters / search_select / date_range_picker /
  domain. The genuine string returners keep `-> SafeText`: the Alpine
  selectors (GameStatusSelector / SessionDeviceSelector, which build f-string
  markup) and the script-tag helpers (CsrfInput / ModuleScript /
  ExternalScript / StaticScript).
- layout.render_page / layout.Page / AddForm now accept `Node` in their
  `content` / `scripts` / `fields` parameters (TYPE_CHECKING import in
  layout to avoid the components import cycle), matching what views already
  pass.
- session._session_fields builds a `Fragment(*rows, separator="\n")` instead
  of `mark_safe("\n".join(str(row) ...))` — keeps the tree intact so media
  could bubble, per the Fragment convention.
- Inline SVG icon children use `Safe(...)` nodes instead of `mark_safe(...)`
  strings (filters mode-toggle + collapse icons, date_range_picker calendar
  icon).
- _filter_field reads the widget's own id from its node `.attributes`
  (`_widget_id`) for the label's `for`, dropping the superfluous `for_widget`
  argument that always rendered `for="None"`. Removes the two TODOs whose
  premise ("the Component function can't expose the id") the class/node
  refactor retired, plus RangeSlider's dead commented-out Label block.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 15:12:52 +02:00
parent 1c5bff8651
commit 022d43a5a5
7 changed files with 86 additions and 80 deletions
+8 -4
View File
@@ -8,6 +8,7 @@ it hoists shared `<head>` content (the `_HEADERS` block, analogous to
"""
import json
from typing import TYPE_CHECKING
from django.contrib.messages import get_messages
from django.http import HttpRequest, HttpResponse
@@ -19,6 +20,9 @@ from django_htmx.jinja import django_htmx_script
from games.templatetags.version import version, version_date
if TYPE_CHECKING:
from common.components import Node
# Static head script that sets the dark/light class before paint (avoids FOUC).
_THEME_FOUC_SCRIPT = """<script>
if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
@@ -269,11 +273,11 @@ def Navbar(*, today_played: str, last_7_played: str, current_year: int) -> SafeT
def Page(
content: SafeText | str,
content: "Node | SafeText | str",
*,
request: HttpRequest,
title: str = "",
scripts: SafeText | str = "",
scripts: "Node | SafeText | str" = "",
mastered: bool = False,
) -> SafeText:
"""Assemble a full HTML document around `content` (the fast_app equivalent).
@@ -356,10 +360,10 @@ def Page(
def render_page(
request: HttpRequest,
content: SafeText | str,
content: "Node | SafeText | str",
*,
title: str = "",
scripts: SafeText | str = "",
scripts: "Node | SafeText | str" = "",
mastered: bool = False,
status: int = 200,
) -> HttpResponse: