Background
The current Sort/Relevance facet dropdown (ol-facet-drop _renderSort()) renders a flat list of radio buttons mapping directly to Open Library Solr sort values:
```js
// frontend/src/utils/filters.js
export const SORT_OPTIONS = [
{ value: '', label: 'Relevance' },
{ value: 'new', label: 'Newest first' },
{ value: 'old', label: 'Oldest first' },
{ value: 'rating desc', label: 'Top rated' },
{ value: 'readinglog desc',label: 'Most read' },
{ value: 'title', label: 'Title A–Z' },
];
```
The flat list conflates two orthogonal decisions — what field to sort on and which direction — and hides "Reading Log" as a peer of "Relevance" with no indication that sub-options exist.
Proposed redesign
Part 1 — Sort by / Order two-section layout (inspired by GitHub's sort UX)
Split the dropdown into two named sections:
Sort by (checkbox-style, single selection — not radio so the label can be styled more flexibly):
- Relevance (no Order section shown when selected)
- Date published → Order: Newest / Oldest
- Rating → Order: Highest / Lowest
- Reading log → Order: Most read / Least read (see Part 2)
- Title → Order: A–Z / Z–A
Order (only visible when a field that has a direction is selected):
Shows the two direction options for the chosen sort field, e.g. "Newest" / "Oldest" for Date.
This keeps the Solr sort param values (new, old, rating desc, readinglog desc, title) but derives them from two independent UI selections rather than encoding both in one flat list.
Part 2 — Reading Log submenu (right-chevron trigger, inspired by GitHub's Reactions submenu)
"Reading Log" in the Sort by section should behave like GitHub's "Reactions" item: a right-facing chevron (›) indicates a submenu. Clicking/hovering opens a nested panel with:
- Want to Read
- Currently Reading
- Already Read
Each maps to a different OL reading-log sort bucket. The selected sublabel should appear in the facet button's chip text (e.g. "Reading Log: Want to Read").
This requires research into which Solr/OL API parameters map to each reading-log state. The current value readinglog desc is the aggregate; individual states may need different params.
Implementation notes
Data shape
The sort field in EMPTY_FILTERS is currently a single string. Implementing Order as a separate axis may require adding a sortDir field, or keeping a single derived string (simpler — avoids ripple changes). Worth deciding before coding.
Pure-logic first (per AGENTS.md)
All new option constants and any buildChips / buildSearchParams changes belong in filters.js and must be covered by Vitest before touching the component.
Component changes
ol-facet-drop.js — _renderSort() becomes a two-panel layout; submenu state (open/closed) is local to the component
filters.js — possibly new SORT_FIELDS and per-field direction options; update getSortLabel, buildChips, buildSearchParams
ol-search-bar.js — minimal; the facet button label comes from _facetLabel('sort') which calls getSortLabel
Submenu interaction model
Two options to evaluate:
- Click to toggle — clicking the Reading Log row expands an inline sub-list (no separate panel); simpler to implement and accessible
- Right-panel submenu — clicking opens a second panel sliding in from the right (matches GitHub's pattern but more complex)
Option 1 is recommended unless the design specifically needs the slide-in feel.
OL API research needed
- Confirm Solr params for per-reading-log-state sorts (want-to-read / currently-reading / already-read)
- Check whether the OL search API exposes these as distinct
sort= values or requires a separate filter param
- Update
backend/main.py to proxy the new param if needed
Acceptance criteria
Background
The current Sort/Relevance facet dropdown (
ol-facet-drop_renderSort()) renders a flat list of radio buttons mapping directly to Open Library Solr sort values:```js
// frontend/src/utils/filters.js
export const SORT_OPTIONS = [
{ value: '', label: 'Relevance' },
{ value: 'new', label: 'Newest first' },
{ value: 'old', label: 'Oldest first' },
{ value: 'rating desc', label: 'Top rated' },
{ value: 'readinglog desc',label: 'Most read' },
{ value: 'title', label: 'Title A–Z' },
];
```
The flat list conflates two orthogonal decisions — what field to sort on and which direction — and hides "Reading Log" as a peer of "Relevance" with no indication that sub-options exist.
Proposed redesign
Part 1 — Sort by / Order two-section layout (inspired by GitHub's sort UX)
Split the dropdown into two named sections:
Sort by (checkbox-style, single selection — not radio so the label can be styled more flexibly):
Order (only visible when a field that has a direction is selected):
Shows the two direction options for the chosen sort field, e.g. "Newest" / "Oldest" for Date.
This keeps the Solr
sortparam values (new,old,rating desc,readinglog desc,title) but derives them from two independent UI selections rather than encoding both in one flat list.Part 2 — Reading Log submenu (right-chevron trigger, inspired by GitHub's Reactions submenu)
"Reading Log" in the Sort by section should behave like GitHub's "Reactions" item: a right-facing chevron (›) indicates a submenu. Clicking/hovering opens a nested panel with:
Each maps to a different OL reading-log sort bucket. The selected sublabel should appear in the facet button's chip text (e.g. "Reading Log: Want to Read").
This requires research into which Solr/OL API parameters map to each reading-log state. The current value
readinglog descis the aggregate; individual states may need different params.Implementation notes
Data shape
The
sortfield inEMPTY_FILTERSis currently a single string. Implementing Order as a separate axis may require adding asortDirfield, or keeping a single derived string (simpler — avoids ripple changes). Worth deciding before coding.Pure-logic first (per AGENTS.md)
All new option constants and any
buildChips/buildSearchParamschanges belong infilters.jsand must be covered by Vitest before touching the component.Component changes
ol-facet-drop.js—_renderSort()becomes a two-panel layout; submenu state (open/closed) is local to the componentfilters.js— possibly newSORT_FIELDSand per-field direction options; updategetSortLabel,buildChips,buildSearchParamsol-search-bar.js— minimal; the facet button label comes from_facetLabel('sort')which callsgetSortLabelSubmenu interaction model
Two options to evaluate:
Option 1 is recommended unless the design specifically needs the slide-in feel.
OL API research needed
sort=values or requires a separate filter parambackend/main.pyto proxy the new param if neededAcceptance criteria
filters.jspure-logic changes covered by Vitest before component changes