Commit Graph

5 Commits

Author SHA1 Message Date
lukas 7104605c06 Type component children with a covariant Children alias
The builders annotated their ``children`` parameter as
``list[HTMLTag] | HTMLTag | None`` where ``HTMLTag = str``. ``list[str]`` is
invariant, so passing ``list[Element]`` / ``list[Node]`` — the normal case —
was a type error everywhere a component nested children.

Introduce a proper child type in core:

    Child    = Node | str
    Children = Sequence[Child] | str | None

``Sequence`` is covariant, so ``list[Element]`` / ``list[Node]`` are accepted;
``Child`` includes ``Node`` so node children are no longer rejected. ``Element``
itself also accepts a bare ``Node`` (it wraps one), typed ``Children | Node``.

Replace the ``list[HTMLTag] | HTMLTag | None`` annotations across primitives /
domain with ``Children``, and add ``as_children()`` to normalise a ``children``
argument to a ``list[Child]`` — retiring the repeated
``children if isinstance(children, list) else [children]`` dance that defeated
type narrowing. Inline ``mark_safe(...)`` SVG/markup children become ``Safe(...)``
nodes (a ``Node`` child instead of a stub-typed string).

Pyright on the component package drops from 43 to 22 errors; the remaining 22
are pre-existing and unrelated (django-stubs model access, the ``mark_safe``
``_Wrapped`` return type, and ``list[HTMLAttribute]`` attribute invariance).
Full suite green (443).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 18:14:09 +02:00
lukas 9c42d85f52 Migrate remaining Component() callers to Element; delete the shim
The legacy back-compat ``Component(tag_name=...)`` function (a thin
string-returning wrapper over ``Element``) was the last piece of the
pre-node-tree API. Migrate its ~18 call sites across the views to the node
builders and remove it:

- stats_content.py: the table helpers now use the whitelisted ``Td`` / ``Th``
  / ``Tr`` builders and ``Element`` for table/tbody/thead/h1; helper return
  types are ``Node``.
- auth.py / statuschange.py / game.py / purchase.py: the hand-built
  ``<form>`` / ``<button>`` / ``<h1>`` / ``<h2>`` / ``<table>`` markup now uses
  ``Element("tag", ...)``.
- core.py: drop the ``Component()`` function and its back-compat note;
  ``common/components/__init__`` no longer exports it.
- Tests that exercised the shim now target ``Element`` directly
  (test_components cache/escaping/edge-case classes; test_node_tree drops the
  legacy-parity and legacy-bridge cases, which ``Element`` coverage subsumes).
- CLAUDE.md: drop the "legacy Component retained for back-compat" notes.

Full suite green (443; one obsolete legacy-bridge test removed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 16:51:27 +02:00
Claude 0819ddb87d Phase 3: declare component media that bubbles through the node tree
The JS-bearing widgets now declare their script dependencies, so a view
no longer needs to know which scripts a component requires:

- SearchSelect / FilterSelect → search_select.js
- RangeSlider → range_slider.js
- DateRangePicker → date_range_picker.js
- YearPicker → datepicker.umd.js (external, from Phase 2)
- FilterBar chrome → filter_bar.js

Because the filter-bar internals now build a node tree (the legacy
Component() string-builder calls became Element/Div), each bar's
collect_media() returns its own filter_bar.js merged with the scripts
that bubble up from the FilterSelect / RangeSlider / DateRangePicker
widgets it contains — exactly the set the views thread by hand today.

Adds Node.with_media() so a function-built node can declare media
without a full BaseComponent subclass, and tests proving the bubbling.

Note: the six *FilterBar functions still share the _filter_bar chrome
helper rather than a BaseComponent class hierarchy; folding them into
one is a follow-up that does not affect media collection (Phase 4).

https://claude.ai/code/session_01BKurBhE3Qj25p7Bfsg7EeK
2026-06-13 07:24:29 +00:00
Claude f673f3ac80 Phase 1: add lazy node tree (Node/Element/Safe/Fragment/Media)
Introduce a FastHTML-style component model alongside the existing
function-based one, purely additive:

- Node: base renderable; __html__/__str__ render lazily so str()/f-string
  composition keeps working during migration.
- Element: the single class for any HTML element (tag + attrs + children),
  rendering via the existing memoized _render_element.
- Safe: wraps pre-rendered HTML (migration bridge for f-string components).
- Fragment: ordered children with no wrapper tag (replaces str(a)+str(b)).
- BaseComponent: base for higher-level components; render() returns a
  subtree, media declared via a Media attribute.
- Media: declarative JS deps with order-preserving dedup merge.
- collect_media()/render() helpers walk the tree.

The legacy Component() function now builds an Element and is Node-aware in
its child handling, so a tree mixing string- and node-returning components
renders correctly with byte-identical output. No call sites changed yet.

https://claude.ai/code/session_01BKurBhE3Qj25p7Bfsg7EeK
2026-06-13 06:56:37 +00:00
lukas ed8589a972 Fix more code smells
Django CI/CD / test (push) Successful in 39s
Django CI/CD / build-and-push (push) Successful in 1m19s
2026-06-06 13:14:55 +02:00