Skip to content

Highlight active search constraints on results pages - #7159

Open
roed-math wants to merge 6 commits into
LMFDB:mainfrom
roed-math:ai/t31-active-constraints
Open

Highlight active search constraints on results pages#7159
roed-math wants to merge 6 commits into
LMFDB:mainfrom
roed-math:ai/t31-active-constraints

Conversation

@roed-math

Copy link
Copy Markdown
Contributor

Closes #2770.

On search results pages, the inputs that are actually constraining the results being displayed are now tinted (text boxes and selects) or ringed (checkboxes), so it is visible at a glance why the results are filtered.

How it works

Marking happens server-side in the shared search_boxes.py machinery, so every section gets it. SearchBox._attrs(info) emits exactly one class attribute per input, merging the box's own classes with advanced, search_constraint (this input constrains the results when filled in) and search_active (it is filled in right now). Class names are passed with a first-class classes= argument rather than hand-written into extra, since two class attributes on a tag is invalid HTML and browsers silently discard one of them.

search_active describes the search that produced the rows on screen, and is deliberately server-owned: nothing rewrites it as the user edits the form, so the highlight never claims a constraint that has not been submitted. The existing fresh/stale "Search again" button remains the signal that the form has been edited.

What counts as a constraint

is_constraint is a bool, or a function of info for boxes whose role depends on the rest of the search.

Not constraints: result count, sort and column controllers, dynamic-statistics variable boxes, diagram axis/colour/count boxes, hidden inputs, and the trace and Euler table display controls (n, n_primality, view_modp) in classical modular forms and L-functions. That last group includes the n_primality=primes that set_Trn inserts on every trace search, which would otherwise look filled in when the user never touched it.

Contextual, via a callable:

  • an_modulo counts only when there are trace constraints for it to turn into congruences, not when it merely drives view_modp=reductions.
  • The hypergeometric prime p counts only when one of the Ap, Bp, Apperp, Bpperp boxes is filled, since it only picks which columns those compare against.
  • The abelian variety geometric-decomposition checkbox counts only when one of the inputs it retargets is used.
  • A select beside a text box (TextBoxWithSelect) counts only once that text box is filled. This is safe because SearchParser.__call__ returns immediately on an empty field, so a select handed to a @search_parser as mode= cannot affect the query alone. Six selects are read directly during parsing and do constrain by themselves, so they opt out with is_constraint=True.

Colours

Two colour-scheme keys, so both stay overridable per scheme: search_active_background (#FFF9C4) for the tint and search_active_ring (#F9A825) for checkboxes, which are rendered natively and cannot be tinted. The ring uses box-shadow rather than outline so the browser's keyboard focus indicator survives. The tint uses background-color rather than the background shorthand so it cannot reset a background image. The exact shades are a maintainer taste call and easy to tune.

Two bugs found and fixed along the way

Both were pre-existing and unrelated to the highlighting, but each was directly in the path of deciding whether an input constrains the results.

  • Classical modular forms level type. The dropdown offers "prime squared" and "powerful", but common_parse recognised neither, so both fell to the invalid-type branch. That branch flashes an error and returns a redirect, and both callers use the query common_parse builds while discarding its return value, so the search then ran with no condition on the level at all. mf_newforms and mf_newspaces already have level_is_prime_square and level_is_powerful, so the two values only needed adding to the list. The invalid branch now raises ValueError after the flash, the way the rest of that module reports bad input.
  • Abelian variety geometric decomposition. CheckBox renders value="yes" but the parser tested == "on", so ticking the box in the form did nothing: the search used the ordinary dim*, number_fields and galois_groups columns instead of the geometric ones. It now accepts both values, keeping hand-written on links working.

Tests

New SearchBoxTest in lmfdb/tests/test_utils.py covers the rendering rules. Integration assertions in test_hgm.py, test_cmf.py, test_cmf2.py, test_lfunctions.py, test_modular_curves.py and test_av.py check the rendered pages, plus direct common_parse query-construction tests for the geometric decomposition checkbox. Tests compare class tokens as sets through a shared helper that asserts there is at most one class attribute, so nothing depends on ordering. No new test files, so the CI count is unchanged.


Ported from roed-math#23, where the full write-up and comment history live.

🤖 Generated with Claude Code

roed314 and others added 6 commits July 19, 2026 11:23
On search results pages, inputs that constrain the displayed query are now
visually distinguished with a subtle yellow tint so users can see at a glance
why results are filtered.

What/why: constraint-eligible search boxes render a `search_constraint` class
on results pages, plus `search_active` when their value in `info` is non-empty;
CSS tints active text/select backgrounds and rings active checkboxes. Display
controls (count, sort/column selectors, DynStats variable boxes, and diagram
axis/color selectors) set `is_constraint = False` so they are never
highlighted. Hidden inputs are untouched. A small JS hook keeps the highlight
live as the user edits, pairing with the existing stale/fresh button signal.

How verified: flask test client across elliptic curves, number fields, abstract
groups, abvar/Fq (checkbox), modular-curve/groups diagram search, and DynStats;
browse pages render unchanged; direct unit checks of every box subclass; all 19
color schemes resolve the new `search_active_background` key; /style.css renders;
ECQ diagram-search and CMF dynamic-stats tests pass; pyflakes clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Emit exactly one class attribute per rendered input.  Class names now go
through a first-class `classes` argument on SearchBox, merged with
`advanced` and the `search_*` classes and deduplicated; any class written
into `extra` is merged too, so a tag can no longer end up with two class
attributes (invalid HTML, and browsers discard one of them, which was
making the feature silently fail on the hypergeometric and CMF forms).
The existing `extra=['class=...']` uses migrate to `classes`, and `extra`
is copied rather than shared.

Stop marking presentation-only controls as constraints.  The trace and
Euler tables' `n`, `n_primality` and `view_modp` choose which columns are
displayed, not which rows match, so they are no longer highlighted; this
includes the `n_primality=primes` that set_Trn inserts on every trace
search.  `an_modulo` is contextual, since it only enters the query
through the trace constraints, so `is_constraint` now also accepts a
function of `info`.

Keep `search_active` describing the results on screen.  The JS that
retagged inputs as the user typed is gone, so the highlight no longer
claims a constraint that has not been submitted; the search button's
existing fresh/stale signal still reports that the form was edited.

Mark active checkboxes with a box-shadow ring rather than an outline, so
the keyboard focus indicator survives, in a darker shade with usable
contrast, and tint text inputs with background-color so the shorthand
does not reset a background image.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A select beside a text box usually says how to read that box, and is
handed to a @search_parser function as its mode.  Those return
immediately when their field is empty, so such a select cannot affect
the query on its own; TextBoxWithSelect now treats its select as
constraining only once the text box it qualifies has been filled in.

Six selects are read directly while parsing instead, and so do restrict
the results by themselves: the level type in classical modular forms and
modular curves, the conductor type in elliptic curves and mod-l Galois
representations, and the two CMF parities, which usually arrive with
their text boxes empty because simult_change() submits both at once.
Those pass is_constraint=True.  Modular curves matters in particular
because modcurve_browse.html links straight to ?level_type=... searches.

The hypergeometric prime and the abelian variety geometric-decomposition
checkbox are the same shape: both only pick which column a partner box is
matched against, so they are gated on those boxes rather than highlighted
whenever set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The level type offers "prime squared" and "powerful", but common_parse
recognized neither, so both fell to the invalid-type branch.  That branch
flashes an error and returns a redirect, and both callers use the query
common_parse builds while ignoring what it returns, so the redirect was
dropped and the search ran with no condition on the level: picking either
option showed an error and then every level anyway.

mf_newforms and mf_newspaces already have level_is_prime_square and
level_is_powerful, so the two values only needed adding to the list.  The
invalid branch now raises ValueError after the flash, the way the rest of
this module reports bad input, so an unrecognized type stops the search
rather than quietly running it unfiltered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CheckBox renders value="yes", but abvar's common_parse switched to the
geometric columns only on "on", the value a browser supplies when a
checkbox has no value attribute.  Ticking "Use geometric decomposition"
in the form and searching was therefore indistinguishable from leaving it
alone: the dimension, number field and Galois group inputs went to
dim*, number_fields and galois_groups rather than their geometric
counterparts.  Accept both values so hand written "on" links keep working.

Test the query common_parse builds rather than the rows that come back,
so the three retargeted field families are pinned without depending on
what is currently in the database.

Also tighten the prime square predicate in test_level_types, which as
written also accepted fourth and sixth powers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jenpaulhus

Copy link
Copy Markdown
Contributor

I like this addition! Just a note that on genus 2 curve searches, the two include/exclude boxes never get highlighted. That might be fine (as the box immediately above each of them does change colors) but wanted to point it out in case that was not intended.

@jwj61

jwj61 commented Aug 6, 2026

Copy link
Copy Markdown
Member

I also like it. I looked at it with a blue color scheme. Have others looked with others?

@roed314

roed314 commented Aug 6, 2026

Copy link
Copy Markdown
Member

@havarddj had some opinions on the color scheme, so we should wait to merge this until we get some input from him.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Show active constraints on search results pages

4 participants