Skip to main content

max / makenotwork

server: dedupe pagination range into a shared helper (ultra-fuzz Run #1 --deep Phase 4) feed, discover, and landing each carried a byte-identical build_pagination_range; discover had its own private copy plus the tests. Consolidate into routes::pages::public::pagination so the sliding-window logic has one home and can't drift between the three public list pages. Tests move with it. (The report's two proposed composite indexes — creator_subscriptions and fan_plus_subscriptions on (user_id, status) — are redundant: both tables already have UNIQUE(user_id), so those queries are single-row point lookups, not status-filtered scans. Not added.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-06-22 22:37 UTC
Commit: d83d18c9bfb3139607c5943a8a0539c9fa81b7e7
Parent: 993520f
5 files changed, +61 insertions, -65 deletions
@@ -55,18 +55,6 @@ pub struct DiscoverQuery {
55 55 pub has_source: Option<String>,
56 56 }
57 57
58 - /// Build a sliding window of page numbers for pagination controls.
59 - fn build_pagination_range(current_page: u32, total_pages: u32) -> Vec<u32> {
60 - if total_pages <= constants::PAGINATION_WINDOW_SIZE {
61 - (1..=total_pages).collect()
62 - } else {
63 - let start = current_page.saturating_sub(2).max(1);
64 - let end = (start + 4).min(total_pages);
65 - let start = end.saturating_sub(4).max(1);
66 - (start..=end).collect()
67 - }
68 - }
69 -
70 58 /// Shared result data for both the full discover page and the HTMX partial.
71 59 struct DiscoverData {
72 60 items: Vec<DiscoverItem>,
@@ -153,7 +141,7 @@ async fn fetch_discover_data(pool: &PgPool, query: &DiscoverQuery) -> Result<Dis
153 141 };
154 142
155 143 let total_pages = ((total_count as f64) / (limit as f64)).ceil() as u32;
156 - let pagination_range = build_pagination_range(page, total_pages);
144 + let pagination_range = super::pagination::build_pagination_range(page, total_pages);
157 145 let result_count = if mode == "projects" {
158 146 projects.len() as u32
159 147 } else {
@@ -184,44 +172,6 @@ async fn fetch_discover_data(pool: &PgPool, query: &DiscoverQuery) -> Result<Dis
184 172 })
185 173 }
186 174
187 - #[cfg(test)]
188 - mod tests {
189 - use super::*;
190 -
191 - #[test]
192 - fn pagination_small_total() {
193 - assert_eq!(build_pagination_range(1, 3), vec![1, 2, 3]);
194 - assert_eq!(build_pagination_range(2, 5), vec![1, 2, 3, 4, 5]);
195 - }
196 -
197 - #[test]
198 - fn pagination_large_at_start() {
199 - assert_eq!(build_pagination_range(1, 20), vec![1, 2, 3, 4, 5]);
200 - assert_eq!(build_pagination_range(2, 20), vec![1, 2, 3, 4, 5]);
201 - }
202 -
203 - #[test]
204 - fn pagination_large_at_middle() {
205 - assert_eq!(build_pagination_range(10, 20), vec![8, 9, 10, 11, 12]);
206 - }
207 -
208 - #[test]
209 - fn pagination_large_at_end() {
210 - assert_eq!(build_pagination_range(20, 20), vec![16, 17, 18, 19, 20]);
211 - assert_eq!(build_pagination_range(19, 20), vec![16, 17, 18, 19, 20]);
212 - }
213 -
214 - #[test]
215 - fn pagination_zero_pages() {
216 - assert_eq!(build_pagination_range(1, 0), Vec::<u32>::new());
217 - }
218 -
219 - #[test]
220 - fn pagination_single_page() {
221 - assert_eq!(build_pagination_range(1, 1), vec![1]);
222 - }
223 - }
224 -
225 175 /// Query parameters for the tag tree browser.
226 176 #[derive(Debug, Deserialize)]
227 177 pub struct TagTreeQuery {
@@ -22,18 +22,6 @@ pub struct FeedQuery {
22 22 pub page: Option<u32>,
23 23 }
24 24
25 - /// Build a sliding window of page numbers for pagination controls.
26 - pub(super) fn build_pagination_range(current_page: u32, total_pages: u32) -> Vec<u32> {
27 - if total_pages <= constants::PAGINATION_WINDOW_SIZE {
28 - (1..=total_pages).collect()
29 - } else {
30 - let start = current_page.saturating_sub(2).max(1);
31 - let end = (start + 4).min(total_pages);
32 - let start = end.saturating_sub(4).max(1);
33 - (start..=end).collect()
34 - }
35 - }
36 -
37 25 /// GET /feed: paginated feed of items from followed users, projects, and tags.
38 26 #[tracing::instrument(skip_all, name = "feed::feed_page")]
39 27 pub(super) async fn feed_page(
@@ -74,7 +62,7 @@ pub(super) async fn feed_page(
74 62 .saturating_add(constants::FEED_PAGE_SIZE as i64)
75 63 .min(total_items as i64)
76 64 .clamp(0, u32::MAX as i64) as u32;
77 - let pagination_range = build_pagination_range(page, total_pages);
65 + let pagination_range = super::pagination::build_pagination_range(page, total_pages);
78 66
79 67 Ok(FeedTemplate {
80 68 csrf_token,
@@ -253,7 +253,7 @@ pub(super) async fn library_tab_feed(
253 253 .saturating_add(constants::FEED_PAGE_SIZE as i64)
254 254 .min(total_items as i64)
255 255 .clamp(0, u32::MAX as i64) as u32;
256 - let pagination_range = super::feed::build_pagination_range(page, total_pages);
256 + let pagination_range = super::pagination::build_pagination_range(page, total_pages);
257 257
258 258 Ok(LibraryFeedTabTemplate {
259 259 items,
@@ -7,6 +7,7 @@ mod feed;
7 7 mod health;
8 8 pub(crate) mod join_wizard;
9 9 pub(crate) mod landing;
10 + mod pagination;
10 11 mod sitemap;
11 12 mod two_factor;
12 13
@@ -0,0 +1,57 @@
1 + //! Shared pagination helpers for the public page handlers.
2 +
3 + use crate::constants;
4 +
5 + /// Build a sliding window of page numbers for pagination controls.
6 + ///
7 + /// One definition for every public list page (feed, discover, landing) — they
8 + /// previously each carried a byte-identical copy, so a tweak to the window
9 + /// behavior had to be made in lockstep or silently drift.
10 + pub(super) fn build_pagination_range(current_page: u32, total_pages: u32) -> Vec<u32> {
11 + if total_pages <= constants::PAGINATION_WINDOW_SIZE {
12 + (1..=total_pages).collect()
13 + } else {
14 + let start = current_page.saturating_sub(2).max(1);
15 + let end = (start + 4).min(total_pages);
16 + let start = end.saturating_sub(4).max(1);
17 + (start..=end).collect()
18 + }
19 + }
20 +
21 + #[cfg(test)]
22 + mod tests {
23 + use super::*;
24 +
25 + #[test]
26 + fn pagination_small_total() {
27 + assert_eq!(build_pagination_range(1, 3), vec![1, 2, 3]);
28 + assert_eq!(build_pagination_range(2, 5), vec![1, 2, 3, 4, 5]);
29 + }
30 +
31 + #[test]
32 + fn pagination_large_at_start() {
33 + assert_eq!(build_pagination_range(1, 20), vec![1, 2, 3, 4, 5]);
34 + assert_eq!(build_pagination_range(2, 20), vec![1, 2, 3, 4, 5]);
35 + }
36 +
37 + #[test]
38 + fn pagination_large_at_middle() {
39 + assert_eq!(build_pagination_range(10, 20), vec![8, 9, 10, 11, 12]);
40 + }
41 +
42 + #[test]
43 + fn pagination_large_at_end() {
44 + assert_eq!(build_pagination_range(20, 20), vec![16, 17, 18, 19, 20]);
45 + assert_eq!(build_pagination_range(19, 20), vec![16, 17, 18, 19, 20]);
46 + }
47 +
48 + #[test]
49 + fn pagination_zero_pages() {
50 + assert_eq!(build_pagination_range(1, 0), Vec::<u32>::new());
51 + }
52 +
53 + #[test]
54 + fn pagination_single_page() {
55 + assert_eq!(build_pagination_range(1, 1), vec![1]);
56 + }
57 + }