Files
agatha 0ad82e60ac 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>
2026-05-10 18:11:18 +00:00

103 lines
4.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.