Page size changes from 50 to 24. Library now shows discrete page navigation with a "Page N of M" indicator, total image count, and URL state (?page=N) so pages are bookmarkable and the browser Back button works. Tag filter resets to page 1. Out-of-range page params are clamped silently. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.1 KiB
Implementation Plan: Library Pagination UI
Branch: 015-library-pagination | Date: 2026-05-09 | Spec: spec.md
Input: Feature specification from specs/015-library-pagination/spec.md
Summary
Replace the current "Load more" append-on-scroll pattern in the library with discrete page navigation (Previous/Next buttons, page indicator, total count). Page state is persisted to the URL query string for bookmarkability. No API or backend changes required — the API already supports limit and offset parameters.
Technical Context
Language/Version: TypeScript (strict), Angular latest stable
Primary Dependencies: Angular Router (query params for URL state), Angular HttpClient (existing)
Storage: N/A — UI-only change
Testing: Angular TestBed / Jasmine (existing test suite)
Target Platform: Browser SPA
Project Type: UI feature within existing Angular standalone component
Performance Goals: Page load of 24 images replaces 50-image Load More; no regression
Constraints: Must preserve existing tag filter query param (?tags=) when updating page param; must not break existing spec tests
Scale/Scope: Single component change (library.component.ts) + its spec file
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
| Principle | Status | Notes |
|---|---|---|
| §2.1 Strict separation | ✅ PASS | UI communicates with API only via HTTP; no storage or DB knowledge in component |
| §2.6 No speculative abstraction | ✅ PASS | No new abstractions introduced; pagination is a concrete change to one component |
| §3.2 OpenAPI as contract | ✅ PASS | Uses existing GET /api/v1/images?limit=&offset= contract; no new endpoints |
| §3.4 Pagination | ✅ PASS | This feature is the UI surface for the API pagination already in place |
| §5.1 Tests alongside implementation | ✅ REQUIRED | Component spec must be updated alongside each changed behaviour |
| §5.4 Test gate | ✅ REQUIRED | UI tests must pass; make test-unit passes before task marked done |
| §6 Tech stack | ✅ PASS | Angular + TypeScript strict — no new dependencies needed |
| §7.3 Linting | ✅ REQUIRED | ESLint + Prettier enforced; no lint regressions |
| §8 Scope boundaries | ✅ PASS | Pagination is explicitly required (§3.4); no out-of-scope additions |
Post-Phase-1 re-check: No contracts or data model introduced; no new violations.
Project Structure
Documentation (this feature)
specs/015-library-pagination/
├── plan.md ← this file
├── research.md ← Phase 0 output
├── quickstart.md ← Phase 1 output
├── contracts/
│ └── pagination-query.md ← Phase 1 output
└── tasks.md ← Phase 2 output (/speckit-tasks)
Source Code (changes only)
ui/src/app/library/
├── library.component.ts ← primary change
└── library.component.spec.ts ← tests updated alongside
No other files change. No new files added to source tree.
Key Design Decisions
Page size: 24
Fixed at 24 images per page (spec FR-011). Fits common grid widths (2/3/4/6 columns), is a meaningful reduction from the current silent 50-image cap, and divides cleanly. Not user-configurable.
Replace, don't append
Current loadMore() appends items to the array. The new goToPage(n) replaces this.images entirely. The offset field becomes derived from page: offset = (page - 1) * limit.
URL state via Angular Router query params
?page=2added alongside existing?tags=cat,funny- Use
queryParamsHandling: 'merge'when updating page to preserve tag params - Use
queryParamsHandling: 'merge'when updating tags to preserve page reset (page always resets to 1 on filter change, so page param is removed or set to 1) - On
ngOnInit, readpagefromsnapshot.queryParamMap; clamp to valid range
Out-of-page-range handling
If URL ?page=99 is requested but only 3 pages exist: silently load page 1. No error state.
Pagination controls visibility
Only shown when totalPages > 1. Total pages = Math.ceil(total / limit).