Feat: Replace pagination bar with numbered page buttons and chevron controls

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>
This commit is contained in:
2026-05-10 18:11:18 +00:00
parent 40ceecda76
commit 0ad82e60ac
9 changed files with 583 additions and 41 deletions

View File

@@ -0,0 +1,102 @@
# 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.