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>
Generic leaf builders (Div, Span, Td, Tr, Th, Ul, Li, Strong, Label,
Template, P) are now generated from one _html_element factory over the
single Element class — the tag name is data, not a per-tag body. Only
elements that add classes/behaviour (Button, Pill, Checkbox, Radio,
Input, A, SearchField, H1, Modal, AddForm, tables) stay hand-written.
All primitives now return Node objects; string-built widgets (Icon,
SimpleTable, YearPicker) return Safe, and YearPicker declares its
datepicker media. Raw concatenation (_popover_html, Popover slot) uses
Fragment.
Node.__str__/__html__ now return a SafeString: a node's rendered output
is safe HTML by construction, so str(node) stays safe when fed back into
a child list or template (matching the old SafeText behaviour and
preventing double-escaping).
Consumers adapted: the form widgets (SearchSelectWidget,
PrimitiveCheckboxWidget) return render(component) so Django gets a safe
string; the session form's manual field markup joins via str(row).
Component tests render nodes to HTML before asserting.
https://claude.ai/code/session_01BKurBhE3Qj25p7Bfsg7EeK
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
Serve alpinejs 3.15.12, @alpinejs/mask 3.15.12, flowbite 2.4.1 and
flowbite-datepicker 2.0.0 from games/static/js/ instead of jsdelivr, so
pages (and browser tests) work without network access. Adds the
StaticScript primitive for vendored UMD bundles, which cannot be loaded
as ES modules.
https://claude.ai/code/session_01BKurBhE3Qj25p7Bfsg7EeK
Implements the DateRangePicker design: a DateRangeField that looks like a
single input but splits each date into DD/MM/YYYY part inputs (ordered by
the new common.time.dateformat_hyphenated), and a DateRangeCalendar popup
with a preset column (today, yesterday, last 7/30 days, this/last month,
this year), anchor-style range picking with an outlined/filled/muted range
track, and a Cancel / Clear / Select footer.
Typing fills each part's placeholder from the right (YYYY -> YY19 -> 1987),
auto-advances between parts, and Backspace/Delete reverts the active part.
The committed value lives in hidden ISO {prefix}-min/{prefix}-max inputs --
the same contract as DateRangeFilter, so filter_bar.js needs no changes.
As a tryout, the Purchased filter in PurchaseFilterBar now uses the
DateRangePicker; Refunded keeps the native-date DateRangeFilter, and the
native-path e2e tests were repointed at it.
Includes unit tests for the component family and the filter-bar
integration, plus Playwright e2e tests for segment entry, calendar
picking, presets, and footer actions.
https://claude.ai/code/session_017b75KJAu4kNNpZPu9NAPBM
Replace all tuple[str, str] annotations with purpose-specific names:
- LabeledOption = tuple[str, str] for (value, label) pairs used in
FilterChoice, FilterSelect params, _modifier_options, _find_label,
and _extract_labeled.
- RangeValues(min, max) NamedTuple for _parse_range return values,
making the two fields self-documenting at every call site.
Export LabeledOption from common.components alongside SearchSelectOption.
Document the "name compound types explicitly" convention in CLAUDE.md.
https://claude.ai/code/session_01EyAJcMoDktLrY9tSbdHViA
Add a Template() primitive for the standard <template> tag and export it. Replace
inline Component(tag_name="div"/"span"/"input"/"template") in search_select.py
and Pill with Div/Span/Input/Template; drop the private _template helper in favour
of Template at the call sites. Bare custom-styled <button>s stay on Component
(the opinionated Button() would inject unwanted classes). Document the
prefer-primitives convention in CLAUDE.md.
https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
FilterSelect fully replaces it: delete SelectableFilter and its _selectable_*
helpers, the now-unused _get_filter_options, selectable_filter.js, and the .sf-*
rules in input.css (rebuilt base.css). The three list views load search_select.js
instead of selectable_filter.js. Drop the SelectableFilter export and refresh
docs/comments that referenced it.
https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
FilterSelect renders value rows with +/- (include/exclude) buttons, check/cross
pills for the included/excluded sets, and an optional set of pinned modifier
pseudo-options (e.g. (Any)/(None)) that stay visible above the value rows. A
selected modifier is mutually exclusive with value pills. It delegates assembly
to _combobox_shell and supports both pre-rendered options (complete set) and
search_url + prefetch (windowed); included/excluded are passed as resolved
value+label so pills show labels even outside the fetched window. Styling is
inline (ported from the old SelectableFilter CSS) so nothing lives in input.css.
https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS