Conversation
…ages. move ci contaiiner to compose
…ender so the menu isn't lost in the center of the screen
…justing template settings in debug mode
commit c76660006840abb36aa37d7355d5c7e242babebf Merge: b2b5a011a be94a0a Author: AJ Slater <aj@slater.net> Date: Sun Apr 5 15:49:11 2026 -0700 Merge branch 'develop' into select-multiple commit b2b5a011ab207a7307505b092877f789e1a66ab3 Author: AJ Slater <aj@slater.net> Date: Sat Apr 4 22:15:54 2026 -0700 fix card menu hover highlighting commit 5a98fe23d89be5e11fa46ada927100ccd3e08cda Author: AJ Slater <aj@slater.net> Date: Sat Apr 4 22:13:37 2026 -0700 new design for select many mode. no settings drawer involvement. reconfigure cards commit 82c612e3f77eec90b02465174b49fc8552871686 Author: AJ Slater <aj@slater.net> Date: Sat Apr 4 01:50:11 2026 -0700 add github config to source include. remove dockerhub config commit b02bd450f4598300d78433a2d749741872d14894 Author: AJ Slater <aj@slater.net> Date: Sat Apr 4 01:42:32 2026 -0700 update deps commit d7bbc875e7936d808259c9ba726127fcc3af905e Author: AJ Slater <aj@slater.net> Date: Fri Apr 3 23:56:12 2026 -0700 select many toolbar commit 246d5ecda59aaabb83e41db3ad32ec552804b9ae Author: AJ Slater <aj@slater.net> Date: Fri Apr 3 22:23:24 2026 -0700 select many feature first attempt
iOS Panels (and other Basic-Auth OPDS clients) intermittently hit
sqlite3.IntegrityError: FOREIGN KEY constraint failed when settings
or bookmarks were saved. Two interacting bugs caused it:
- Janitor cleanup_sessions used `if not session.get_decoded():` to
detect "corrupt" sessions. get_decoded() returns {} for both real
decode failures and legitimate anonymous sessions with no stored
data — exactly what Basic-Auth OPDS clients produce. The nightly
task was wiping valid session rows. Replaced with a direct
signing.loads() call so only genuine signature/decode failures are
flagged.
- _ensure_session_key returned the cookie's session_key without
verifying the row still exists. With cached_db the session loads
from cache without rechecking, so a stale cookie key would slip
through and cause an FK violation when used as SettingsBrowser /
SettingsReader.session_id. Now we verify existence and flush+save
to cycle the key when the row is gone.
Either fix alone closes the user-visible error; both together also
stop the underlying churn that created the bad state.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
The same stale-session_key FK-violation pattern existed in two more places that also write rows whose session FK can be stale: - BookmarkAuthMixin.get_bookmark_auth_filter — feeds session_id into Bookmark.objects.bulk_create / bulk_update. - ReaderSettingsBaseView._get_bookmark_auth_filter — feeds session_id into SettingsReader.objects.create. Both used the old `if not session.session_key: save()` pattern that trusts the cookie. Hoist the validated _ensure_session_key helper from SettingsBaseView up to AuthMixin so every auth-aware view shares one implementation, and switch both call sites to it. BookmarkAuthMixin now extends AuthMixin to inherit the helper. BookmarkFilterMixin is unchanged — it's read-only (filter Q only) and a missing session correctly returns no rows. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…611) The /admin/stats endpoint's user_registered_count and auth_group_count fields have been silently returning 0 since at least Sep 2024. The Stats tab in the admin UI shows 0 registered users even on installs with multiple accounts. Root cause: _add_config tried to rename the per-model count keys produced by _get_model_counts: config["user_registered_count"] = config.pop("users_count", 0) config["auth_group_count"] = config.pop("groups_count", 0) But _get_model_counts builds keys via ``snakecase(model.__name__) + "_count"``. For Django's ``django.contrib.auth.models.User`` / ``Group`` that produces ``user_count`` and ``group_count`` (singular). The pop()s with the plural names never matched, so the default ``0`` won every time — and the actual ``user_count`` / ``group_count`` keys were left orphaned in the dict, then dropped by the StatsConfigSerializer which only declares ``user_registered_count`` / ``auth_group_count``. Fix: pop the right source keys. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* Reader page: fix load-progress spinner that never appeared
Two stacked bugs in the same setTimeout:
1. Non-arrow callback lost ``this``. The function ran with the
timer's context, not the component's, so ``this.loaded`` and
the write below were both no-ops.
2. The write targeted ``this.loading``, which has never been a
data field on this component. The template binds the spinner
to ``showProgress`` (line 15: ``v-if="showProgress && !loaded"``).
So even if the arrow had been there from the start, the spinner
still wouldn't have rendered — both bugs had to land at once.
Net: ``LoadingPage`` has been dead code for slow image loads.
Switch to an arrow function and write ``showProgress`` instead
of ``loading``. Stash the timer ID so ``beforeUnmount`` can
clear it; a fast page swap mid-delay would otherwise fire the
write on a torn-down component.
Implements B1 of tasks/frontend-perf/01-correctness-bugs.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Reader store: fix arc-mtime fallback that itself 500'd
``loadMtimes`` builds an arcs list of ``{ group, pks }`` from
``this.arcs``; if the dict is empty the function previously
fell back to ``arcs.push({ r: "0" })``. The comment noted that
"No arcs is a 500 from the mtime api" — the fallback was added
to dodge that 500 — but the wrong-shape fallback also produced
a 500 because the API expects ``group``/``pks`` keys, not ``r``.
Use the canonical shape so the fallback actually works.
Implements B2 of tasks/frontend-perf/01-correctness-bugs.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* iOS PWA download: pass the object URL to revokeObjectURL, not the Blob
``URL.createObjectURL(blob)`` returns a ``blob:...`` URL string;
``URL.revokeObjectURL`` must receive that same string to free the
mapping. The previous code passed ``response.data`` (the Blob
itself), which silently no-op'd and leaked one object URL per
download.
On iOS PWAs this matters more than elsewhere because the leak
accumulates across the user's session and can't be reclaimed
short of reloading the app.
Implements B6 of tasks/frontend-perf/01-correctness-bugs.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Browser API: stop mutating caller's settings in getGroupDownloadURL
``getGroupDownloadURL`` did ``delete settings.show`` on the
caller's object before building the URL. Side-effect: any caller
that re-used the settings dict after the download-URL build saw
its ``show`` key silently vanish. This was probably fine when
the function was first written but it's a footgun now that
settings flow through a Pinia store.
Destructure-and-spread to drop ``show`` without touching the
input.
Implements B8 of tasks/frontend-perf/01-correctness-bugs.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Auth store: make logout awaitable + clear state unconditionally
Two changes to the logout action:
1. ``async`` so callers can ``await``. The current call site
(``auth-menu.vue``) fires and forgets, but a future UX pass
that wants to disable the button while logout is in flight
needs the promise.
2. Clear ``this.user`` in ``finally`` rather than only on
success. The user clicked "log out" — UI should reflect the
logged-out state immediately, regardless of whether the
server-side logout endpoint succeeded. Server-side cookies
that survive the network failure will get cleaned up by the
next 401.
Implements B7 of tasks/frontend-perf/01-correctness-bugs.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Browser filter menu: stop double-rendering each filter row
``<v-list>`` was passed both ``:items="vuetifyItems"`` AND a
default-slot ``v-for`` over the same list. Vuetify renders the
items prop into ``v-list-item`` children directly, so every row
was being built twice — once by the prop, once by the manual
``v-for``. Visible to users on filter menus with large choice
lists (genres, characters, etc.); each row appeared duplicated
and the DOM cost doubled.
Drop the prop. Keep the ``v-for`` because it carries the custom
``#append`` slot for ``metronName`` rendering. ``:model-value`` /
``@update:selected`` still drive selection state via each list-
item's ``:value`` prop.
Implements B10 of tasks/frontend-perf/01-correctness-bugs.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Admin job-tab: remove API-fetching click handler from expanded panel
The expanded status panel had ``@click="loadAllStatuses"`` on its
container div. Any click inside the panel — including clicks on
child elements that bubbled — refetched the entire status map.
Probably copy-pasted as a "refresh on click" gesture, but it
fired far too often: a user inspecting a long status list would
trigger N API calls just from glancing around.
The status data is pushed through the websocket already
(socket.js fans librarian notifications into the admin store),
so the panel is up-to-date without a manual refresh.
Implements B11 of tasks/frontend-perf/01-correctness-bugs.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Reader store: handle bookmark-write errors instead of silently rejecting
``setRoutesAndBookmarkPage`` awaited ``_setBookmarkPage`` but
didn't catch its errors. On a network blip the promise rejected,
the bookmark didn't persist, and the failure became an unhandled
rejection in the browser console — not visible to the user, not
retried, just lost.
Wrap in try/catch. The local page state stays where it is (the
user is reading forward; the bookmark catches up on the next
write), but the failure is logged so debugging surfaces. A
proper user-visible toast + retry path is broader UX work
tracked in the plan.
Implements B3 of tasks/frontend-perf/01-correctness-bugs.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Metadata dialog: clear progress timer on unmount
``updateProgress`` chains itself via ``setTimeout`` until the
metadata loads or progress reaches 100. The timer ID was never
stashed, so closing the dialog mid-animation left the chain
running — each tick fired on a torn-down component, writing
``this.progress`` and re-scheduling against now-null refs.
Stash the timer ID and clear it in ``beforeUnmount`` so the
chain stops cleanly when the dialog goes away.
Implements B12 of tasks/frontend-perf/01-correctness-bugs.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Reader pager: key dynamic component on identity to force remount
``<component :is="...">`` without ``:key`` lets Vue reuse the
existing instance across an ``is`` change when the components
share enough surface (props, name). For the reader's
vertical/horizontal pager swap that's wrong: scroll listeners
attached by the previous mode persist, the new mode's
``mounted`` runs against stale internal state, and any
abort/teardown logic in ``beforeUnmount`` never fires.
Add ``:key="component.name"`` so the swap is a true unmount +
remount — old listeners go away, new mode starts clean.
Implements B13 of tasks/frontend-perf/01-correctness-bugs.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Metadata dialog: lint cleanup for the B12 fixup
Vue option-order rule: ``beforeUnmount`` belongs above
``methods``. Block-comment style required for the multi-line
explanation in ``updateProgress``. Both surfaced when running
eslint on the prior commit; pure cleanup, no behavior change.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…n 401 (#652) Panels and other strict OPDS clients require the WWW-Authenticate header (per RFC 7235) to trigger the auth prompt, and the spec calls for application/opds-authentication+json on the auth document. The exception handler was already converting 403 to 401 with the auth doc body, but was bypassing DRF's natural 401 response and dropping both the header and the proper content type, which Panels read as a forbidden state. Also fix a stray `from re import DEBUG` in the auth view that always forced the absolute-URL path. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
@beville.