Remember the last sort for each top collection (#415) - #831
Merged
Conversation
updateSettings passed the settings as a `params` wrapper, which xior sends
as the request body `{"params": {...}}`. The endpoint validates the body's
top level keys, so every one of them was unknown, the update validated as
empty and the request saved nothing.
Nothing was actually lost: the browse page request that follows a settings
change carries the same settings as query params and persists them. The
save request itself has just been doing nothing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PwhwRtnMZ2pLcnhF96AyWA
Sorting is one global setting today, so sorting issues by added time leaves the publisher list in added-time order too. Issue #415 asks each top collection to keep the sort it was last browsed with. Add SettingsBrowser.collection_order_memory, a map of top collection to the sort it was left in. It follows table_columns, the other per top collection map: a JSON column in DIRECT_KEYS, so loading, saving, resetting and saved-view cloning all carry it without further wiring. The map round-trips out of stored settings, so the validator cleans rather than rejects: unknown collections and sorts that no longer exist are dropped with a warning instead of failing the whole browse page. search_score is never stored, since relevance ordering only means anything while its search is running. Empty for every existing row, which reads as "nothing customized yet" and keeps today's carry-the-sort-over behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PwhwRtnMZ2pLcnhF96AyWA
Switching top collections now files the sort the old one is left in and hands back the sort the new one was last browsed with. A collection with nothing filed keeps whatever sort arrives with it, so nothing changes until you sort something. Searching does the same pair: entering a search files the sort it displaces and clearing the search puts it back, instead of always landing on sort_name. Whether a payload is the user changing collections is decided before the validators run, since they inject an orderBy of their own that would otherwise look like a payload that already carries a sort. Payloads that really do carry one -- settings loads, saved views, redirects -- keep it. The memory rides the settings the browse page request already echoes, so there is no new request to persist it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PwhwRtnMZ2pLcnhF96AyWA
The browser store handles collection switches the user makes, but the server makes some of its own: a url whose collection forces a different top, or a redirect after folder view is turned off. Those bypass the store, so the old collection's sort leaked into the new one and would later be filed under the wrong collection, overwriting a real memory. Apply the same file-and-restore in the settings GET when validation rewrites the top collection, and in raise_redirect, which is the one place every settings-mask redirect passes through. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PwhwRtnMZ2pLcnhF96AyWA
The sidecar adds columns it is missing when it opens, so a backup written before this column restores an empty memory rather than failing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PwhwRtnMZ2pLcnhF96AyWA
Backend: the field's wiring, the lenient validator, the file-and-restore helper, and the HTTP round trips -- PATCH, the browse page query params that actually persist the map, reset, saved views, and a url that forces a different top collection. Frontend: switching collections, coming back to one, a collection with nothing filed, payloads that carry their own sort, and the sort a search displaces and gives back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PwhwRtnMZ2pLcnhF96AyWA
_get_browser_order_defaults has no callers; set_order_by_default is the copy that runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PwhwRtnMZ2pLcnhF96AyWA
ajslater
marked this pull request as ready for review
August 30, 2026 07:25
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 #415.
Sorting is one global setting today, so sorting Issues by added time leaves the
Publishers list in added-time order too. Each top collection now keeps the sort
it was last browsed with.
Per the decisions on the plan: a collection with nothing remembered keeps
whatever sort arrives with it (so nothing changes until you sort something),
clearing a search restores the sort the search replaced, the field is spelled
out as
collection_order_memory/collectionOrderMemory, and there is no offswitch — a settings reset clears it, and the map only ever holds sorts you
actually used.
How it works
SettingsBrowser.collection_order_memoryis a map of top collection to the sortit was left in. It follows
table_columns, the other per-top-collection map: aJSON column in
DIRECT_KEYS, so loading, saving, resetting and saved-viewcloning carry it with no further wiring. It is empty for every existing row,
which reads as "nothing customized yet".
The browser store files the outgoing sort and restores the incoming one when the
user changes collections, and does the same pair around a search. The memory
rides the settings the browse page request already echoes, so there is no new
request to persist it.
Two things the store cannot see needed a server-side counterpart: a url whose
collection forces a different top (a
/browse/foldersbookmark, returning fromthe reader) and a redirect after folder view is turned off. Both go through the
same file-and-restore helper — without it, one collection's sort leaked into
another and would later be filed under the wrong collection, overwriting a real
memory.
search_scoreis never stored, since relevance ordering only means anythingwhile its search runs, and a restore never overrules a sort the payload is
already asking for.
Also here:
paramswrapper, so the body was{"params": {...}}and every top-level keywas unknown. Nothing was lost — the browse page request that follows persists
the same settings — but the save request itself did nothing. Fixed first, on
its own commit.
_get_browser_order_defaultshad no callers;set_order_by_defaultis thecopy that runs.
OPDS is untouched: it has its own settings row and its serializer never gains
the field.
Verification
tests/test_collection_order_memory.pycovers the field wiring, the lenient validator, the file-and-restore helper,
and the HTTP round trips — PATCH, the browse page query params that actually
persist the map, reset, saved views, and a url that forces a different top
collection.
tests/test_user_data_restore.pygains sidecar round-trip andolder-backup cases.
frontend/tests/unit/browser-store-order-memory.test.js(11 tests)passes alongside the existing search-clear tests. The rest of the suite passes
except
stats-tab.test.js, which fails identically on a cleandevelopcheckout and is unrelated to this change.
ruff check,ruff format --check, eslint and prettier are clean.One caveat on the frontend run: this sandbox has Node 22 while the project
builds on Node 26, and
src/datetime.jsconstructsIntl.DurationFormatatimport, which Node 22 lacks. I stubbed it locally to run the suite and reverted
the stub; nothing in the diff touches it, and CI's Node 26 has the real thing.
Generated by Claude Code