Page size changes from 50 to 24. Library now shows discrete page navigation with a "Page N of M" indicator, total image count, and URL state (?page=N) so pages are bookmarkable and the browser Back button works. Tag filter resets to page 1. Out-of-range page params are clamped silently. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.0 KiB
Tasks: Library Pagination UI
Input: Design documents from specs/015-library-pagination/
Prerequisites: plan.md ✅, spec.md ✅, research.md ✅, contracts/pagination-query.md ✅, quickstart.md ✅
Tests: Tests accompany each implementation task per §5.1. All changes are in ui/src/app/library/library.component.ts and its spec file.
Organization: No setup or foundational phase needed — the Angular project and library component already exist. Phase 1 implements US1 (page navigation controls). Phase 2 adds US2 (URL state). Polish runs lint and manual verification.
Format: [ID] [P?] [Story] Description
- [P]: Can run in parallel (different files, no dependencies)
- [Story]: Which user story this task belongs to
Phase 1: User Story 1 — Previous/Next Page Navigation (Priority: P1) 🎯 MVP
Goal: Replace the "Load more" append pattern with discrete Previous/Next page navigation, a "Page N of M" indicator, and a total image count. Page size changes from 50 to 24.
Independent Test: With at least 25 images in the library, open /. Confirm 24 images are shown, a "Page 1 of N" indicator is visible, "Previous" is absent, and "Next" is present. Click "Next" — confirm the grid is replaced (not appended) with the next 24 images and the indicator updates. Click "Previous" — confirm the first page returns. Apply a tag filter — confirm the page resets to 1.
-
T001 [US1] Write tests in
ui/src/app/library/library.component.spec.tscovering: (1) page indicator text "Page 1 of N" renders when totalPages > 1; (2) total count text renders (e.g. "143 images"); (3) "Next" button present when not on last page; (4) "Previous" button absent on first page; (5) "Previous" present and "Next" absent on last page; (6) no pagination controls rendered when all images fit on one page (total ≤ 24); (7) clicking "Next" callsimageService.listwith offset=24; (8) clicking "Previous" from page 2 callsimageService.listwith offset=0; (9) applying a filter resets to page 1 (offset=0). Runng testand confirm the new tests FAIL (implementation pending). -
T002 [US1] Update
ui/src/app/library/library.component.ts: (a) changeprivate readonly limit = 50toprivate readonly limit = 24; (b) removehasMoreproperty andloadMore()method; (c) add propertiescurrentPage = 1,totalPages = 1,total = 0; (d) rename/replaceload()to callimageService.list(this.activeFilters, this.limit, (this.currentPage - 1) * this.limit)and on success setthis.images = res.items(replace, not append),this.total = res.total,this.totalPages = Math.ceil(res.total / this.limit), clampcurrentPagetoMath.max(1, Math.min(this.currentPage, this.totalPages)); (e) addnextPage()that incrementscurrentPageand callsload(); (f) addprevPage()that decrementscurrentPageand callsload(); (g) inapplyFilter(), resetthis.currentPage = 1before callingload(); (h) replace the<button class="load-more">element in the template with a pagination bar: a "Previous" button bound to(click)="prevPage()"disabled/hidden whencurrentPage === 1, a "Page {{ currentPage }} of {{ totalPages }}" span, a "Next" button bound to(click)="nextPage()"disabled/hidden whencurrentPage === totalPages, and place a total count element showing "{{ total }} images" outside the pagination bar and outside the*ngIf="totalPages > 1"guard so it always renders when images exist (FR-003, SC-002); wrap only the Previous button, page indicator span, and Next button inside*ngIf="totalPages > 1". Runng testand confirm T001 tests pass.
Checkpoint: US1 complete. Library shows paginated results with Previous/Next controls and page indicator.
Phase 2: User Story 2 — Page State in URL (Priority: P2)
Goal: Persist the current page number in the URL query string (?page=N) so that the URL is bookmarkable and the browser Back button works.
Independent Test: Navigate to page 3. Copy the URL (should contain ?page=3). Open in a new tab — confirm page 3 loads directly. Press browser Back — confirm page 2 is shown. Navigate to /?page=9999 — confirm page 1 loads without error.
-
T003 [US2] Add tests to
ui/src/app/library/library.component.spec.tscovering: (1) on init with?page=2in queryParamMap,currentPageis set to 2 andlistis called withoffset=24; (2) on init with?page=9999and total of 48 images,currentPageis clamped to page 1; (3)nextPage()callsrouter.navigatewithqueryParams: { page: 2 }andqueryParamsHandling: 'merge'; (4)applyFilter()callsrouter.navigatewithqueryParams: { page: 1 }andqueryParamsHandling: 'merge'. Runng testand confirm new tests FAIL. -
T004 [US2] Update
ui/src/app/library/library.component.ts: (a) inngOnInit, after reading thetagsparam, readconst pageParam = this.route.snapshot.queryParamMap.get('page')and setthis.currentPage = pageParam ? Math.max(1, parseInt(pageParam, 10)) : 1(out-of-range clamping happens after load when totalPages is known); (b) updatenextPage()andprevPage()to callthis.router.navigate([], { queryParams: { page: this.currentPage }, queryParamsHandling: 'merge' })after updatingcurrentPage; (c) updateapplyFilter()to callthis.router.navigate([], { queryParams: { page: 1, tags: tags.join(',') || null }, queryParamsHandling: 'merge' })when resetting to page 1 (passnullfor tags to remove param when empty); (d) after load resolves andtotalPagesis known, clampcurrentPagetoMath.min(this.currentPage, Math.max(1, this.totalPages))and if clamped, call navigate to correct the URL. Runng testand confirm T003 tests pass.
Checkpoint: US2 complete. Page state persists in URL; Back button and direct links work.
Phase 3: Polish & Cross-Cutting Concerns
- T005 Run
ng lintonui/src/app/library/library.component.tsand fix any issues; confirmng testpasses with all existing and new tests green; manually verify all quickstart.md scenarios in a browser (pagination controls, URL state, tag filter reset, single-page no-controls, out-of-range URL, empty state).
Dependencies & Execution Order
- T001 before T002 (write failing tests before implementation)
- T002 before T003 (US2 tests build on US1 implementation)
- T003 before T004 (write failing tests before implementation)
- T004 before T005 (polish after full implementation)
Execution Order Summary
Step 1: T001 (US1: failing tests)
Step 2: T002 (US1: implementation — tests turn green)
Step 3: T003 (US2: failing tests)
Step 4: T004 (US2: implementation — tests turn green)
Step 5: T005 (polish: lint + manual verification)
Implementation Strategy
MVP (US1 only)
- T001–T002 — page navigation controls, limit change, replace append
- STOP and VALIDATE: open browser, confirm pagination controls appear and work
- Deploy if ready
Full Delivery
- T001–T002 (US1) → validate
- T003–T004 (US2) → validate URL state
- T005 (polish) → ship