Fix following past 48 entries and make the e621 page size configurable

Follow lists were paginated at the API default of 48, but the SPA treats
them as complete sets: the tag/pool toggles read their state from page one
(so the 49th follow looked unfollowed and its spinner waited for a page that
could never contain it) and the Followed page rendered only 48 cards while
showing that as the count. Both follow endpoints are now unpaginated — they
are per-user sets and still restricted to the caller's rows — and the three
consumers take plain arrays.

Post visibility: the old J621-Django online view fetched limit=320 (e621's
maximum) while ours hard-coded 48, and fetchPostsByIds capped id batches at
100. The Online browser now has a 'Posts per page' setting (48/100/200/320)
in its sidebar, mirrored in Account -> Browsing preferences, stored per user
as e621_per_page and also used for pool loading; the id-batch cap is raised
to 320.

Tests: follow list shape/isolation (4) and preference validation/merge (3)
added; the full backend suite is 46 green. Live-checked the array response
shape and the preference bounds (200 accepted, 500 rejected).
This commit is contained in:
2026-09-18 19:06:18 -05:00
parent d16a77907a
commit 1adb761c8d
16 changed files with 237 additions and 25 deletions
@@ -13,7 +13,6 @@ import type {
FollowedPool,
FollowedTag,
FollowFeed,
Paginated,
} from "@/lib/types";
const ratingPill: Record<string, string> = {
@@ -207,11 +206,12 @@ export default function FollowedPage() {
const tagsQuery = useQuery({
queryKey: ["follows-tags"],
queryFn: () => api<Paginated<FollowedTag>>("/api/follows/tags/"),
// Unpaginated: this is the complete follow list, shown as cards below.
queryFn: () => api<FollowedTag[]>("/api/follows/tags/"),
});
const poolsQuery = useQuery({
queryKey: ["follows-pools"],
queryFn: () => api<Paginated<FollowedPool>>("/api/follows/pools/"),
queryFn: () => api<FollowedPool[]>("/api/follows/pools/"),
});
const feedQuery = useQuery({
@@ -269,8 +269,8 @@ export default function FollowedPage() {
);
}
const tags = tagsQuery.data?.results ?? [];
const pools = poolsQuery.data?.results ?? [];
const tags = tagsQuery.data ?? [];
const pools = poolsQuery.data ?? [];
const feed = feedQuery.data;
return (