{% comment %} SPEC §11.4's filter builder, as a row of add-a-rule controls. Every word of vocabulary in here — which sources exist, which operators each value type takes, which operators take no value at all, what to call them, which sources this deployment cannot evaluate yet — comes from `filter_config`, which the view builds from `CONDITION_SCHEMA["x-brightbean"]` and the condition engine's own source registry. There is no second table of operators in this template, and that is the point: an operator added to apps/contacts/conditions.py shows up here with no edit. The rules live in Alpine state and are serialised into one hidden input on every change, so the form GETs a single `filter=` parameter that the server hands straight to the condition engine. Posting the rules as separate form fields would mean re-assembling the document server-side — a second parser for a language that already has one. Plain No filter: showing everyone.
{% comment %} `filter_embedded` is set by the flow builder's rule-trigger panel (issue #22), which renders this same partial inside a form that already has its own Save button. The hidden input above is what both surfaces actually need; this button belongs only to the page that submits nothing else. {% endcomment %} {% if not filter_embedded %}{% endif %} {% if can_manage_crm %} {% comment %} Saving is its own POST, and it carries the same serialised document the Apply button sends — one spelling of the filter, so a segment can never be saved as something other than what the list is showing. Three controls, not one, because "save" means two different things depending on where the rules came from. With a segment loaded, the obvious act is to update *it*; offering only "Save as segment" meant an operator correcting a saved filter silently minted a second one beside the original, and left the update and delete routes with no way in at all. {% endcomment %} {% if filter_config.segmentId %} {% endif %} {% if filter_config.segmentId %} {% endif %} {% endif %}