feat(search): add a configurable default content type (#1371)

Closes #1018.

Worth correcting the issue first: the search tab is not hardcoded.
`useContentTypePreferences` has persisted the user's choice to
localStorage since #564, so a browser that has picked a tab already
keeps it. What is missing is the other half the issue asks for, a
default for a browser that has stored nothing, and a per-user override.

`DEFAULT_CONTENT_TYPE` is a user overridable select next to
`BOOK_LANGUAGE`, so it follows the same path: global value in Settings,
per-user value in Search Preferences, resolved with `user_id` in
`/api/config`. The frontend uses it only when this browser has no stored
choice, which is captured before the existing effect writes one, so
nothing changes for anyone who has already picked a tab.

The resolution is a pure function in `utils/contentTypePreference.ts`
rather than logic inside the hook, since vitest here has no jsdom and
the existing tests cover resolvers like `resolveDefaultLanguageCodes`
the same way.

One small move in `App.tsx`: the `config` state was declared below the
hook that now reads it, so it moved above it.

## Verification

- `tests/core/test_config_api.py`: the payload carries
`default_content_type` and reads it with the user's id.
- `tests/core/test_admin_users_api.py`: the key appears in the per-user
search preferences list.
- `src/frontend/src/tests/contentTypePreference.test.ts`: a stored tab
wins, combined mode survives, the server default applies when nothing is
stored, and an unrecognised value falls back to ebook.
- Python suite (3163) and frontend suite (201) green, plus ruff, ruff
format, basedpyright, vulture, tsc, oxlint, oxfmt and the production
build.
This commit is contained in:
splitsec2
2026-09-25 18:14:42 -04:00
committed by GitHub
parent ce1092db7f
commit fe99d4bb5b
10 changed files with 182 additions and 23 deletions
+1
View File
@@ -1367,6 +1367,7 @@ class TestAdminSearchPreferences:
assert data["keys"] == [
"SEARCH_MODE",
"BOOK_LANGUAGE",
"DEFAULT_CONTENT_TYPE",
"SHOW_COMBINED_SELECTOR",
"FORCE_COMBINED_SEARCH",
"METADATA_PROVIDER",
+3
View File
@@ -49,6 +49,7 @@ def test_config_endpoint_uses_user_scope_and_runtime_flags(main_module, client):
"DEFAULT_RELEASE_SOURCE_AUDIOBOOK": "audiobookbay",
"DOWNLOAD_TO_BROWSER_CONTENT_TYPES": ["book", "audiobook"],
"BOOK_LANGUAGE": ["de", "en"],
"DEFAULT_CONTENT_TYPE": "audiobook",
"AUTO_OPEN_DOWNLOADS_SIDEBAR": False,
"HARDCOVER_AUTO_REMOVE_ON_DOWNLOAD": True,
"AA_DEFAULT_SORT": "newest",
@@ -77,6 +78,7 @@ def test_config_endpoint_uses_user_scope_and_runtime_flags(main_module, client):
assert data["default_release_source_audiobook"] == "audiobookbay"
assert data["download_to_browser_content_types"] == ["book", "audiobook"]
assert data["default_language"] == ["de", "en"]
assert data["default_content_type"] == "audiobook"
assert data["settings_enabled"] is True
assert data["metadata_default_sort"] == "relevance"
@@ -84,6 +86,7 @@ def test_config_endpoint_uses_user_scope_and_runtime_flags(main_module, client):
assert ("SHOW_COMBINED_SELECTOR", 42) in calls
assert ("DOWNLOAD_TO_BROWSER_CONTENT_TYPES", 42) in calls
assert ("BOOK_LANGUAGE", 42) in calls
assert ("DEFAULT_CONTENT_TYPE", 42) in calls
def test_config_endpoint_falls_back_to_audiobook_metadata_provider(main_module, client):