diff --git a/NEWS.md b/NEWS.md
index 69aeed200..596b0047d 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -17,6 +17,8 @@ border-radius: 128px;
- Renamed comics no longer contain colons or other characters Windows and
SMB shares can't store.
- A Metron API key is no longer overridden by a `~/.netrc` login.
+ - Dynamic covers, custom covers and finishing a book on its last page are
+ always on; those settings and "Read RTL Comics LTR" are gone.
## v2.5.4
diff --git a/codex/choices/browser.py b/codex/choices/browser.py
index f4b42aa98..6c6aab844 100644
--- a/codex/choices/browser.py
+++ b/codex/choices/browser.py
@@ -776,8 +776,6 @@ def clean_table_columns(value) -> tuple[dict[str, list[str]], list[str]]:
BROWSER_DEFAULTS = MappingProxyType(
{
- "custom_covers": True,
- "dynamic_covers": True,
"filters": _DEFAULT_FILTERS,
"order_by": "sort_name",
"order_reverse": False,
diff --git a/codex/choices/reader.py b/codex/choices/reader.py
index fcf587c00..d6419a470 100644
--- a/codex/choices/reader.py
+++ b/codex/choices/reader.py
@@ -24,10 +24,8 @@
)
READER_DEFAULTS = MappingProxyType(
{
- "finish_on_last_page": True,
"fit_to": "W",
"reading_direction": "ltr",
- "read_rtl_in_reverse": False,
"two_pages": False,
"page_transition": True,
"cache_book": False,
diff --git a/codex/librarian/telemeter/per_user_stats.py b/codex/librarian/telemeter/per_user_stats.py
index caddaa1f0..bcbd5c3f0 100644
--- a/codex/librarian/telemeter/per_user_stats.py
+++ b/codex/librarian/telemeter/per_user_stats.py
@@ -53,8 +53,6 @@
"order_by",
"view_mode",
"table_cover_size",
- "dynamic_covers",
- "custom_covers",
)
# Ordered, unlike SettingsReader.DIRECT_KEYS which is a frozenset. A test pins
# the two to the same set so this cannot drift from the model.
@@ -62,8 +60,6 @@
"fit_to",
"two_pages",
"reading_direction",
- "read_rtl_in_reverse",
- "finish_on_last_page",
"page_transition",
"cache_book",
)
diff --git a/codex/librarian/telemeter/stats.py b/codex/librarian/telemeter/stats.py
index 17084b978..52ad1e3dd 100644
--- a/codex/librarian/telemeter/stats.py
+++ b/codex/librarian/telemeter/stats.py
@@ -62,13 +62,11 @@
(
"top_collection",
"order_by",
- "dynamic_covers",
"view_mode",
"table_cover_size",
- "custom_covers",
),
),
- (SettingsReader, ("finish_on_last_page", "fit_to", "reading_direction")),
+ (SettingsReader, ("fit_to", "reading_direction")),
)
# Sections built by a single collector function, in payload order.
_SIMPLE_SECTIONS: Final = (
diff --git a/codex/migrations/0057_remove_pinned_settings.py b/codex/migrations/0057_remove_pinned_settings.py
new file mode 100644
index 000000000..8f33e4a75
--- /dev/null
+++ b/codex/migrations/0057_remove_pinned_settings.py
@@ -0,0 +1,39 @@
+"""Generated by Django 6.1.2 on 2026-10-19 04:16."""
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ """
+ Remove four settings that stopped being user options.
+
+ v2.5.x pinned dynamic covers and custom covers on, finishing a book on its
+ last page on, and reading RTL comics in reverse off, and dropped their
+ checkboxes, but kept the columns so the toggles could come back with a
+ revert. They are gone for good now. All four shipped in 0038, so they
+ come out here rather than in a fold. Reversible: the browser columns have
+ defaults and the reader columns are nullable.
+ """
+
+ dependencies = [
+ ("codex", "0056_match_mode_ask"),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name="settingsbrowser",
+ name="custom_covers",
+ ),
+ migrations.RemoveField(
+ model_name="settingsbrowser",
+ name="dynamic_covers",
+ ),
+ migrations.RemoveField(
+ model_name="settingsreader",
+ name="finish_on_last_page",
+ ),
+ migrations.RemoveField(
+ model_name="settingsreader",
+ name="read_rtl_in_reverse",
+ ),
+ ]
diff --git a/codex/models/settings.py b/codex/models/settings.py
index 64668c840..7b776414b 100644
--- a/codex/models/settings.py
+++ b/codex/models/settings.py
@@ -362,18 +362,6 @@ class SettingsBrowser(SettingsBase):
search = CharField(max_length=4095, default="", blank=True)
# Display preferences
- #
- # PENDING SCHEMA REMOVAL
- #
- # The next two are no longer user options: their checkboxes are gone
- # from the browser settings drawer and the behavior is pinned on in
- # ``views/browser/annotate/cover.py``. The columns stay so nobody's
- # stored preference is destroyed while we find out whether anyone misses
- # the toggles -- restoring them is a revert, not a data migration. Drop
- # these two, their serializer fields and their ``BROWSER_DEFAULTS``
- # entries together.
- custom_covers = BooleanField(default=True)
- dynamic_covers = BooleanField(default=True)
twenty_four_hour_time = BooleanField(default=False)
always_show_filename = BooleanField(default=False)
@@ -408,8 +396,6 @@ class SettingsBrowser(SettingsBase):
"order_extra_keys",
"collection_order_memory",
"search",
- "custom_covers",
- "dynamic_covers",
"twenty_four_hour_time",
"always_show_filename",
"view_mode",
@@ -488,17 +474,6 @@ class SettingsReader(SettingsBase):
default="",
max_length=max_choices_len(ReadingDirectionChoices),
)
- # PENDING SCHEMA REMOVAL
- #
- # The next two are no longer user options: their checkboxes are gone from
- # the reader settings drawer and the behavior is pinned in
- # ``stores/reader.js`` -- finish-on-last-page on, read-RTL-in-reverse off
- # (its default, so a right-to-left book is read right to left). The
- # columns stay so nobody's stored preference is destroyed; restoring the
- # toggles is a revert, not a data migration. Drop these two, their
- # serializer fields and their ``READER_DEFAULTS`` entries together.
- read_rtl_in_reverse = BooleanField(default=None, null=True)
- finish_on_last_page = BooleanField(default=None, null=True)
page_transition = BooleanField(default=None, null=True)
cache_book = BooleanField(default=None, null=True)
@@ -508,8 +483,6 @@ class SettingsReader(SettingsBase):
"fit_to",
"two_pages",
"reading_direction",
- "read_rtl_in_reverse",
- "finish_on_last_page",
"page_transition",
"cache_book",
}
diff --git a/codex/serializers/admin/stats.py b/codex/serializers/admin/stats.py
index a3bf48a61..5d0cec547 100644
--- a/codex/serializers/admin/stats.py
+++ b/codex/serializers/admin/stats.py
@@ -80,13 +80,10 @@ class StatsSessionsSerializer(Serializer):
top_collection = CountDictField(required=False, read_only=True)
order_by = CountDictField(required=False, read_only=True)
- dynamic_covers = CountDictField(required=False, read_only=True)
- finish_on_last_page = CountDictField(required=False, read_only=True)
fit_to = CountDictField(required=False, read_only=True)
reading_direction = CountDictField(required=False, read_only=True)
view_mode = CountDictField(required=False, read_only=True)
table_cover_size = CountDictField(required=False, read_only=True)
- custom_covers = CountDictField(required=False, read_only=True)
multi_sort_count = IntegerField(required=False, read_only=True)
@@ -268,26 +265,18 @@ class StatsPerUserSerializer(Serializer):
browser_order_by_users = CountDictField(required=False)
browser_view_mode_users = CountDictField(required=False)
browser_table_cover_size_users = CountDictField(required=False)
- browser_dynamic_covers_users = CountDictField(required=False)
- browser_custom_covers_users = CountDictField(required=False)
browser_chosen_top_collection_users = CountDictField(required=False)
browser_chosen_order_by_users = CountDictField(required=False)
browser_chosen_view_mode_users = CountDictField(required=False)
browser_chosen_table_cover_size_users = CountDictField(required=False)
- browser_chosen_dynamic_covers_users = CountDictField(required=False)
- browser_chosen_custom_covers_users = CountDictField(required=False)
reader_global_fit_to_users = CountDictField(required=False)
reader_global_two_pages_users = CountDictField(required=False)
reader_global_reading_direction_users = CountDictField(required=False)
- reader_global_read_rtl_in_reverse_users = CountDictField(required=False)
- reader_global_finish_on_last_page_users = CountDictField(required=False)
reader_global_page_transition_users = CountDictField(required=False)
reader_global_cache_book_users = CountDictField(required=False)
reader_chosen_fit_to_users = CountDictField(required=False)
reader_chosen_two_pages_users = CountDictField(required=False)
reader_chosen_reading_direction_users = CountDictField(required=False)
- reader_chosen_read_rtl_in_reverse_users = CountDictField(required=False)
- reader_chosen_finish_on_last_page_users = CountDictField(required=False)
reader_chosen_page_transition_users = CountDictField(required=False)
reader_chosen_cache_book_users = CountDictField(required=False)
diff --git a/codex/serializers/auth.py b/codex/serializers/auth.py
index 730c338e5..a003f2d82 100644
--- a/codex/serializers/auth.py
+++ b/codex/serializers/auth.py
@@ -128,8 +128,6 @@ class SessionReaderDefaultsSerializer(Serializer):
two_pages = BooleanField(read_only=True)
page_transition = BooleanField(read_only=True)
cache_book = BooleanField(read_only=True)
- finish_on_last_page = BooleanField(read_only=True)
- read_rtl_in_reverse = BooleanField(read_only=True)
class SessionDefaultsSerializer(Serializer):
diff --git a/codex/serializers/browser/settings.py b/codex/serializers/browser/settings.py
index d72daf850..bd585f2d6 100644
--- a/codex/serializers/browser/settings.py
+++ b/codex/serializers/browser/settings.py
@@ -30,7 +30,6 @@
BrowserRouteCollectionField,
)
from codex.serializers.mixins import JSONFieldSerializer
-from codex.serializers.route import SimpleRouteSerializer
from codex.serializers.settings import SettingsInputSerializer
# Sorts worth remembering per top collection. ``search_score`` only means
@@ -113,33 +112,16 @@ class BrowserFilterChoicesInputSerializer(JSONFieldSerializer):
search = CharField(allow_blank=True, required=False)
-class BrowserCoverInputSerializerBase(BrowserFilterChoicesInputSerializer):
- """Base Serializer for Cover and Settings."""
+class BrowserSettingsSerializerBase(BrowserFilterChoicesInputSerializer):
+ """Base Serializer for Browser & OPDS Settings."""
JSON_FIELDS = frozenset(BrowserFilterChoicesInputSerializer.JSON_FIELDS | {"show"})
- custom_covers = BooleanField(required=False)
- dynamic_covers = BooleanField(required=False)
order_by = ChoiceField(
choices=tuple(BROWSER_ORDER_BY_CHOICES.keys()), required=False
)
order_reverse = BooleanField(required=False)
show = BrowserSettingsShowCollectionFlagsSerializer(required=False)
-
-
-class BrowserCoverInputSerializer(BrowserCoverInputSerializerBase):
- """Browser Settings for the cover response."""
-
- JSON_FIELDS = frozenset(
- BrowserCoverInputSerializerBase.JSON_FIELDS | {"parent_route"}
- )
-
- parent_route = SimpleRouteSerializer(required=False)
-
-
-class BrowserSettingsSerializerBase(BrowserCoverInputSerializerBase):
- """Base Serializer for Browser & OPDS Settings."""
-
top_collection = BrowseCollectionField(required=False)
@override
diff --git a/codex/serializers/reader.py b/codex/serializers/reader.py
index 8e9df826d..b2487a4a7 100644
--- a/codex/serializers/reader.py
+++ b/codex/serializers/reader.py
@@ -30,8 +30,6 @@ class ReaderSettingsSerializer(Serializer):
reading_direction = ReadingDirectionField(
allow_blank=True, allow_null=True, required=False
)
- read_rtl_in_reverse = BooleanField(allow_null=True, required=False)
- finish_on_last_page = BooleanField(allow_null=True, required=False)
mtime = TimestampField(read_only=True)
page_transition = BooleanField(allow_null=True, required=False)
cache_book = BooleanField(allow_null=True, required=False)
diff --git a/codex/settings/db.py b/codex/settings/db.py
index 61ddeeb53..b9adb06e9 100644
--- a/codex/settings/db.py
+++ b/codex/settings/db.py
@@ -241,13 +241,6 @@ def _in_choices_or_factory(row: SettingsDefaults, key: str) -> Any:
"bookmark",
"table_columns",
)
-_READER_DEFAULTS_KEYS: tuple[str, ...] = (
- "fit_to",
- "reading_direction",
- "two_pages",
- "page_transition",
- "cache_book",
-)
def get_browser_defaults() -> dict[str, Any]:
@@ -270,17 +263,9 @@ def get_browser_defaults() -> dict[str, Any]:
def get_reader_defaults() -> dict[str, Any]:
- """
- Resolve the site default global reader settings.
-
- Keeps the two pinned ``READER_DEFAULTS`` keys seeded alongside the
- admin-configurable ones.
- """
+ """Resolve the site default global reader settings."""
row = _get_settings_defaults_or_factory()
- return {
- **READER_DEFAULTS,
- **{key: _in_choices_or_factory(row, key) for key in _READER_DEFAULTS_KEYS},
- }
+ return {key: _in_choices_or_factory(row, key) for key in READER_DEFAULTS}
def get_throttle_settings() -> ThrottleSettings | None:
diff --git a/codex/user_data/restore.py b/codex/user_data/restore.py
index afb3caec6..70f39870c 100644
--- a/codex/user_data/restore.py
+++ b/codex/user_data/restore.py
@@ -775,8 +775,6 @@ def _rename_table_columns(raw) -> dict:
)
_BROWSER_BOOL_COLUMNS: Final[tuple[str, ...]] = (
"order_reverse",
- "custom_covers",
- "dynamic_covers",
"twenty_four_hour_time",
"always_show_filename",
)
diff --git a/codex/user_data/schema.sql b/codex/user_data/schema.sql
index 7048aee35..52e00a1b7 100644
--- a/codex/user_data/schema.sql
+++ b/codex/user_data/schema.sql
@@ -88,8 +88,6 @@ CREATE TABLE IF NOT EXISTS settings_browser (
order_extra_keys TEXT NOT NULL DEFAULT '[]',
collection_order_memory TEXT NOT NULL DEFAULT '{}',
search TEXT NOT NULL DEFAULT '',
- custom_covers INTEGER NOT NULL DEFAULT 1,
- dynamic_covers INTEGER NOT NULL DEFAULT 1,
twenty_four_hour_time INTEGER NOT NULL DEFAULT 0,
always_show_filename INTEGER NOT NULL DEFAULT 0,
view_mode TEXT NOT NULL DEFAULT '',
diff --git a/codex/user_data/serializers.py b/codex/user_data/serializers.py
index 8256f8411..6815f78dd 100644
--- a/codex/user_data/serializers.py
+++ b/codex/user_data/serializers.py
@@ -207,8 +207,6 @@ def serialize_settings_browser(
browser.collection_order_memory, separators=(",", ":")
),
"search": browser.search,
- "custom_covers": int(browser.custom_covers),
- "dynamic_covers": int(browser.dynamic_covers),
"twenty_four_hour_time": int(browser.twenty_four_hour_time),
"always_show_filename": int(browser.always_show_filename),
"view_mode": browser.view_mode,
diff --git a/codex/views/browser/annotate/cover.py b/codex/views/browser/annotate/cover.py
index 653a71d1d..e16e5e3c6 100644
--- a/codex/views/browser/annotate/cover.py
+++ b/codex/views/browser/annotate/cover.py
@@ -1,33 +1,12 @@
"""Cover pk annotation for browser card querysets."""
-from typing import Final
-
from django.db.models import OuterRef, Q, Subquery
-from codex.models import Comic, Folder, Volume
+from codex.models import Comic, Folder
from codex.models.paths import CustomCover
-from codex.views.browser.annotate.card import _COLLECTION_BY, BrowserAnnotateCardView
+from codex.views.browser.annotate.card import BrowserAnnotateCardView
from codex.views.const import COLLECTION_RELATION, CUSTOM_COVER_COLLECTION_RELATION
-# PENDING SCHEMA REMOVAL
-#
-# Dynamic covers and custom covers are no longer user options.
-# They are pinned on here instead of read from ``self.params``, and their
-# toggles are gone from the browser settings drawer.
-#
-# What is deliberately still here: ``SettingsBrowser.dynamic_covers`` and
-# ``.custom_covers``, their serializer fields, and the query params. Every
-# user's stored preference is therefore untouched, so putting the toggles back
-# means reverting the code — no data migration, and nobody's setting is lost in
-# the meantime.
-#
-# When we commit to this, drop the two columns, their serializer and
-# ``BROWSER_DEFAULTS`` entries, the params the client sends, and the branches
-# below that can no longer be reached: the sort-name fuzzy match in
-# ``_cover_collection_q`` and the early return in ``_cover_custom_subqueries``.
-_DYNAMIC_COVERS: Final[bool] = True
-_CUSTOM_COVERS: Final[bool] = True
-
class BrowserAnnotateCoverView(BrowserAnnotateCardView):
"""
@@ -35,40 +14,23 @@ class BrowserAnnotateCoverView(BrowserAnnotateCardView):
Pre-computes the representative comic pk per card via a correlated
subquery, replacing the 72-request fan-out to ``CoverView`` with a
- single aggregation pass. Reproduces ``CoverView.get_collection_filter``
- semantics exactly: direct collection-fk match when ``dynamic_covers`` is
- on or the model is Volume/Folder; sort-name fuzzy match (correlated
- on the GROUP BY columns) otherwise. Dynamic covers are now pinned on, so
- the fuzzy branch is unreachable — see the note above.
+ single aggregation pass. The cover comic comes from a direct
+ collection-fk match; Folder cards match through the ``folders`` M2M so
+ descendant subfolders count. A custom cover, when one exists, overrides it.
"""
def _cover_collection_q(self, collection_model) -> Q:
"""Build the collection filter Q for a cover subquery (correlated via OuterRef)."""
+ # Folders are hierarchical: parent_folder is the direct FK, but the
+ # browse filter uses the ``folders`` M2M that includes every
+ # ancestor folder. A Folder card's cover must be pickable from any
+ # comic in a descendant subfolder, not just direct children —
+ # otherwise parent folders that contain only subfolders fall back
+ # to the card's own pk and serve the missing-cover placeholder.
+ if collection_model is Folder:
+ return Q(folders__pk=OuterRef("pk"))
collection_rel = COLLECTION_RELATION[self.model_collection]
- if _DYNAMIC_COVERS or collection_model in (Volume, Folder):
- # Folders are hierarchical: parent_folder is the direct FK, but the
- # browse filter uses the ``folders`` M2M that includes every
- # ancestor folder. A Folder card's cover must be pickable from any
- # comic in a descendant subfolder, not just direct children —
- # otherwise parent folders that contain only subfolders fall back
- # to the card's own pk and serve the missing-cover placeholder.
- if collection_model is Folder:
- return Q(folders__pk=OuterRef("pk"))
- return Q(**{f"{collection_rel}__pk": OuterRef("pk")})
-
- # Fuzzy match: correlate on the GROUP BY columns. These are the
- # same columns used by add_group_by, so ``ids`` (the JsonGroupArray
- # of merged collection pks) and the cover subquery pick the exact same
- # comic set — without recomputing sort_names in Python.
- collection_by_cols = _COLLECTION_BY.get(collection_model, ("sort_name",))
- correlation: dict = {
- f"{collection_rel}__{col}": OuterRef(col) for col in collection_by_cols
- }
- parent_route = self.params.get("parent_route", {})
- if parent_pks := parent_route.get("pks"):
- parent_rel = COLLECTION_RELATION[parent_route["collection"]]
- correlation[f"{parent_rel}__pk__in"] = parent_pks
- return Q(**correlation)
+ return Q(**{f"{collection_rel}__pk": OuterRef("pk")})
def _cover_filter_q(self, collection_model) -> Q:
"""Build the full filter Q for the cover comic subquery."""
@@ -121,8 +83,6 @@ def _cover_comic_ordered_qs(self, collection_model):
def _cover_custom_subqueries(self):
"""Return (pk, updated_at) subqueries for a CustomCover, or (None, None)."""
- if not _CUSTOM_COVERS:
- return None, None
# ``model_collection`` is the collection the cards being annotated belong to
# (the *child* of the URL collection). ``kwargs["collection"]`` is the URL
# collection itself, which is one level too high for the cover lookup —
diff --git a/frontend/src/components/admin/tabs/stats-tab.vue b/frontend/src/components/admin/tabs/stats-tab.vue
index 665b468f1..90473a2a1 100644
--- a/frontend/src/components/admin/tabs/stats-tab.vue
+++ b/frontend/src/components/admin/tabs/stats-tab.vue
@@ -35,9 +35,7 @@ const UNSET_LABELS = Object.freeze({
fitTo: "Unset (Fit to Width)",
readingDirection: "Unset (Left to Right)",
orderBy: "Unset (Name)",
- finishOnLastPage: "Unset (On)",
twoPages: "Unset (Off)",
- readRtlInReverse: "Unset (Off)",
pageTransition: "Unset (On)",
cacheBook: "Unset (Off)",
});
diff --git a/frontend/src/components/browser/drawer/browser-settings-covers.vue b/frontend/src/components/browser/drawer/browser-settings-covers.vue
deleted file mode 100644
index 8e5fb7468..000000000
--- a/frontend/src/components/browser/drawer/browser-settings-covers.vue
+++ /dev/null
@@ -1,78 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/frontend/src/components/reader/drawer/reader-settings-scope.vue b/frontend/src/components/reader/drawer/reader-settings-scope.vue
index c69b0fddf..d3f478306 100644
--- a/frontend/src/components/reader/drawer/reader-settings-scope.vue
+++ b/frontend/src/components/reader/drawer/reader-settings-scope.vue
@@ -67,14 +67,6 @@ import GLOBAL_DEFAULTS from "@/choices/reader-defaults.json";
import ReaderSettingsControls from "./reader-settings-controls.vue";
import SettingsIndicatorDot from "./settings-indicator-dot.vue";
-/*
- * PENDING SCHEMA REMOVAL
- *
- * ``readRtlInReverse`` and ``finishOnLastPage`` are gone from here because
- * they no longer have controls to override. Their columns and serializer
- * fields are deliberately kept -- see the notes in ``stores/reader.js``. Put
- * them back in this list if the checkboxes come back.
- */
const ATTRS = Object.freeze([
"fitTo",
"readingDirection",
diff --git a/frontend/src/stores/browser.js b/frontend/src/stores/browser.js
index 912110a06..3f5b1bda0 100644
--- a/frontend/src/stores/browser.js
+++ b/frontend/src/stores/browser.js
@@ -58,13 +58,6 @@ const SEARCH_ROOT_COLLECTIONS = Object.freeze(
);
const NON_BROWSE_COLLECTIONS = Object.freeze(new Set(["arcs", "folders"]));
const SEARCH_HIDE_TIMEOUT = 5000;
-const COVER_KEYS = Object.freeze(["customCovers", "dynamicCovers", "show"]);
-const DYNAMIC_COVER_KEYS = Object.freeze([
- "filters",
- "orderBy",
- "orderReverse",
- "q",
-]);
const FILTER_ONLY_KEYS = Object.freeze(["filters", "q"]);
const METADATA_LOAD_KEYS = Object.freeze(["filters", "q", "mtime"]);
@@ -276,8 +269,6 @@ export const useBrowserStore = defineStore("browser", {
},
settings: {
breadcrumbs: [],
- customCovers: BROWSER_DEFAULTS.customCovers,
- dynamicCovers: BROWSER_DEFAULTS.dynamicCovers,
filters: BROWSER_DEFAULTS.filters,
orderBy: BROWSER_DEFAULTS.orderBy,
orderReverse: BROWSER_DEFAULTS.orderReverse,
@@ -438,25 +429,6 @@ export const useBrowserStore = defineStore("browser", {
? toBrowseRoute({ name: "browser", params })
: { name: "home" };
},
- /*
- * PENDING SCHEMA REMOVAL
- *
- * Dynamic covers are pinned on, so the dynamic keys always ride along and
- * the ``parentRoute`` fallback the sort-name cover match needed is
- * unreachable. ``customCovers`` and ``dynamicCovers`` are still sent in
- * COVER_KEYS and still stored per user; the server ignores them. Drop them
- * from COVER_KEYS, from the state below and from the schema together.
- */
- coverSettings(state) {
- const { collection } = liveBrowseParams();
- if (collection == "comics") {
- return {};
- }
- return this._filterSettings(state, [
- ...COVER_KEYS,
- ...DYNAMIC_COVER_KEYS,
- ]);
- },
filterOnlySettings(state) {
return this._filterSettings(state, FILTER_ONLY_KEYS);
},
diff --git a/frontend/src/stores/reader.js b/frontend/src/stores/reader.js
index 75ba1d68c..d86be86e5 100644
--- a/frontend/src/stores/reader.js
+++ b/frontend/src/stores/reader.js
@@ -120,8 +120,6 @@ export const useReaderStore = defineStore("reader", {
fitTo: READER_DEFAULTS.fitTo,
twoPages: READER_DEFAULTS.twoPages,
readingDirection: READER_DEFAULTS.readingDirection,
- readRtlInReverse: READER_DEFAULTS.readRtlInReverse,
- finishOnLastPage: READER_DEFAULTS.finishOnLastPage,
pageTransition: READER_DEFAULTS.pageTransition,
cacheBook: READER_DEFAULTS.cacheBook,
},
@@ -243,21 +241,6 @@ export const useReaderStore = defineStore("reader", {
/*
* GETTER Algorithms
*/
- /*
- * PENDING SCHEMA REMOVAL
- *
- * "Read RTL Comics LTR" is no longer a user option, and it is pinned OFF
- * — its default — so a right-to-left book is read right to left. The
- * checkbox is gone from the reader settings drawer.
- * ``SettingsReader.read_rtl_in_reverse`` and its serializer field are
- * deliberately still there, so every user's stored preference survives
- * and putting the toggle back is a revert rather than a data migration.
- * Drop the column, its serializer and ``READER_DEFAULTS`` entries, and
- * this pass-through, when we commit to this.
- */
- setReadRTLInReverse(bookSettings) {
- return bookSettings;
- },
getBookSettings(book) {
if (!book) {
return {};
@@ -277,8 +260,7 @@ export const useReaderStore = defineStore("reader", {
* shape confusion.
*/
const resultSettings = {};
- let bookSettings = book?.settings || {};
- bookSettings = this.setReadRTLInReverse(bookSettings);
+ const bookSettings = book?.settings || {};
const allSettings = [
this.globalSettings,
this.intermediateSettings,
@@ -696,17 +678,6 @@ export const useReaderStore = defineStore("reader", {
};
page = Math.max(Math.min(book.maxPage, page), 0);
const updates = { page };
- /*
- * PENDING SCHEMA REMOVAL
- *
- * Finishing a book on its last page is no longer a user option; the
- * checkbox is gone from the reader settings drawer.
- * ``SettingsReader.finish_on_last_page`` and its serializer field are
- * deliberately still there, so every user's stored preference survives
- * and putting the toggle back is a revert rather than a data migration.
- * Drop the column, its serializer and ``READER_DEFAULTS`` entries when
- * we commit to this.
- */
if (page >= book.maxPage) {
updates["finished"] = true;
}
diff --git a/frontend/tests/unit/stats-tab.test.js b/frontend/tests/unit/stats-tab.test.js
index e95de1a41..cb07e0bb8 100644
--- a/frontend/tests/unit/stats-tab.test.js
+++ b/frontend/tests/unit/stats-tab.test.js
@@ -48,13 +48,10 @@ const STATS = {
topCollection: { publishers: 2 },
// camelCase, like the API renders it and like the choices maps are keyed.
orderBy: { sortName: 2 },
- dynamicCovers: { true: 2 },
- finishOnLastPage: { true: 1 },
fitTo: { W: 1 },
readingDirection: { ltr: 1 },
viewMode: { cover: 2, table: 1 },
tableCoverSize: { sm: 1 },
- customCovers: { true: 2 },
multiSortCount: 0,
},
collections: {
@@ -87,6 +84,7 @@ const STATS = {
browserOrderByUsers: { sortName: 1, "": 1 },
browserChosenOrderByUsers: { sortName: 1 },
readerGlobalFitToUsers: { W: 1, "": 1 },
+ readerGlobalTwoPagesUsers: { true: 1, "": 1 },
},
usage: {
bookmarkCount: 1,
diff --git a/tests/test_settings_defaults.py b/tests/test_settings_defaults.py
index a9908dc67..af6e6cb9b 100644
--- a/tests/test_settings_defaults.py
+++ b/tests/test_settings_defaults.py
@@ -93,13 +93,7 @@ def test_browser_field_defaults_match_the_settings_models(self) -> None:
assert _field_default(SettingsDefaults, "table_columns") == {}
def test_reader_field_defaults_match_reader_defaults(self) -> None:
- for key in (
- "fit_to",
- "reading_direction",
- "two_pages",
- "page_transition",
- "cache_book",
- ):
+ for key in READER_DEFAULTS:
assert _field_default(SettingsDefaults, key) == READER_DEFAULTS[key], key
def test_fresh_get_equals_factory(self) -> None:
diff --git a/tests/test_telemeter_privacy.py b/tests/test_telemeter_privacy.py
index c0b64f4ce..b7e1e8bb5 100644
--- a/tests/test_telemeter_privacy.py
+++ b/tests/test_telemeter_privacy.py
@@ -55,11 +55,8 @@
"stats.tagging.default_sources",
"stats.sessions.top_collection",
"stats.sessions.order_by",
- "stats.sessions.dynamic_covers",
"stats.sessions.view_mode",
"stats.sessions.table_cover_size",
- "stats.sessions.custom_covers",
- "stats.sessions.finish_on_last_page",
"stats.sessions.fit_to",
"stats.sessions.reading_direction",
# per_user draws on the same closed vocabularies as sessions, counted
@@ -74,20 +71,12 @@
"stats.per_user.browser_chosen_view_mode_users",
"stats.per_user.browser_table_cover_size_users",
"stats.per_user.browser_chosen_table_cover_size_users",
- "stats.per_user.browser_dynamic_covers_users",
- "stats.per_user.browser_chosen_dynamic_covers_users",
- "stats.per_user.browser_custom_covers_users",
- "stats.per_user.browser_chosen_custom_covers_users",
"stats.per_user.reader_global_fit_to_users",
"stats.per_user.reader_chosen_fit_to_users",
"stats.per_user.reader_global_two_pages_users",
"stats.per_user.reader_chosen_two_pages_users",
"stats.per_user.reader_global_reading_direction_users",
"stats.per_user.reader_chosen_reading_direction_users",
- "stats.per_user.reader_global_read_rtl_in_reverse_users",
- "stats.per_user.reader_chosen_read_rtl_in_reverse_users",
- "stats.per_user.reader_global_finish_on_last_page_users",
- "stats.per_user.reader_chosen_finish_on_last_page_users",
"stats.per_user.reader_global_page_transition_users",
"stats.per_user.reader_chosen_page_transition_users",
"stats.per_user.reader_global_cache_book_users",
diff --git a/tests/test_user_data_restore.py b/tests/test_user_data_restore.py
index 63286ba03..4953a24c7 100644
--- a/tests/test_user_data_restore.py
+++ b/tests/test_user_data_restore.py
@@ -465,6 +465,7 @@ def _browser_row(**overrides) -> dict:
"order_reverse": 1,
"order_extra_keys": "[]",
"search": "",
+ # Retired sidecar columns; restore must ignore them.
"custom_covers": 1,
"dynamic_covers": 1,
"twenty_four_hour_time": 0,