# 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)`.
