# Implementation Plan: Library Pagination UI **Branch**: `015-library-pagination` | **Date**: 2026-05-09 | **Spec**: [spec.md](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) ```text 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) ```text 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=2` added 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`, read `page` from `snapshot.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)`.