Repository navigation
feat(search): the server picks the Messages list's order and names its terms - #1964
Merged
Merged
Conversation
With no `sort`, `GET /v1/messages` now ranks a search with a positive free-text word best match first, and lists one with none newest first. It used to list the oldest first. A `sort` the request names is applied as before, and `relevance` for a search with no free-text word is still `validation-failed`. Each page carries `search` beside its four keys: `sort`, the order the page is in, and `terms`, the free-text terms the search ranks by, read from `Expr::positive_text_terms`. The answer's schema is `ListMessagesResponse`. The document rules name that route and schema as the one page with a fifth key, and now fail any other page with a key beyond the four. Refs #1538 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Messages list sends no `sort` unless the person picked a Date order. The sort menu shows the order the page says it applied, offers Relevance only when the server returned terms, and the rows bold those terms. `freeTextTerms.ts`, the web's own copy of the server's reading of free text, is deleted, and so is `effectiveMessageSort`. Picking Relevance leaves `sort` out of the address. The menu offers it only when the server already ranks by default, and a kept `relevance` would follow the person to a search with no free-text word, which the server refuses. `useRoutePagedList` answers the latest page as `lastPage`, so a screen reads what a route says beside its rows from the one cache entry. Refs #1538 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`http-api.md`, "Lists", gains the one written exception to the four-key page, with its reason and the rejected alternative. `search.md` records that the server picks the Messages list's order when no `sort` is sent. CHANGELOG entry under 0.11.0. Refs #1538 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mbeisser1
commented
Oct 6, 2026
mbeisser1
left a comment
Member
Author
There was a problem hiding this comment.
Review of 3c00441 (the PR merged with main at cb1b2f0).
- Standards: 5 findings, below.
- Spec: 1 finding, below.
- Correctness: no findings. The reported
sortandtermsalways match the applied ORDER BY,searchcomes back on every page (an empty last page included),lastPagecannot carry a previous query's terms (noplaceholderData, and the list remounts per query), and the other callers ofGET /v1/messagessend an explicitsort.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hand-written page under a name without the Page_ prefix was not seen as a page, so the no-extra-keys rule never ran on it. http-api.md gives the check its own paragraph, apart from the rejected alternative. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Filter kept both, and nothing checked that they agreed. message_list_sort_text now fails loudly on a key missing from MESSAGE_LIST_SORT_KEYS rather than reporting an empty sort. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The query was built twice from the same terms, once in compile and once in Filter::rank_query. The default order asks whether there are terms rather than building the query to throw it away. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An exhaustive match gives each key its spelling, so a new key without a name fails the build rather than a request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Knowing a page only by its four keys meant a Page<T> that lost one was no longer a page, and every page rule stopped running without failing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mbeisser1
marked this pull request as ready for review
October 6, 2026 04:02
mbeisser1
commented
Oct 6, 2026
Member
Author
Review summaryReviewed and fixed on 3f0539b. CI run 37411846666 passed on that commit. First review (of 3c00441)
Re-review of the fixes Merges of main
Other
|
8 of 9 tasks
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.
Feature Description
The server now decides the Messages list's order and tells the web app what it decided. With no
sort,GET /v1/messagesputs the best match first when the query has a positive free-text term, and the newest message first when it has none. Each page also says which order it applied and which terms it ranks by. The web app stops parsing the search itself:web/src/lib/freeTextTerms.ts, its copy ofExpr::positive_text_terms, is deleted.This is option 1 of #1538, decided on 2026-10-05. For the person using the app, nothing changes except where the two parsers disagreed. In those cases the app now shows what the server does, so it can no longer offer Relevance for a search the server would refuse.
User Story
As a person searching my messages, I want the sort menu and the bold matches to follow what the server actually ranks by, so the list never offers an order that fails or hides one that works.
Implementation Details
Server.
search::Filterkeeps the positive free-text terms it builds its rank query from (Filter::ranked_terms).list_messagesreadssortwith no default. Once the query is compiled, it picksrelevancewhen there are terms and-datewhen there are none (default_message_list_sort). The answer is aListMessagesResponse: the four page keys plussearch: MessageSearch { sort, terms: [FreeTextTerm { text, prefix }] }.sortuses the same spelling as the parameter (message_list_sort_text). A namedsortis applied as before, andsort=relevanceon a query with no positive term is stillvalidation-failed.Field name. I chose
searchrather thanquery.queryis already a string elsewhere on the wire (an Export Run'squeryscope), and http-api.md reserves the…Querytype suffix for query strings, soListMessagesQuerywould read as the query-string type.searchwith typeMessageSearchfollows the naming rule for "a thing named for what it is".Document rules.
openapi/document_rules.rshas aPAGE_EXCEPTIONconstant namingGET /v1/messagesandListMessagesResponse. Four checks enforce it:search.Page_*included, now fails on any property beyond its allowed keys. This check is new.I checked that the rule bites by renaming the allowed key in the constant: the test then fails with "has search beside its four keys".
Web.
MessageSearchListsends nosortunless a Date order was picked.lastPage.search.sort. It offers Relevance only whensearch.termsis non-empty, and the rows boldsearch.terms.useRoutePagedListnow also returnslastPage, typed through an optional extra-fields generic onPagedFetchPage. It adds no new cache or hook.effectiveMessageSortand itsrankablebranch are deleted.Picking Relevance clears the pick. Relevance is offered only when the server already ranks by default. Picking it removes
sortfrom the address (pickedSortParam), andsort=relevancein the address reads as no pick. Keepingrelevancewould carry it to the next search, and the server refuses it for a field-only query. This matches the old behaviour, whereeffectiveMessageSortdropped a Relevance pick for a query it could not rank.Key Files Changed
crates/server/server/src/messages_api.rs: the default order,ListMessagesResponse,MessageSearch,FreeTextTermcrates/server/server/src/search/{mod,emit}.rs:Filter::ranked_termscrates/server/server/src/db/conversation_messages.rs:default_message_list_sort,message_list_sort_text;DEFAULT_MESSAGE_LIST_SORTremovedcrates/server/server/src/openapi/document_rules.rs: the named page exception, plus the no-extra-keys check on every pageweb/src/screens/MessageSearchList.tsx,web/src/lib/messageSearchSort.ts,web/src/lib/resultsView.ts,web/src/lib/routeQuery.ts,web/src/components/ResultsColumn.tsxweb/src/lib/freeTextTerms.tsand its test: deleteddocs/architecture/http-api.md("Lists": the written exception, with its reason and the rejected alternative),docs/architecture/search.md(the default order rule),CHANGELOG.mdHTTP API Changes
GET /v1/messages: with nosort, the default order changes fromdate(oldest first) torelevancewhenqhas a positive free-text term, and-date(newest first) otherwise.GET /v1/messages: the answer changes fromPage_MessagetoListMessagesResponse, which is{items, total, limit, offset, search: {sort, terms: [{text, prefix}]}}.searchdescribes the query, not the rows. It is the one exception to the four-key page, written down indocs/architecture/http-api.md, "Lists".docs/src/assets/openapi.jsonandweb/src/lib/serverApi.types.tsare regenerated.Database Changes
schema/sql/*.sqlchanged (every database rebuilds empty and re-imports; the fingerprint is computed, nothing to bump)How to Test
dinner. The request has nosort. The menu reads Relevance and offers Relevance and Date, anddinneris bold in each row.sort=-date. Pick Relevance:sortleaves the address and the list is ranked again.date:2024 -dinner. The request has nosort. The menu reads Date, Newest first and offers only Date, the first rows are from Dec 31, 2024, and nothing is bold.I did all four in a real browser (Playwright) against this branch's server on a scratch data directory, port 8090, serving the built
web/dist.New tests, each confirmed to fail without the fix. I ran each against the pre-fix server or web sources (
git stash/git checkout origin/main --on the non-test files):messages_api::tests::with_no_sort_the_server_picks_the_order_and_reports_it: fails on the order assertion, since the old default was oldest first.messages_api::tests::a_named_sort_is_applied_and_reportedmessages_api::tests::the_page_names_the_terms_the_search_ranks_by: covers-word,not word, a negated group-("…" or …), a field wordbody:work,date:2024, a phrase and a prefix.MessageSearchList.test.tsx: "sends no sort, and shows the order the server applied", "offers only Date when the server returns no terms to rank by", "takes the order and the terms from the server, not from the words typed", and "bolds the terms the server returned". All four failed on the old component.Updated tests. Two existing server tests assumed four keys or the oldest-first default. The web tests for
messageSearchSort,resultsViewanduseConversationMessageswere updated for the new shape and for Relevance not being kept as a pick.Commands run, all passing:
./scripts/check-pr.shcargo test -p message-crate-server: 1293 passedcd web && npm test: 2057 passedcd web && npm run build./scripts/check-generated-api-types.shcd docs && npm run check && npm run buildChecklist
./scripts/check-pr.shpasses and formatter rewrites are committedRelated Issues
Closes #1538
Found in the review of #1526. ADR 0004 (one module parses the search language) and ADR 0002 (one way to fetch data in
web/) apply.Screenshots / Demo
None: nothing changes on screen for a search that both parsers read the same way.
Deployment Notes
The change breaks clients by design, as the no-backwards-compatibility rule allows: a client that relied on the oldest-first default of
GET /v1/messagesnow gets the new default. The only clients in this repository are the web app and the server's own tests, and both are updated. No environment or dependency changes.🤖 Generated with Claude Code