{% comment %} The filter builder's Alpine component. Its own file because it is a hundred lines of JavaScript and list.html is already a page. It is included from that page's alpine_components block, which base.html renders before Alpine's own deferred script tag — the window in which a global x-data factory has to be defined for x-data to be able to call it. Written as a plain function rather than Alpine.data() for the same reason, and kept out of the flow-builder bundle deliberately: it is glue for one page, and a build step for it would mean a second entry point and a second artefact that can go stale. tests/js/contacts-filter-bar.test.ts reads THIS file, pulls the script out and evaluates it, so what the test checks is what the browser gets — which is why the prose above must not contain a literal script tag for the extractor to trip over. It knows the *shape* of a §11.4 filter and nothing about its vocabulary. Every operator, label, type mapping and limit is read out of `config.vocabulary`, which the view fills from CONDITION_SCHEMA's `x-brightbean` extension block. Hard-coding "text fields take is/is_not/contains" here would be the second operator table the condition engine's docstring exists to prevent. {% endcomment %}