Repository navigation
Filter search by field values and references, with text optional for one type - #2606
Merged
Merged
Conversation
… do, and test the filters over the tool
This was referenced Oct 19, 2026
pournasserian
added a commit
that referenced
this pull request
Oct 19, 2026
Closes #2621 ## What The third slice of F-FRM (#2426): pickers for reference and media fields (FRM-01), backed by search (`ISearch`, #2566, #2606) and the media library (#2543). - **`PickerField`** (`Components/Forms`) is an interactive island inside the static generated form. `FieldWidget` renders it for reference and media fields. - Over the circuit it shows the current choice, a Clear button and a search box. The choice is a hidden input named `Form.Values[field]`, so the form posts and saves exactly as before. - **Entries**: as the person types, it searches the reference's target type (at most 10 direct hits). Each hit shows its first text value, or an entity's first text fact, with its state and a short id. - **Media**: it filters the library by file name or folder, newest first, with a preview for images. - The matches are a `listbox` of buttons that rove with the module from #2585. ArrowDown moves from the search box into the list, the arrows move through it, Enter chooses and Escape clears the matches. - Before the circuit attaches, or when it is lost, the prerendered island is the plain select from #2613, so a form posted then still saves. - **`admin.js`**: a `data-ui="rove-input"` box moves focus to the first item of the list its `aria-controls` names on ArrowDown. ## Why - **An island, not a page in interactive mode.** Only the search needs the circuit. The form, its other fields and the save stay static, so the save path from #2613 does not change, and a lost circuit costs only the search. - **A hidden input in the island.** The static form collects it with the other fields, and no server state has to survive between the choice and the save. - **`FieldOption[]` rather than an interface.** An island's parameters cross into the circuit as JSON. With `IReadOnlyList<FieldOption>`, the browser refused the root components with "The list of component operations is not valid". The parameter says why in a comment. - **Not in this PR**: field filters in the picker (`SearchQuery.Fields` exists for it once a field's settings name a filter), list-of-reference widgets, and rule hints (FRM-03, needs F-RUL #2419). ## Tests - bUnit (`PickerFieldTests`): - Before the circuit, a picker is the select with the stored choice. - An entry picker searches its target type and names hits by their first text fact. It wires the listbox to the search box and the rove module, and choosing posts the id and shows the name. Clear empties the choice. - A search with no match says so, and Escape clears the matches. - A media picker finds an asset by file name, case-insensitively, with its preview, and posts its id. - `GeneratedFormTests` renders the reference and media widgets before the circuit, as selects. - Browser (`FormPickerTests`), keyboard alone: type the title, Tab to the picker, type part of a product's name, press ArrowDown and Enter, then write the rationale and save. The stored entry references the product. `dotnet build -warnaserror`, the unit and bUnit suite, the browser tests and the SQLite integration suite pass locally. The local PostgreSQL run on a loaded machine had nine failures in untouched classes, the same pattern as the last three PRs. CI runs the full PostgreSQL suite. No new package and no new project reference. ## Checklist - [x] Linked to an issue a maintainer has confirmed - [x] Targets `v2` - [x] Tests or docs updated where behaviour changed - [x] No tool-generated co-author trailers or footers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2599
What
The second slice of F-SRC (#2430): search filters on field values and references, combined with text, or without text for one type (SRC-01).
FluentCMS.Core.Search):SearchQuery.Fields: filters on the latest version's field values, all of which must hold. Each is aFieldFilter(field, operator, value), and they need exactly one type inContentTypes.SearchQuery.References: an entry or entity id. Only entries whose latest version references it are kept.FieldFilterschecks the filters against the type's definition before anything is read, and applies them to an entry's fields:equalsandcontains, which ignores case.equals,less_than,at_most,greater_thanandat_least. Numbers are compared as decimals, as the number field type stores them. A date-time value is a point in time in ISO 8601 with an offset, or aYYYY-MM-DDdate for adateOnlyfield, checked with the same rules the field type uses.equals.search_filter_needs_one_type,search_field_not_found,search_field_not_filterable,search_operator_not_supportedandsearch_filter_value_invalid. An emptyFieldslist issearch_filter_empty.viahits.EntrySearchapplies the field and reference filters to each page's candidates that pass the scope, deletions and state filters. It reads their latest versions in one query per page, and the same rules apply to entries found through a reference.search_entriestakesfields, an array of{field, operator, value}, andreferences, an id.queryis optional with one type and either filter. A malformed item or an unknown operator is an argument error naming its position.ToolArgumentsReader.OptionalArray.Why
SRC-01 asks for full-text and structured search. The first slice filtered by type, state and published version. Agents next ask for things like "products under 20" or "the pages that reference this product".
Choices worth a look:
Search__MaxMatchesof the type's entries and says when it stopped early. If the maintainer prefers SQL here, it can move behind the sameFieldFiltersrules.via: every candidate is a match, so a reference has nothing to explain.Tests
Unit: each field type's operators and value kinds, with their refusals, including dates without an offset or in another form. Each operator on text, decimals beyond a double's precision, date-times with offsets, calendar dates, booleans and references. A missing or null field, all filters having to hold, and an entity's fact value.
Integration, each on SQLite and PostgreSQL:
price less_than 20on an entity type finds the product at 19.99, and not those at 25 or without the word. With no words,name contains "wid"finds both widgets, with rank 0.referencesfinds the page whose latest version references the product, and not one whose earlier version did. Combined with words, it keeps only matches that also reference it.fieldslist, an unknown field,containson a number and values of the wrong kind are refused with their codes.containsand no query, and finds by reference the page whose latest version references the product, not the one whose earlier version did. An unknown operator and an incomplete item are argument errors.containson a number and a value of the wrong kind each return their code.The full suite ran locally against a
postgres:17container as well as SQLite, and markdownlint passes. No new package and no new project reference.Checklist
v2