Eliminate the Python/JS class-string duplication: the server renders hidden
<template> prototypes (row, pill, include/exclude/modifier pills) using the same
component functions, and search_select.js clones them, filling only the
[data-ss-label] slot, value, and data-* attrs. All Tailwind class strings and DOM
structure now live solely in the Python components — the JS no longer hardcodes
any class. Pill gains an opt-in label_slot; the shell takes a templates list.
Companion issue #8 tracks the further HTMX-idiomatic step of returning rendered
row HTML from the search endpoint.
https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
Anchor the options panel with top-full. As an absolutely positioned child of the
now-flex field container, its static position was centered by items-center,
placing the dropdown over the search box instead of below it.
https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
- Wire the long-defined-but-unused _FIELD_CLASS into the container so pills and
the search input form a single padded flex row; the flex-1 input now fills the
widget instead of looking unclickable inside a larger box (affects both
SearchSelect and FilterSelect via the shared shell).
- Filter option labels get text-body so they're readable on dark backgrounds.
- Filter +/- buttons get text-body (readable at rest) and hover:border-brand-strong
so the border stays visible against the brand hover fill.
- Mirror the filter class changes in search_select.js and rebuild base.css.
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
Introduce a general 'prefetch' option (rows to load on first open, default 0 =
unchanged) carried as data-prefetch. Rework the JS search so a search_url widget
filters its loaded window instantly on every keystroke while issuing a debounced
server request for the rest, with an AbortController so a slower earlier response
can never overwrite a newer one. No-results stays hidden until the server
response decides it, avoiding a flash over an incomplete window. On first focus a
prefetch-enabled widget seeds its window immediately. Rename single-letter locals
to full words while reworking these functions.
https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
Pull the domain-agnostic combobox skeleton (pills region, search box, options
panel with its no-results node, outer container) into a private _combobox_shell
helper. SearchSelect now builds its form-specific pills and option rows and
delegates assembly to the shell. Rendered markup is byte-identical; a structural
test guards the fixed region order so future builders (e.g. a filter variant)
can share the shell without drift.
https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
- Parameterize SearchSelectWidget with a required options_resolver so
each widget explicitly names its resolver instead of implicitly using
_game_options
- Add autofocus support: SearchSelect forwards it to the search input,
and SearchSelectWidget extracts it from Django's attrs dict
- Add _device_options and _platform_options resolvers (single pk__in
queries, same pattern as _game_options)
- Add /api/devices/search and /api/platforms/search endpoints
- Switch PlayEventForm.game from plain Select to SearchSelectWidget
(preserving autofocus), and use SingleGameChoiceField for correct labels
- Switch SessionForm.device to SearchSelectWidget
- Switch PurchaseForm.platform and GameForm.platform to SearchSelectWidget
- Wire ModuleScript("search_select.js") into add/edit playevent and
add/edit game views
https://claude.ai/code/session_013fpJD54HxRgxRv2xzwXGNo