diff --git a/docs/superpowers/specs/2026-03-22-paging-design.md b/docs/superpowers/specs/2026-03-22-paging-design.md new file mode 100644 index 0000000..80e3ee6 --- /dev/null +++ b/docs/superpowers/specs/2026-03-22-paging-design.md @@ -0,0 +1,100 @@ +# Paging Feature Design + +## Overview + +Introduce offset-based pagination to the admin post list and the generated static site (both post listings and category listings). + +## Data Layer + +### New `sites` column + +Add `posts_per_page INTEGER NOT NULL DEFAULT 10` to the `sites` table. This setting controls the number of posts per page on the **generated static site only**. + +### New SQL queries + +- `CountPostsOfSite(siteID, showDeleted)` — returns total post count for the site +- `CountPostsOfCategory(categoryID)` — returns total published post count for a category + +### Model changes + +**`models.Site`** — add field: +```go +PostsPerPage int +``` + +**New shared type** (`models/paging.go`): +```go +type PageInfo struct { + CurrentPage int + TotalPages int + PostsPerPage int +} +``` + +Existing `db.PagingParams` and queries (`SelectPostsOfSite`, `SelectPostsOfCategory`) already support `LIMIT/OFFSET` and remain unchanged. + +## Admin Section + +### Post list pagination + +- **Page size: hardcoded at 25** (not tied to the `PostsPerPage` site setting) +- Handler (`handlers/posts.go` `Index()`) reads a `page` query parameter (default 1) +- Computes offset as `(page - 1) * 25` +- Fetches total post count via new `CountPosts()` service method to build `PageInfo` +- Passes `PageInfo` to template + +### Service changes + +- `ListPosts()` accepts paging params from the handler instead of hardcoding them +- New `CountPosts()` method that calls the count query + +### Template (`views/posts/index.html`) + +- Full numbered pagination with Previous/Next below the post list: `< 1 2 3 ... 10 >` +- Preserves existing query params (e.g. `?filter=deleted`) when paginating +- Both regular post list and trash view are paginated + +### Site settings form + +- Add "Posts per page" number input to `views/sitesettings/general.html` +- Add `PostsPerPage` field to `UpdateSiteSettingsParams` +- Server-side validation: minimum 1, maximum 100 + +## Generated Static Site + +### URL structure + +Post listing pages: +- `/posts/` — page 1 +- `/posts/page/2/` — page 2 +- `/posts/page/N/` — page N + +Category listing pages: +- `/categories//` — page 1 +- `/categories//page/2/` — page 2 +- `/categories//page/N/` — page N + +### Site root + +`/` (site root) shows the same content as `/posts/` (page 1 of all posts). + +### Builder changes (`providers/sitebuilder/builder.go`) + +- Instead of rendering one `posts_list.html` with all posts, generate multiple page files +- Uses `site.PostsPerPage` from the site setting to determine page size +- Same pattern for category pages + +### Publisher changes (`services/publisher/iter.go`) + +- Existing iterator fetches posts in batches of 50 internally — this stays as-is +- The builder chunks posts into pages of `PostsPerPage` size and renders each page as a separate HTML file + +### Template (`layouts/simplecss/templates/posts_list.html`) + +- Receives `PageInfo` plus the posts for that page +- Renders **Previous / Next** links only (no numbered pagination) +- Previous link hidden on page 1; Next link hidden on last page + +## Approach + +Offset-based pagination using the existing `db.PagingParams` infrastructure. Page number maps to offset: `offset = (page - 1) * postsPerPage`.