- Add ts/search_select.ts: typed port of the SearchSelect/FilterSelect widget.
Exports SearchSelectOption / SearchSelectChangeDetail as the single source of
truth for the "search-select:change" event contract
- add_purchase.ts now imports those types via `import type` (no runtime
coupling), instead of redefining them locally
- Declare window.readSearchSelect in ts/globals.d.ts
- Point the SearchSelect component Media and every view/e2e/test reference at
the compiled dist/search_select.js
- Update doc comments in common/components/search_select.py to name the TS source
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Port FastHTML's proc_htmx as onSwap(selector, initializeElement) in
utils.js, built on htmx.onLoad: it runs an initializer once per matching
element, on initial page load and inside every htmx-swapped fragment.
Migrate search_select.js, range_slider.js, filter_bar.js and
add_purchase.js to it, removing the hand-rolled DOMContentLoaded +
htmx:afterSwap listeners and per-element guard flags. This also fixes a
latent bug: both events passed the Event object as range_slider's
"force" parameter, so every htmx swap force-re-initialized all sliders
and stacked duplicate listeners. The collapse button's
window.initRangeSliders() call was a no-op (handles are positioned in
percentages, so hidden-init is safe) and is removed with the global.
Add e2e/test_widgets_e2e.py covering the onSwap lifecycle (initial-load
init, htmx-swap init, single-fire toggles) plus FilterSelect pills and
the add-purchase type toggle. The synthetic page in
test_search_select_e2e.py now loads htmx and search_select.js as a
module, matching the new initialization path.
https://claude.ai/code/session_01BKurBhE3Qj25p7Bfsg7EeK