diff --git a/CLAUDE.md b/CLAUDE.md index 298af56..6456fa5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,6 +35,7 @@ games/ — Django app: models, views, templates, forms, signals, tasks, common/ — Shared utilities: time formatting, component system, criteria, layout, icons timetracker/ — Django project: settings, URL root, ASGI/WSGI tests/ — Pytest tests +e2e/ — Playwright browser tests (run via `make test-e2e`) contrib/ — One-off scripts (exchange rate import) docs/ — Additional documentation ``` @@ -57,12 +58,12 @@ docs/ — Additional documentation ### Key patterns -**Layout system** (`common/layout.py`): Views call `render_page(request, content, title=...)` instead of Django's `render()`. This assembles a full HTML document via `Page()` — analogous to FastHTML's `fast_app()`. `Page()` handles the ``, navbar, toast container, JS includes, and FOUC-prevention script. The navbar shows today's playtime and last-7-days playtime from the `model_counts` context processor. +**Layout system** (`common/layout.py`): Views call `render_page(request, content, title=...)` instead of Django's `render()`. This assembles a full HTML document via `Page()` — analogous to FastHTML's `fast_app()`. `Page()` handles the ``, navbar, toast container, FOUC-prevention script, and **JS includes**: it calls `collect_media(content)` to gather every component's declared `Media` and emits the `') +def StaticScript(filename: str) -> SafeText: + """A plain (classic, non-module) `') + + +# Media for the Flowbite-datepicker year picker (vendored UMD bundle). Declared +# on the YearPicker node so Page() loads it wherever a YearPicker appears. +_YEAR_PICKER_MEDIA = Media(js_external=("datepicker.umd.js",)) + + def YearPicker( year: int | None = None, available_years: tuple[int, ...] = (), url_template: str = "", -) -> SafeText: +) -> Node: """A Flowbite-datepicker year picker. `year` is the selected year, or ``None`` for the all-time view (the empty @@ -567,8 +554,8 @@ def YearPicker( placeholder, substituted with the chosen year in JS (keeps this component decoupled from the project's URL names). - The Flowbite-datepicker UMD bundle is *not* loaded here — the view hoists it - via ``render_page(scripts=...)``. + The Flowbite-datepicker UMD bundle is declared as ``media`` on the returned + node, so ``Page()`` loads it automatically. """ label = str(year) if year is not None else "Choose a year" selected = str(year) if year is not None else "" @@ -579,7 +566,8 @@ def YearPicker( "hover:bg-neutral-tertiary-medium focus:ring-4 focus:ring-brand-medium" ) years_csv = ",".join(str(y) for y in available_years) - return mark_safe(f"""