Skip to content

feat(search): advanced options on the form, filters and sorting on results - #104

Merged
jouwdan merged 4 commits into
mainfrom
claude/search-advanced-filtering-ma7sqs
Aug 14, 2026
Merged

feat(search): advanced options on the form, filters and sorting on results#104
jouwdan merged 4 commits into
mainfrom
claude/search-advanced-filtering-ma7sqs

Conversation

@jouwdan

@jouwdan jouwdan commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

The search form was a box, a forum and a sort order; the results page was a
listing you could only re-run from the form. Both grow the half MyBB has had
since 2006, and the results page grows the half it never had.

The form. Behind a disclosure that opens itself when anything in it is
set: a forum with include subforums, one or more authors by username, a date
window, whether to match thread titles alone, and whether a result is a post
or a thread. An author nobody answers to refuses the search by name rather
than quietly matching everybody.

The results page. A filter and sort panel built from the search's own
facets — each forum and author with the number of results it would leave —
plus one chip per filter that is on, each with an href that removes only
itself. The filters live in the results URL as a refinement on the stored
search, so changing them costs no search row and no flood interval, and the
reader's original search stays where it was.

A refinement narrows and never widens: a titles-only search cannot be refined
back into the whole post, a week cannot be refined into a year, and a forum
outside the search cannot be refined into it. trimRefinement drops anything
that only repeats what the search already asked for, so "is this filtered?"
has an answer and the panel never offers an option that would do nothing.

@meith/search grows the vocabulary — sort, match, grouping, period, and the
narrowing rules over them — so the app, the view and the REST route share one
definition of what a filter is. The Postgres repository grows titles-only
matching, real thread grouping (each thread represented by its best match
under the current order), and summarize(): one bounded query returning the
count and both facet breakdowns, computed without the forum and author filters
so the counts hold still as a reader moves between them. The count reuses the
20,000-row window that already bounds ranking, and says so when it stops
there.

Theme API 0.13: three optional fields — SearchFormModel.advanced,
SearchResultsModel.refine, and within.hidden. A theme that never reads
them renders exactly what it rendered before.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01DC4qB9MRBHiseChyrnCTxR

claude added 4 commits August 14, 2026 15:09
…sults

The search form was a box, a forum and a sort order; the results page was a
listing you could only re-run from the form. Both grow the half MyBB has had
since 2006, and the results page grows the half it never had.

**The form.** Behind a disclosure that opens itself when anything in it is
set: a forum with *include subforums*, one or more authors by username, a date
window, whether to match thread titles alone, and whether a result is a post
or a thread. An author nobody answers to refuses the search by name rather
than quietly matching everybody.

**The results page.** A filter and sort panel built from the search's own
facets — each forum and author with the number of results it would leave —
plus one chip per filter that is on, each with an href that removes only
itself. The filters live in the results URL as a *refinement* on the stored
search, so changing them costs no search row and no flood interval, and the
reader's original search stays where it was.

A refinement narrows and never widens: a titles-only search cannot be refined
back into the whole post, a week cannot be refined into a year, and a forum
outside the search cannot be refined into it. `trimRefinement` drops anything
that only repeats what the search already asked for, so "is this filtered?"
has an answer and the panel never offers an option that would do nothing.

`@meith/search` grows the vocabulary — sort, match, grouping, period, and the
narrowing rules over them — so the app, the view and the REST route share one
definition of what a filter is. The Postgres repository grows titles-only
matching, real thread grouping (each thread represented by its best match
under the current order), and `summarize()`: one bounded query returning the
count and both facet breakdowns, computed without the forum and author filters
so the counts hold still as a reader moves between them. The count reuses the
20,000-row window that already bounds ranking, and says so when it stops
there.

Theme API 0.13: three optional fields — `SearchFormModel.advanced`,
`SearchResultsModel.refine`, and `within.hidden`. A theme that never reads
them renders exactly what it rendered before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DC4qB9MRBHiseChyrnCTxR
"Every forum in this search" is wider than the select it sits in, so a
three-column filter panel truncated it to "Every forum in this sea". The
panel already says it filters *these results*; the option only has to say
"not one of them in particular".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DC4qB9MRBHiseChyrnCTxR
Filtering and sorting arrived as a six-control grid in a card, which is most of
a screen above a listing — a listing you cannot see is a listing you cannot
read. The controls are the same; the shape is not.

The order leaves the form and becomes three links in a segmented control:
changing it is one decision and the commonest one, so it should not need a
submit, and each href carries the filters already applied. The filters stay a
GET form — several decisions at once, one apply — but the labels move beside
their controls rather than above them, and the whole thing reads as one bar
between the heading and the results. Anything on is a chip that removes itself,
and "clear filters" appears beside apply only when there is something to clear.

An earlier pass folded the filters behind a disclosure instead. It saved more
space and was worse: a filter nobody can see is a filter nobody uses, and a
reader looking at a narrowed page with nothing on screen to say so does not
trust it. `SearchRefineModel` therefore carries no open/closed state at all —
`sorts` and `sortsLabel` are new, and the model says in its own documentation
that this is a strip and why hiding it is not the way to keep it one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DC4qB9MRBHiseChyrnCTxR
`search-light` and `search-dark` showed the results page before it had a
filter strip, which is the half of the page this branch changed. `pnpm
site:shots` wipes the directory and rephotographs the whole set against a
freshly seeded demo board, so the other twelve are the same designs with
different sample content and timestamps rather than deliberate changes; dues
came out byte-identical and is absent from the diff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DC4qB9MRBHiseChyrnCTxR
@jouwdan
jouwdan merged commit 15c5063 into main Aug 14, 2026
19 of 20 checks passed
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.

2 participants