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>
4.1 KiB
Implementation Plan: Pagination Controls Redesign
Branch: 018-pagination-controls | Date: 2026-05-10 | Spec: 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)
specs/018-pagination-controls/
├── plan.md ← this file
├── research.md
└── tasks.md (generated by /speckit-tasks)
Source Code (changed files only)
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 whencurrentPage === 1‹disabled whencurrentPage === 1- Active page button has distinct active style
›disabled whencurrentPage === totalPages»disabled whencurrentPage === 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.