Adds « ‹ [1][2][3][4] › » navigation to the library. Page window slides to keep the current page in view. Prev/next/first/last controls are always rendered but disabled at their respective bounds. Also wires up karmaConfig in angular.json so FirefoxHeadless is used for tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
103 lines
4.1 KiB
Markdown
103 lines
4.1 KiB
Markdown
# Implementation Plan: Pagination Controls Redesign
|
||
|
||
**Branch**: `018-pagination-controls` | **Date**: 2026-05-10 | **Spec**: [spec.md](spec.md)
|
||
|
||
## Summary
|
||
|
||
Replace the existing "← Previous / Page X of Y / Next →" pagination bar in `LibraryComponent` with six controls: first-page («), previous-page (‹), up to four numbered page buttons, next-page (›), and last-page (»). All logic stays in the existing component — no new component is introduced (§2.6: no speculative abstraction, only one paginated view exists).
|
||
|
||
## Technical Context
|
||
|
||
**Language/Version**: TypeScript (strict mode)
|
||
**Primary Dependencies**: Angular (latest stable), Karma + Jasmine
|
||
**Storage**: N/A — no data layer changes
|
||
**Testing**: Angular TestBed unit tests (component spec)
|
||
**Target Platform**: Browser SPA
|
||
**Project Type**: Web application — UI only
|
||
**Performance Goals**: No measurable regression in render or navigation time
|
||
**Constraints**: ESLint + Prettier must pass (§7.3); all existing tests must continue to pass (§5.4)
|
||
**Scale/Scope**: Single component change; one paginated view in the app
|
||
|
||
## Constitution Check
|
||
|
||
| Principle | Status | Notes |
|
||
|-----------|--------|-------|
|
||
| §2.6 No speculative abstraction | ✅ PASS | Pagination logic stays inline in LibraryComponent; no new component introduced |
|
||
| §5.1 Tests alongside implementation | ✅ PASS | Spec tests for window algorithm, disabled states, and navigation covered in tasks |
|
||
| §5.2 Test pyramid | ✅ PASS | Unit tests via TestBed; no integration or E2E tests required for a template change |
|
||
| §5.4 Suite must pass before done | ✅ PASS | Gate enforced per task |
|
||
| §7.3 Lint/format enforced | ✅ PASS | ESLint + Prettier gate on all tasks |
|
||
| §8 Scope boundaries | ✅ PASS | No out-of-scope work touched |
|
||
|
||
No violations. No Complexity Tracking table needed.
|
||
|
||
## Project Structure
|
||
|
||
### Documentation (this feature)
|
||
|
||
```text
|
||
specs/018-pagination-controls/
|
||
├── plan.md ← this file
|
||
├── research.md
|
||
└── tasks.md (generated by /speckit-tasks)
|
||
```
|
||
|
||
### Source Code (changed files only)
|
||
|
||
```text
|
||
ui/src/app/library/
|
||
├── library.component.ts ← template, styles, class (page window getter + goToPage/firstPage/lastPage methods)
|
||
└── library.component.spec.ts ← new tests for window algorithm, disabled states, button navigation
|
||
```
|
||
|
||
No new files. No API changes. No data model changes.
|
||
|
||
## Page Window Algorithm
|
||
|
||
Given `currentPage` (1-based) and `totalPages`, compute the array of up to four page numbers to display:
|
||
|
||
```
|
||
start = max(1, currentPage - 1)
|
||
end = min(totalPages, start + 3)
|
||
start = max(1, end - 3) ← re-anchor if near the end
|
||
pages = [start .. end]
|
||
```
|
||
|
||
Examples:
|
||
- Page 1 of 20 → [1, 2, 3, 4]
|
||
- Page 7 of 20 → [6, 7, 8, 9]
|
||
- Page 19 of 20 → [17, 18, 19, 20]
|
||
- Page 2 of 3 → [1, 2, 3]
|
||
|
||
## New Controls Layout
|
||
|
||
```
|
||
« ‹ [1] [2] [3] [4] › »
|
||
```
|
||
|
||
- `«` disabled when `currentPage === 1`
|
||
- `‹` disabled when `currentPage === 1`
|
||
- Active page button has distinct active style
|
||
- `›` disabled when `currentPage === totalPages`
|
||
- `»` disabled when `currentPage === totalPages`
|
||
- Entire bar hidden when `totalPages <= 1` (existing behaviour retained)
|
||
|
||
## Methods to Add/Change
|
||
|
||
| Method | Change |
|
||
|--------|--------|
|
||
| `get pageWindow(): number[]` | New getter — returns array of up to 4 page numbers |
|
||
| `goToPage(page: number)` | New — navigates to arbitrary page number |
|
||
| `firstPage()` | New — navigates to page 1 |
|
||
| `lastPage()` | New — navigates to last page |
|
||
| `nextPage()` | Existing — no change needed |
|
||
| `prevPage()` | Existing — no change needed |
|
||
|
||
## Research
|
||
|
||
No unknowns. Tech stack is fixed (Angular/TypeScript). The windowing algorithm is a standard sliding-window with boundary clamping. No external research required.
|
||
|
||
**Decision**: Keep all logic in `LibraryComponent` (no child component).
|
||
**Rationale**: §2.6 prohibits speculative abstraction; only one paginated view exists in the app. Extracting a `PaginationComponent` would be justified only when a second use case appears.
|
||
**Alternatives considered**: Standalone `PaginationComponent` — rejected; no second consumer.
|